Initial public release
This commit is contained in:
parent
3c3f068914
commit
4ece4babad
63 changed files with 15101 additions and 0 deletions
218
src/tui/status/filestate.zig
Normal file
218
src/tui/status/filestate.zig
Normal file
|
@ -0,0 +1,218 @@
|
|||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const nc = @import("notcurses");
|
||||
const tp = @import("thespian");
|
||||
const tracy = @import("tracy");
|
||||
const root = @import("root");
|
||||
|
||||
const Widget = @import("../Widget.zig");
|
||||
const command = @import("../command.zig");
|
||||
const tui = @import("../tui.zig");
|
||||
|
||||
a: Allocator,
|
||||
parent: nc.Plane,
|
||||
plane: nc.Plane,
|
||||
name: []const u8,
|
||||
name_buf: [512]u8 = undefined,
|
||||
title: []const u8 = "",
|
||||
title_buf: [512]u8 = undefined,
|
||||
file_type: []const u8,
|
||||
file_type_buf: [64]u8 = undefined,
|
||||
file_icon: [:0]const u8 = "",
|
||||
file_icon_buf: [6]u8 = undefined,
|
||||
file_color: u24 = 0,
|
||||
line: usize,
|
||||
lines: usize,
|
||||
column: usize,
|
||||
file_exists: bool,
|
||||
file_dirty: bool = false,
|
||||
detailed: bool = false,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn create(a: Allocator, parent: nc.Plane) !Widget {
|
||||
const self: *Self = try a.create(Self);
|
||||
self.* = try init(a, parent);
|
||||
self.show_cwd();
|
||||
return Widget.to(self);
|
||||
}
|
||||
|
||||
fn init(a: Allocator, parent: nc.Plane) !Self {
|
||||
var n = try nc.Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent);
|
||||
errdefer n.deinit();
|
||||
|
||||
return .{
|
||||
.a = a,
|
||||
.parent = parent,
|
||||
.plane = n,
|
||||
.name = "",
|
||||
.file_type = "",
|
||||
.lines = 0,
|
||||
.line = 0,
|
||||
.column = 0,
|
||||
.file_exists = true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, a: Allocator) void {
|
||||
self.plane.deinit();
|
||||
a.destroy(self);
|
||||
}
|
||||
|
||||
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
||||
const frame = tracy.initZone(@src(), .{ .name = @typeName(@This()) ++ " render" });
|
||||
defer frame.deinit();
|
||||
tui.set_base_style(&self.plane, " ", theme.statusbar);
|
||||
self.plane.erase();
|
||||
self.plane.home();
|
||||
if (tui.current().mini_mode) |_|
|
||||
self.render_mini_mode(theme)
|
||||
else if (self.detailed)
|
||||
self.render_detailed(theme)
|
||||
else
|
||||
self.render_normal(theme);
|
||||
self.render_terminal_title();
|
||||
return false;
|
||||
}
|
||||
|
||||
fn render_mini_mode(self: *Self, theme: *const Widget.Theme) void {
|
||||
self.plane.off_styles(nc.style.italic);
|
||||
const mini_mode = if (tui.current().mini_mode) |m| m else return;
|
||||
_ = self.plane.print(" {s}", .{mini_mode.text}) catch {};
|
||||
if (mini_mode.cursor) |cursor| {
|
||||
const pos: c_int = @intCast(cursor);
|
||||
self.plane.cursor_move_yx(0, pos + 1) catch return;
|
||||
var cell = self.plane.cell_init();
|
||||
_ = self.plane.at_cursor_cell(&cell) catch return;
|
||||
tui.set_cell_style(&cell, theme.editor_cursor);
|
||||
_ = self.plane.putc(&cell) catch {};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Content save
|
||||
// Content save alert
|
||||
// Content save edit
|
||||
// Content save settings
|
||||
// Content save off
|
||||
// Content save check
|
||||
// Content save cog
|
||||
// Content save all
|
||||
fn render_normal(self: *Self, theme: *const Widget.Theme) void {
|
||||
self.plane.on_styles(nc.style.italic);
|
||||
_ = self.plane.putstr(" ") catch {};
|
||||
if (self.file_icon.len > 0) {
|
||||
self.render_file_icon(theme);
|
||||
_ = self.plane.print(" ", .{}) catch {};
|
||||
}
|
||||
_ = self.plane.putstr(if (!self.file_exists) " " else if (self.file_dirty) " " else "") catch {};
|
||||
_ = self.plane.print("{s}", .{self.name}) catch {};
|
||||
return;
|
||||
}
|
||||
|
||||
fn render_detailed(self: *Self, theme: *const Widget.Theme) void {
|
||||
self.plane.on_styles(nc.style.italic);
|
||||
_ = self.plane.putstr(" ") catch {};
|
||||
if (self.file_icon.len > 0) {
|
||||
self.render_file_icon(theme);
|
||||
_ = self.plane.print(" ", .{}) catch {};
|
||||
}
|
||||
_ = self.plane.putstr(if (!self.file_exists) "" else if (self.file_dirty) "" else "") catch {};
|
||||
_ = self.plane.print(" {s}:{d}:{d}", .{ self.name, self.line + 1, self.column + 1 }) catch {};
|
||||
_ = self.plane.print(" of {d} lines", .{self.lines}) catch {};
|
||||
if (self.file_type.len > 0)
|
||||
_ = self.plane.print(" ({s})", .{self.file_type}) catch {};
|
||||
return;
|
||||
}
|
||||
|
||||
fn render_terminal_title(self: *Self) void {
|
||||
const file_name = if (std.mem.lastIndexOfScalar(u8, self.name, '/')) |pos|
|
||||
self.name[pos + 1 ..]
|
||||
else if (self.name.len == 0)
|
||||
root.application_name
|
||||
else
|
||||
self.name;
|
||||
var new_title_buf: [512]u8 = undefined;
|
||||
const new_title = std.fmt.bufPrint(&new_title_buf, "{s}{s}", .{ if (!self.file_exists) "◌ " else if (self.file_dirty) " " else "", file_name }) catch return;
|
||||
if (std.mem.eql(u8, self.title, new_title)) return;
|
||||
@memcpy(self.title_buf[0..new_title.len], new_title);
|
||||
self.title = self.title_buf[0..new_title.len];
|
||||
tui.set_terminal_title(self.title);
|
||||
}
|
||||
|
||||
pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
||||
var file_path: []const u8 = undefined;
|
||||
var file_type: []const u8 = undefined;
|
||||
var file_icon: []const u8 = undefined;
|
||||
var file_dirty: bool = undefined;
|
||||
if (try m.match(.{ "E", "pos", tp.extract(&self.lines), tp.extract(&self.line), tp.extract(&self.column) }))
|
||||
return false;
|
||||
if (try m.match(.{ "E", "dirty", tp.extract(&file_dirty) })) {
|
||||
self.file_dirty = file_dirty;
|
||||
} else if (try m.match(.{ "E", "save", tp.extract(&file_path) })) {
|
||||
@memcpy(self.name_buf[0..file_path.len], file_path);
|
||||
self.name = self.name_buf[0..file_path.len];
|
||||
self.file_exists = true;
|
||||
self.file_dirty = false;
|
||||
self.abbrv_home();
|
||||
} else if (try m.match(.{ "E", "open", tp.extract(&file_path), tp.extract(&self.file_exists), tp.extract(&file_type), tp.extract(&file_icon), tp.extract(&self.file_color) })) {
|
||||
@memcpy(self.name_buf[0..file_path.len], file_path);
|
||||
self.name = self.name_buf[0..file_path.len];
|
||||
@memcpy(self.file_type_buf[0..file_type.len], file_type);
|
||||
self.file_type = self.file_type_buf[0..file_type.len];
|
||||
@memcpy(self.file_icon_buf[0..file_icon.len], file_icon);
|
||||
self.file_icon_buf[file_icon.len] = 0;
|
||||
self.file_icon = self.file_icon_buf[0..file_icon.len :0];
|
||||
self.file_dirty = false;
|
||||
self.abbrv_home();
|
||||
} else if (try m.match(.{ "E", "close" })) {
|
||||
self.name = "";
|
||||
self.lines = 0;
|
||||
self.line = 0;
|
||||
self.column = 0;
|
||||
self.file_exists = true;
|
||||
self.show_cwd();
|
||||
}
|
||||
if (try m.match(.{ "B", nc.event_type.PRESS, nc.key.BUTTON1, tp.any, tp.any, tp.any, tp.any, tp.any })) {
|
||||
self.detailed = !self.detailed;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn render_file_icon(self: *Self, _: *const Widget.Theme) void {
|
||||
var cell = self.plane.cell_init();
|
||||
_ = self.plane.at_cursor_cell(&cell) catch return;
|
||||
if (self.file_color != 0x000001) {
|
||||
nc.channels_set_fg_rgb(&cell.channels, self.file_color) catch {};
|
||||
nc.channels_set_fg_alpha(&cell.channels, nc.ALPHA_OPAQUE) catch {};
|
||||
}
|
||||
_ = self.plane.cell_load(&cell, self.file_icon) catch {};
|
||||
_ = self.plane.putc(&cell) catch {};
|
||||
self.plane.cursor_move_rel(0, 1) catch {};
|
||||
}
|
||||
|
||||
fn show_cwd(self: *Self) void {
|
||||
self.file_icon = "";
|
||||
self.file_color = 0x000001;
|
||||
self.name = std.fs.cwd().realpath(".", &self.name_buf) catch "(none)";
|
||||
self.abbrv_home();
|
||||
}
|
||||
|
||||
fn abbrv_home(self: *Self) void {
|
||||
if (std.fs.path.isAbsolute(self.name)) {
|
||||
if (std.os.getenv("HOME")) |homedir| {
|
||||
const homerelpath = std.fs.path.relative(self.a, homedir, self.name) catch return;
|
||||
if (homerelpath.len == 0) {
|
||||
self.name = "~";
|
||||
} else if (homerelpath.len > 3 and std.mem.eql(u8, homerelpath[0..3], "../")) {
|
||||
return;
|
||||
} else {
|
||||
self.name_buf[0] = '~';
|
||||
self.name_buf[1] = '/';
|
||||
@memcpy(self.name_buf[2 .. homerelpath.len + 2], homerelpath);
|
||||
self.name = self.name_buf[0 .. homerelpath.len + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
211
src/tui/status/keystate.zig
Normal file
211
src/tui/status/keystate.zig
Normal file
|
@ -0,0 +1,211 @@
|
|||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const nc = @import("notcurses");
|
||||
const tp = @import("thespian");
|
||||
const tracy = @import("tracy");
|
||||
|
||||
const Widget = @import("../Widget.zig");
|
||||
const command = @import("../command.zig");
|
||||
const tui = @import("../tui.zig");
|
||||
const EventHandler = @import("../EventHandler.zig");
|
||||
|
||||
const history = 8;
|
||||
|
||||
parent: nc.Plane,
|
||||
plane: nc.Plane,
|
||||
frame: u64 = 0,
|
||||
idle_frame: u64 = 0,
|
||||
key_active_frame: u64 = 0,
|
||||
wipe_after_frames: i64 = 60,
|
||||
hover: bool = false,
|
||||
|
||||
keys: [history]Key = [_]Key{.{}} ** history,
|
||||
|
||||
const Key = struct { id: u32 = 0, mod: u32 = 0 };
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const idle_msg = "🐶";
|
||||
pub const width = idle_msg.len + 20;
|
||||
|
||||
pub fn create(a: Allocator, parent: nc.Plane) !Widget {
|
||||
const self: *Self = try a.create(Self);
|
||||
self.* = try init(parent);
|
||||
try tui.current().input_listeners.add(EventHandler.bind(self, listen));
|
||||
return self.widget();
|
||||
}
|
||||
|
||||
fn init(parent: nc.Plane) !Self {
|
||||
var n = try nc.Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent);
|
||||
errdefer n.deinit();
|
||||
var frame_rate = tp.env.get().num("frame-rate");
|
||||
if (frame_rate == 0) frame_rate = 60;
|
||||
|
||||
return .{
|
||||
.parent = parent,
|
||||
.plane = n,
|
||||
.wipe_after_frames = @divTrunc(frame_rate, 2),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn widget(self: *Self) Widget {
|
||||
return Widget.to(self);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, a: Allocator) void {
|
||||
tui.current().input_listeners.remove_ptr(self);
|
||||
self.plane.deinit();
|
||||
a.destroy(self);
|
||||
}
|
||||
|
||||
pub fn layout(_: *Self) Widget.Layout {
|
||||
return .{ .static = width };
|
||||
}
|
||||
|
||||
fn render_active(self: *Self) bool {
|
||||
var c: usize = 0;
|
||||
for (self.keys) |k| {
|
||||
if (k.id == 0)
|
||||
return true;
|
||||
if (c > 0)
|
||||
_ = self.plane.putstr(" ") catch {};
|
||||
if (nc.isSuper(k.mod))
|
||||
_ = self.plane.putstr("H-") catch {};
|
||||
if (nc.isCtrl(k.mod))
|
||||
_ = self.plane.putstr("C-") catch {};
|
||||
if (nc.isShift(k.mod))
|
||||
_ = self.plane.putstr("S-") catch {};
|
||||
if (nc.isAlt(k.mod))
|
||||
_ = self.plane.putstr("A-") catch {};
|
||||
_ = self.plane.print("{s}", .{nc.key_id_string(k.id)}) catch {};
|
||||
c += 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const idle_spinner = [_][]const u8{ "🞻", "✳", "🞼", "🞽", "🞾", "🞿", "🞾", "🞽", "🞼", "✳" };
|
||||
|
||||
fn render_idle(self: *Self) bool {
|
||||
self.idle_frame += 1;
|
||||
if (self.idle_frame > 180) {
|
||||
return self.animate();
|
||||
} else {
|
||||
const i = @mod(self.idle_frame / 8, idle_spinner.len);
|
||||
_ = self.plane.print_aligned(0, .center, "{s} {s} {s}", .{ idle_spinner[i], idle_msg, idle_spinner[i] }) catch {};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
||||
const frame = tracy.initZone(@src(), .{ .name = @typeName(@This()) ++ " render" });
|
||||
defer frame.deinit();
|
||||
tui.set_base_style(&self.plane, " ", if (self.hover) theme.statusbar_hover else theme.statusbar);
|
||||
self.frame += 1;
|
||||
if (self.frame - self.key_active_frame > self.wipe_after_frames)
|
||||
self.unset_key_all();
|
||||
|
||||
self.plane.erase();
|
||||
self.plane.home();
|
||||
return if (self.keys[0].id > 0) self.render_active() else self.render_idle();
|
||||
}
|
||||
|
||||
fn set_nkey(self: *Self, key: Key) void {
|
||||
for (self.keys, 0..) |k, i| {
|
||||
if (k.id == 0) {
|
||||
self.keys[i].id = key.id;
|
||||
self.keys[i].mod = key.mod;
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (self.keys, 0.., 1..) |_, i, j| {
|
||||
if (j < self.keys.len)
|
||||
self.keys[i] = self.keys[j];
|
||||
}
|
||||
self.keys[self.keys.len - 1].id = key.id;
|
||||
self.keys[self.keys.len - 1].mod = key.mod;
|
||||
}
|
||||
|
||||
fn unset_nkey_(self: *Self, key: u32) void {
|
||||
for (self.keys, 0..) |k, i| {
|
||||
if (k.id == key) {
|
||||
for (i..self.keys.len, (i + 1)..) |i_, j| {
|
||||
if (j < self.keys.len)
|
||||
self.keys[i_] = self.keys[j];
|
||||
}
|
||||
self.keys[self.keys.len - 1].id = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const upper_offset: u32 = 'a' - 'A';
|
||||
fn unset_nkey(self: *Self, key: Key) void {
|
||||
self.unset_nkey_(key.id);
|
||||
if (key.id >= 'a' and key.id <= 'z')
|
||||
self.unset_nkey_(key.id - upper_offset);
|
||||
if (key.id >= 'A' and key.id <= 'Z')
|
||||
self.unset_nkey_(key.id + upper_offset);
|
||||
}
|
||||
|
||||
fn unset_key_all(self: *Self) void {
|
||||
for (0..self.keys.len) |i| {
|
||||
self.keys[i].id = 0;
|
||||
self.keys[i].mod = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_key(self: *Self, key: Key, val: bool) void {
|
||||
self.idle_frame = 0;
|
||||
self.key_active_frame = self.frame;
|
||||
(if (val) &set_nkey else &unset_nkey)(self, key);
|
||||
}
|
||||
|
||||
pub fn listen(self: *Self, _: tp.pid_ref, m: tp.message) tp.result {
|
||||
var key: u32 = 0;
|
||||
var mod: u32 = 0;
|
||||
if (try m.match(.{ "I", nc.event_type.PRESS, tp.extract(&key), tp.any, tp.any, tp.extract(&mod), tp.more })) {
|
||||
self.set_key(.{ .id = key, .mod = mod }, true);
|
||||
} else if (try m.match(.{ "I", nc.event_type.RELEASE, tp.extract(&key), tp.any, tp.any, tp.extract(&mod), tp.more })) {
|
||||
self.set_key(.{ .id = key, .mod = mod }, false);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
||||
if (try m.match(.{ "B", nc.event_type.PRESS, nc.key.BUTTON1, tp.any, tp.any, tp.any, tp.any, tp.any })) {
|
||||
command.executeName("toggle_inputview", .{}) catch {};
|
||||
return true;
|
||||
}
|
||||
if (try m.match(.{ "H", tp.extract(&self.hover) })) {
|
||||
tui.current().request_mouse_cursor_pointer(self.hover);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn animate(self: *Self) bool {
|
||||
const positions = eighths_c * (width - 1);
|
||||
const frame = @mod(self.frame, positions * 2);
|
||||
const pos = if (frame > eighths_c * (width - 1))
|
||||
positions * 2 - frame
|
||||
else
|
||||
frame;
|
||||
|
||||
smooth_block_at(self.plane, pos);
|
||||
return false;
|
||||
// return pos != 0;
|
||||
}
|
||||
|
||||
const eighths_l = [_][]const u8{ "█", "▉", "▊", "▋", "▌", "▍", "▎", "▏" };
|
||||
const eighths_r = [_][]const u8{ " ", "▕", "🮇", "🮈", "▐", "🮉", "🮊", "🮋" };
|
||||
const eighths_c = eighths_l.len;
|
||||
|
||||
fn smooth_block_at(plane: nc.Plane, pos: u64) void {
|
||||
const blk = @mod(pos, eighths_c) + 1;
|
||||
const l = eighths_l[eighths_c - blk];
|
||||
const r = eighths_r[eighths_c - blk];
|
||||
plane.erase();
|
||||
plane.cursor_move_yx(0, @as(c_int, @intCast(@divFloor(pos, eighths_c)))) catch return;
|
||||
_ = plane.putstr(@ptrCast(r)) catch return;
|
||||
_ = plane.putstr(@ptrCast(l)) catch return;
|
||||
}
|
71
src/tui/status/linenumstate.zig
Normal file
71
src/tui/status/linenumstate.zig
Normal file
|
@ -0,0 +1,71 @@
|
|||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const nc = @import("notcurses");
|
||||
const tp = @import("thespian");
|
||||
const tracy = @import("tracy");
|
||||
|
||||
const Widget = @import("../Widget.zig");
|
||||
const tui = @import("../tui.zig");
|
||||
|
||||
parent: nc.Plane,
|
||||
plane: nc.Plane,
|
||||
line: usize = 0,
|
||||
lines: usize = 0,
|
||||
column: usize = 0,
|
||||
buf: [256]u8 = undefined,
|
||||
rendered: [:0]const u8 = "",
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn create(a: Allocator, parent: nc.Plane) !Widget {
|
||||
const self: *Self = try a.create(Self);
|
||||
self.* = try init(parent);
|
||||
return Widget.to(self);
|
||||
}
|
||||
|
||||
fn init(parent: nc.Plane) !Self {
|
||||
var n = try nc.Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent);
|
||||
errdefer n.deinit();
|
||||
|
||||
return .{
|
||||
.parent = parent,
|
||||
.plane = n,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, a: Allocator) void {
|
||||
self.plane.deinit();
|
||||
a.destroy(self);
|
||||
}
|
||||
|
||||
pub fn layout(self: *Self) Widget.Layout {
|
||||
return .{ .static = self.rendered.len };
|
||||
}
|
||||
|
||||
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
||||
tui.set_base_style(&self.plane, " ", theme.statusbar);
|
||||
self.plane.erase();
|
||||
self.plane.home();
|
||||
_ = self.plane.putstr(self.rendered) catch {};
|
||||
return false;
|
||||
}
|
||||
|
||||
fn format(self: *Self) void {
|
||||
var fbs = std.io.fixedBufferStream(&self.buf);
|
||||
const writer = fbs.writer();
|
||||
std.fmt.format(writer, " Ln {d}, Col {d} ", .{ self.line + 1, self.column + 1 }) catch {};
|
||||
self.rendered = @ptrCast(fbs.getWritten());
|
||||
self.buf[self.rendered.len] = 0;
|
||||
}
|
||||
|
||||
pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
||||
if (try m.match(.{ "E", "pos", tp.extract(&self.lines), tp.extract(&self.line), tp.extract(&self.column) })) {
|
||||
self.format();
|
||||
} else if (try m.match(.{ "E", "close" })) {
|
||||
self.lines = 0;
|
||||
self.line = 0;
|
||||
self.column = 0;
|
||||
self.rendered = "";
|
||||
}
|
||||
return false;
|
||||
}
|
110
src/tui/status/minilog.zig
Normal file
110
src/tui/status/minilog.zig
Normal file
|
@ -0,0 +1,110 @@
|
|||
const std = @import("std");
|
||||
const nc = @import("notcurses");
|
||||
const tp = @import("thespian");
|
||||
const log = @import("log");
|
||||
|
||||
const Widget = @import("../Widget.zig");
|
||||
const MessageFilter = @import("../MessageFilter.zig");
|
||||
const tui = @import("../tui.zig");
|
||||
const mainview = @import("../mainview.zig");
|
||||
|
||||
parent: nc.Plane,
|
||||
plane: nc.Plane,
|
||||
msg: std.ArrayList(u8),
|
||||
is_error: bool = false,
|
||||
timer: ?tp.timeout = null,
|
||||
|
||||
const message_display_time_seconds = 2;
|
||||
const error_display_time_seconds = 4;
|
||||
const Self = @This();
|
||||
|
||||
pub fn create(a: std.mem.Allocator, parent: nc.Plane) !Widget {
|
||||
const self: *Self = try a.create(Self);
|
||||
self.* = .{
|
||||
.parent = parent,
|
||||
.plane = try nc.Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent),
|
||||
.msg = std.ArrayList(u8).init(a),
|
||||
};
|
||||
try tui.current().message_filters.add(MessageFilter.bind(self, log_receive));
|
||||
try log.subscribe();
|
||||
return Widget.to(self);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, a: std.mem.Allocator) void {
|
||||
self.cancel_timer();
|
||||
self.msg.deinit();
|
||||
log.unsubscribe() catch {};
|
||||
tui.current().message_filters.remove_ptr(self);
|
||||
self.plane.deinit();
|
||||
a.destroy(self);
|
||||
}
|
||||
|
||||
pub fn layout(self: *Self) Widget.Layout {
|
||||
return .{ .static = if (self.msg.items.len > 0) self.msg.items.len + 2 else 1 };
|
||||
}
|
||||
|
||||
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
||||
tui.set_base_style(&self.plane, " ", if (self.msg.items.len > 0) theme.sidebar else theme.statusbar);
|
||||
self.plane.erase();
|
||||
self.plane.home();
|
||||
if (self.is_error)
|
||||
tui.set_base_style(&self.plane, " ", theme.editor_error);
|
||||
_ = self.plane.print(" {s} ", .{self.msg.items}) catch return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn log_receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
||||
if (try m.match(.{ "log", tp.more })) {
|
||||
self.log_process(m) catch |e| return tp.exit_error(e);
|
||||
if (tui.current().mainview.dynamic_cast(mainview)) |mv_| if (mv_.logview_enabled)
|
||||
return false; // pass on log messages to logview
|
||||
return true;
|
||||
} else if (try m.match(.{ "minilog", "clear" })) {
|
||||
self.is_error = false;
|
||||
self.cancel_timer();
|
||||
self.msg.clearRetainingCapacity();
|
||||
Widget.need_render();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn log_process(self: *Self, m: tp.message) !void {
|
||||
var src: []const u8 = undefined;
|
||||
var context: []const u8 = undefined;
|
||||
var msg: []const u8 = undefined;
|
||||
if (try m.match(.{ "log", tp.extract(&src), tp.extract(&msg) })) {
|
||||
if (self.is_error) return;
|
||||
self.reset_timer();
|
||||
self.msg.clearRetainingCapacity();
|
||||
try self.msg.appendSlice(msg);
|
||||
Widget.need_render();
|
||||
} else if (try m.match(.{ "log", "error", tp.extract(&src), tp.extract(&context), "->", tp.extract(&msg) })) {
|
||||
self.is_error = true;
|
||||
self.reset_timer();
|
||||
self.msg.clearRetainingCapacity();
|
||||
try self.msg.appendSlice(msg);
|
||||
Widget.need_render();
|
||||
} else if (try m.match(.{ "log", tp.extract(&src), tp.more })) {
|
||||
self.is_error = true;
|
||||
self.reset_timer();
|
||||
self.msg.clearRetainingCapacity();
|
||||
var s = std.json.writeStream(self.msg.writer(), .{});
|
||||
var iter: []const u8 = m.buf;
|
||||
try @import("cbor").JsonStream(@TypeOf(self.msg)).jsonWriteValue(&s, &iter);
|
||||
Widget.need_render();
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_timer(self: *Self) void {
|
||||
self.cancel_timer();
|
||||
const delay: u64 = std.time.ms_per_s * @as(u64, if (self.is_error) error_display_time_seconds else message_display_time_seconds);
|
||||
self.timer = tp.timeout.init_ms(delay, tp.message.fmt(.{ "minilog", "clear" })) catch null;
|
||||
}
|
||||
|
||||
fn cancel_timer(self: *Self) void {
|
||||
if (self.timer) |*timer| {
|
||||
timer.deinit();
|
||||
self.timer = null;
|
||||
}
|
||||
}
|
74
src/tui/status/modestate.zig
Normal file
74
src/tui/status/modestate.zig
Normal file
|
@ -0,0 +1,74 @@
|
|||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const nc = @import("notcurses");
|
||||
const tp = @import("thespian");
|
||||
const tracy = @import("tracy");
|
||||
const root = @import("root");
|
||||
|
||||
const Widget = @import("../Widget.zig");
|
||||
const command = @import("../command.zig");
|
||||
const ed = @import("../editor.zig");
|
||||
const tui = @import("../tui.zig");
|
||||
|
||||
parent: nc.Plane,
|
||||
plane: nc.Plane,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn create(a: Allocator, parent: nc.Plane) !Widget {
|
||||
const self: *Self = try a.create(Self);
|
||||
self.* = try init(parent);
|
||||
return Widget.to(self);
|
||||
}
|
||||
|
||||
fn init(parent: nc.Plane) !Self {
|
||||
var n = try nc.Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent);
|
||||
errdefer n.deinit();
|
||||
|
||||
return .{
|
||||
.parent = parent,
|
||||
.plane = n,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, a: Allocator) void {
|
||||
self.plane.deinit();
|
||||
a.destroy(self);
|
||||
}
|
||||
|
||||
pub fn layout(_: *Self) Widget.Layout {
|
||||
return .{ .static = if (is_mini_mode()) tui.get_mode().len + 5 else tui.get_mode().len - 1 };
|
||||
}
|
||||
|
||||
fn is_mini_mode() bool {
|
||||
return if (tui.current().mini_mode) |_| true else false;
|
||||
}
|
||||
|
||||
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
||||
if (is_mini_mode())
|
||||
self.render_mode(theme)
|
||||
else
|
||||
self.render_logo(theme);
|
||||
return false;
|
||||
}
|
||||
|
||||
fn render_mode(self: *Self, theme: *const Widget.Theme) void {
|
||||
tui.set_base_style(&self.plane, " ", theme.statusbar_hover);
|
||||
self.plane.on_styles(nc.style.bold);
|
||||
self.plane.erase();
|
||||
self.plane.home();
|
||||
var buf: [31:0]u8 = undefined;
|
||||
_ = self.plane.putstr(std.fmt.bufPrintZ(&buf, " {s} ", .{tui.get_mode()}) catch return) catch {};
|
||||
if (theme.statusbar_hover.bg) |bg| self.plane.set_fg_rgb(bg) catch {};
|
||||
if (theme.statusbar.bg) |bg| self.plane.set_bg_rgb(bg) catch {};
|
||||
_ = self.plane.putstr("") catch {};
|
||||
}
|
||||
|
||||
fn render_logo(self: *Self, theme: *const Widget.Theme) void {
|
||||
tui.set_base_style(&self.plane, " ", theme.statusbar_hover);
|
||||
self.plane.on_styles(nc.style.bold);
|
||||
self.plane.erase();
|
||||
self.plane.home();
|
||||
var buf: [31:0]u8 = undefined;
|
||||
_ = self.plane.putstr(std.fmt.bufPrintZ(&buf, " {s} ", .{tui.get_mode()}) catch return) catch {};
|
||||
}
|
102
src/tui/status/modstate.zig
Normal file
102
src/tui/status/modstate.zig
Normal file
|
@ -0,0 +1,102 @@
|
|||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const nc = @import("notcurses");
|
||||
const tp = @import("thespian");
|
||||
const tracy = @import("tracy");
|
||||
|
||||
const Widget = @import("../Widget.zig");
|
||||
const command = @import("../command.zig");
|
||||
const tui = @import("../tui.zig");
|
||||
const EventHandler = @import("../EventHandler.zig");
|
||||
|
||||
parent: nc.Plane,
|
||||
plane: nc.Plane,
|
||||
ctrl: bool = false,
|
||||
shift: bool = false,
|
||||
alt: bool = false,
|
||||
hover: bool = false,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const width = 5;
|
||||
|
||||
pub fn create(a: Allocator, parent: nc.Plane) !Widget {
|
||||
const self: *Self = try a.create(Self);
|
||||
self.* = try init(parent);
|
||||
try tui.current().input_listeners.add(EventHandler.bind(self, listen));
|
||||
return self.widget();
|
||||
}
|
||||
|
||||
fn init(parent: nc.Plane) !Self {
|
||||
var n = try nc.Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent);
|
||||
errdefer n.deinit();
|
||||
return .{
|
||||
.parent = parent,
|
||||
.plane = n,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn widget(self: *Self) Widget {
|
||||
return Widget.to(self);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, a: Allocator) void {
|
||||
tui.current().input_listeners.remove_ptr(self);
|
||||
self.plane.deinit();
|
||||
a.destroy(self);
|
||||
}
|
||||
|
||||
pub fn layout(_: *Self) Widget.Layout {
|
||||
return .{ .static = width };
|
||||
}
|
||||
|
||||
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
||||
const frame = tracy.initZone(@src(), .{ .name = @typeName(@This()) ++ " render" });
|
||||
defer frame.deinit();
|
||||
tui.set_base_style(&self.plane, " ", if (self.hover) theme.statusbar_hover else theme.statusbar);
|
||||
self.plane.erase();
|
||||
self.plane.home();
|
||||
|
||||
_ = self.plane.print("\u{2003}{s}{s}{s}\u{2003}", .{
|
||||
mode(self.ctrl, "Ⓒ", "🅒"),
|
||||
mode(self.shift, "Ⓢ", "🅢"),
|
||||
mode(self.alt, "Ⓐ", "🅐"),
|
||||
}) catch {};
|
||||
return false;
|
||||
}
|
||||
|
||||
inline fn mode(state: bool, off: [:0]const u8, on: [:0]const u8) [:0]const u8 {
|
||||
return if (state) on else off;
|
||||
}
|
||||
|
||||
fn render_modifier(self: *Self, state: bool, off: [:0]const u8, on: [:0]const u8) void {
|
||||
_ = self.plane.putstr(if (state) on else off) catch {};
|
||||
}
|
||||
|
||||
fn set_modifiers(self: *Self, key: u32, mods: u32) void {
|
||||
const modifiers = switch (key) {
|
||||
nc.key.LCTRL, nc.key.RCTRL => mods ^ nc.mod.CTRL,
|
||||
nc.key.LSHIFT, nc.key.RSHIFT => mods ^ nc.mod.SHIFT,
|
||||
nc.key.LALT, nc.key.RALT => mods ^ nc.mod.ALT,
|
||||
else => mods,
|
||||
};
|
||||
|
||||
self.ctrl = nc.isCtrl(modifiers);
|
||||
self.shift = nc.isShift(modifiers);
|
||||
self.alt = nc.isAlt(modifiers);
|
||||
}
|
||||
|
||||
pub fn listen(self: *Self, _: tp.pid_ref, m: tp.message) tp.result {
|
||||
var key: u32 = 0;
|
||||
var mod: u32 = 0;
|
||||
if (try m.match(.{ "I", tp.any, tp.extract(&key), tp.any, tp.any, tp.extract(&mod), tp.more }))
|
||||
self.set_modifiers(key, mod);
|
||||
}
|
||||
|
||||
pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
||||
if (try m.match(.{ "B", nc.event_type.PRESS, nc.key.BUTTON1, tp.any, tp.any, tp.any, tp.any, tp.any })) {
|
||||
command.executeName("toggle_inputview", .{}) catch {};
|
||||
return true;
|
||||
}
|
||||
return try m.match(.{ "H", tp.extract(&self.hover) });
|
||||
}
|
105
src/tui/status/selectionstate.zig
Normal file
105
src/tui/status/selectionstate.zig
Normal file
|
@ -0,0 +1,105 @@
|
|||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const nc = @import("notcurses");
|
||||
const tp = @import("thespian");
|
||||
const tracy = @import("tracy");
|
||||
|
||||
const Widget = @import("../Widget.zig");
|
||||
const ed = @import("../editor.zig");
|
||||
const tui = @import("../tui.zig");
|
||||
|
||||
parent: nc.Plane,
|
||||
plane: nc.Plane,
|
||||
matches: usize = 0,
|
||||
cursels: usize = 0,
|
||||
selection: ?ed.Selection = null,
|
||||
buf: [256]u8 = undefined,
|
||||
rendered: [:0]const u8 = "",
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn create(a: Allocator, parent: nc.Plane) !Widget {
|
||||
const self: *Self = try a.create(Self);
|
||||
self.* = try init(parent);
|
||||
return Widget.to(self);
|
||||
}
|
||||
|
||||
fn init(parent: nc.Plane) !Self {
|
||||
var n = try nc.Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent);
|
||||
errdefer n.deinit();
|
||||
|
||||
return .{
|
||||
.parent = parent,
|
||||
.plane = n,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, a: Allocator) void {
|
||||
self.plane.deinit();
|
||||
a.destroy(self);
|
||||
}
|
||||
|
||||
pub fn layout(self: *Self) Widget.Layout {
|
||||
return .{ .static = self.rendered.len };
|
||||
}
|
||||
|
||||
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
||||
const frame = tracy.initZone(@src(), .{ .name = @typeName(@This()) ++ " render" });
|
||||
defer frame.deinit();
|
||||
tui.set_base_style(&self.plane, " ", theme.statusbar);
|
||||
self.plane.erase();
|
||||
self.plane.home();
|
||||
_ = self.plane.putstr(self.rendered) catch {};
|
||||
return false;
|
||||
}
|
||||
|
||||
fn format(self: *Self) void {
|
||||
var fbs = std.io.fixedBufferStream(&self.buf);
|
||||
const writer = fbs.writer();
|
||||
_ = writer.write(" ") catch {};
|
||||
if (self.matches > 1) {
|
||||
std.fmt.format(writer, "({d} matches)", .{self.matches}) catch {};
|
||||
if (self.selection) |_|
|
||||
_ = writer.write(" ") catch {};
|
||||
}
|
||||
if (self.cursels > 1) {
|
||||
std.fmt.format(writer, "({d} cursels)", .{self.cursels}) catch {};
|
||||
if (self.selection) |_|
|
||||
_ = writer.write(" ") catch {};
|
||||
}
|
||||
if (self.selection) |sel_| {
|
||||
var sel = sel_;
|
||||
sel.normalize();
|
||||
const lines = sel.end.row - sel.begin.row;
|
||||
if (lines == 0) {
|
||||
std.fmt.format(writer, "({d} selected)", .{sel.end.col - sel.begin.col}) catch {};
|
||||
} else {
|
||||
std.fmt.format(writer, "({d} lines selected)", .{if (sel.end.col == 0) lines else lines + 1}) catch {};
|
||||
}
|
||||
}
|
||||
_ = writer.write(" ") catch {};
|
||||
self.rendered = @ptrCast(fbs.getWritten());
|
||||
self.buf[self.rendered.len] = 0;
|
||||
}
|
||||
|
||||
pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
||||
if (try m.match(.{ "E", "match", tp.extract(&self.matches) }))
|
||||
self.format();
|
||||
if (try m.match(.{ "E", "cursels", tp.extract(&self.cursels) }))
|
||||
self.format();
|
||||
if (try m.match(.{ "E", "close" })) {
|
||||
self.matches = 0;
|
||||
self.selection = null;
|
||||
self.format();
|
||||
} else if (try m.match(.{ "E", "sel", tp.more })) {
|
||||
var sel: ed.Selection = undefined;
|
||||
if (try m.match(.{ tp.any, tp.any, "none" })) {
|
||||
self.matches = 0;
|
||||
self.selection = null;
|
||||
} else if (try m.match(.{ tp.any, tp.any, tp.extract(&sel.begin.row), tp.extract(&sel.begin.col), tp.extract(&sel.end.row), tp.extract(&sel.end.col) })) {
|
||||
self.selection = sel;
|
||||
}
|
||||
self.format();
|
||||
}
|
||||
return false;
|
||||
}
|
23
src/tui/status/statusbar.zig
Normal file
23
src/tui/status/statusbar.zig
Normal file
|
@ -0,0 +1,23 @@
|
|||
const std = @import("std");
|
||||
const nc = @import("notcurses");
|
||||
|
||||
const Widget = @import("../Widget.zig");
|
||||
const WidgetList = @import("../WidgetList.zig");
|
||||
const tui = @import("../tui.zig");
|
||||
|
||||
parent: nc.Plane,
|
||||
plane: nc.Plane,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn create(a: std.mem.Allocator, parent: Widget) !Widget {
|
||||
var w = try WidgetList.createH(a, parent, "statusbar", .{ .static = 1 });
|
||||
if (tui.current().config.modestate_show) try w.add(try @import("modestate.zig").create(a, w.plane));
|
||||
try w.add(try @import("filestate.zig").create(a, w.plane));
|
||||
try w.add(try @import("minilog.zig").create(a, w.plane));
|
||||
if (tui.current().config.selectionstate_show) try w.add(try @import("selectionstate.zig").create(a, w.plane));
|
||||
try w.add(try @import("linenumstate.zig").create(a, w.plane));
|
||||
if (tui.current().config.modstate_show) try w.add(try @import("modstate.zig").create(a, w.plane));
|
||||
if (tui.current().config.keystate_show) try w.add(try @import("keystate.zig").create(a, w.plane));
|
||||
return w.widget();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue