Compare commits
40 commits
ac2a7cfa83
...
a27c212461
| Author | SHA1 | Date | |
|---|---|---|---|
| a27c212461 | |||
| e95b232184 | |||
| c67c0b0c94 | |||
| af9b097077 | |||
| 17b3f152d5 | |||
| 4f912cebeb | |||
| d872e2e734 | |||
| 8107a0d2b8 | |||
| cef495cb53 | |||
| 7bd9c972e7 | |||
| 5ce458e636 | |||
| 5f77a48afe | |||
| 469e10d4d9 | |||
| cea8edecb9 | |||
| c640c3f04b | |||
| 2414f3b00f | |||
| 12f6b884df | |||
| 5294ace5da | |||
| 3f61e46dfe | |||
| 92b1354d4d | |||
| 4d2c7d8a8c | |||
| c50ab782ec | |||
| f3296482d0 | |||
| 38236bd93a | |||
| d07f0f5f35 | |||
| ea1ae2228e | |||
| b46e6edbca | |||
| bcfd17a0e2 | |||
| fbc49c3dab | |||
| 21f7b14970 | |||
| 5b852fdb3d | |||
| deee1afe13 | |||
| ea5843bc2c | |||
| 794f8be2be | |||
| d2c4fb66bd | |||
| b5a506450b | |||
| d132df2d78 | |||
| c12f384a4f | |||
| ae39016f03 | |||
| 83a0adccc7 |
19 changed files with 678 additions and 219 deletions
|
|
@ -80,7 +80,7 @@ pub fn extract_state(self: *Self, iter: *[]const u8) !void {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_buffer_for_file(self: *Self, file_path: []const u8) ?*Buffer {
|
||||
pub fn get_buffer_for_file(self: *const Self, file_path: []const u8) ?*Buffer {
|
||||
return self.buffers.get(file_path);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ const Vtable = struct {
|
|||
pub const Metadata = struct {
|
||||
description: []const u8 = &[_]u8{},
|
||||
arguments: []const ArgumentType = &[_]ArgumentType{},
|
||||
icon: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub const ArgumentType = enum {
|
||||
|
|
@ -188,6 +189,11 @@ pub fn get_arguments(id: ID) ?[]const ArgumentType {
|
|||
return (commands.items[id] orelse return null).meta.arguments;
|
||||
}
|
||||
|
||||
pub fn get_icon(id: ID) ?[]const u8 {
|
||||
if (id >= commands.items.len) return null;
|
||||
return (commands.items[id] orelse return null).meta.icon;
|
||||
}
|
||||
|
||||
const suppressed_errors = std.StaticStringMap(void).initComptime(.{
|
||||
.{ "enable_fast_scroll", void },
|
||||
.{ "disable_fast_scroll", void },
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@
|
|||
["f10", "theme_next"],
|
||||
["f11", "toggle_panel"],
|
||||
["f12", "toggle_inputview"],
|
||||
["alt+!", "select_task"],
|
||||
["alt+!", "run_task"],
|
||||
["ctrl+1", "add_task"],
|
||||
["ctrl+tab", "next_tab"],
|
||||
["ctrl+shift+tab", "previous_tab"],
|
||||
["ctrl+shift+e", "switch_buffers"],
|
||||
|
|
@ -253,6 +254,7 @@
|
|||
"inherit": "project",
|
||||
"on_match_failure": "ignore",
|
||||
"press": [
|
||||
["alt+f9", "home_next_widget_style"],
|
||||
["ctrl+e", "find_file"],
|
||||
["f", "find_file"],
|
||||
["e", "find_file"],
|
||||
|
|
@ -283,6 +285,8 @@
|
|||
},
|
||||
"overlay/palette": {
|
||||
"press": [
|
||||
["alt+f9", "overlay_next_widget_style"],
|
||||
["alt+!", "add_task"],
|
||||
["ctrl+j", "toggle_panel"],
|
||||
["ctrl+q", "quit"],
|
||||
["ctrl+w", "close_file"],
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const OutOfMemoryError = error{OutOfMemory};
|
|||
const FileSystemError = error{FileSystem};
|
||||
const SetCwdError = if (builtin.os.tag == .windows) error{UnrecognizedVolume} else error{};
|
||||
const CallError = tp.CallError;
|
||||
const ProjectManagerError = (SpawnError || error{ProjectManagerFailed});
|
||||
const ProjectManagerError = (SpawnError || error{ ProjectManagerFailed, InvalidProjectDirectory });
|
||||
|
||||
pub fn get() SpawnError!Self {
|
||||
const pid = tp.env.get().proc(module_name);
|
||||
|
|
@ -63,6 +63,7 @@ pub fn open(rel_project_directory: []const u8) (ProjectManagerError || FileSyste
|
|||
const project_directory = std.fs.cwd().realpath(rel_project_directory, &path_buf) catch "(none)";
|
||||
const current_project = tp.env.get().str("project");
|
||||
if (std.mem.eql(u8, current_project, project_directory)) return;
|
||||
if (!root.is_directory(project_directory)) return error.InvalidProjectDirectory;
|
||||
var dir = try std.fs.openDirAbsolute(project_directory, .{});
|
||||
try dir.setAsCwd();
|
||||
dir.close();
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ pub fn Options(context: type) type {
|
|||
label: []const u8 = "Enter text",
|
||||
pos: Widget.Box = .{ .y = 0, .x = 0, .w = 12, .h = 1 },
|
||||
ctx: Context,
|
||||
padding: u8 = 1,
|
||||
icon: ?[]const u8 = null,
|
||||
|
||||
on_click: *const fn (ctx: context, button: *State(Context)) void = do_nothing,
|
||||
on_render: *const fn (ctx: context, button: *State(Context), theme: *const Widget.Theme) bool = on_render_default,
|
||||
|
|
@ -29,18 +31,21 @@ pub fn Options(context: type) type {
|
|||
self.plane.set_style(style_label);
|
||||
self.plane.fill(" ");
|
||||
self.plane.home();
|
||||
for (0..self.opts.padding) |_| _ = self.plane.putchar(" ");
|
||||
if (self.opts.icon) |icon|
|
||||
_ = self.plane.print("{s}", .{icon}) catch {};
|
||||
if (self.text.items.len > 0) {
|
||||
_ = self.plane.print(" {s} ", .{self.text.items}) catch {};
|
||||
_ = self.plane.print("{s} ", .{self.text.items}) catch {};
|
||||
} else {
|
||||
_ = self.plane.print(" {s} ", .{self.label.items}) catch {};
|
||||
_ = self.plane.print("{s} ", .{self.label.items}) catch {};
|
||||
}
|
||||
if (self.cursor) |cursor| {
|
||||
const pos: c_int = @intCast(cursor);
|
||||
if (tui.config().enable_terminal_cursor) {
|
||||
const y, const x = self.plane.rel_yx_to_abs(0, pos + 1);
|
||||
const y, const x = self.plane.rel_yx_to_abs(0, pos + self.opts.padding + self.icon_width);
|
||||
tui.rdr().cursor_enable(y, x, tui.get_cursor_shape()) catch {};
|
||||
} else {
|
||||
self.plane.cursor_move_yx(0, pos + 1) catch return false;
|
||||
self.plane.cursor_move_yx(0, pos + self.opts.padding + self.icon_width) catch return false;
|
||||
var cell = self.plane.cell_init();
|
||||
_ = self.plane.at_cursor_cell(&cell) catch return false;
|
||||
cell.set_style(theme.editor_cursor);
|
||||
|
|
@ -68,6 +73,7 @@ pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts:
|
|||
.opts = opts,
|
||||
.label = std.ArrayList(u8).init(allocator),
|
||||
.text = std.ArrayList(u8).init(allocator),
|
||||
.icon_width = @intCast(if (opts.icon) |icon| n.egc_chunk_width(icon, 0, 1) else 0),
|
||||
};
|
||||
try self.label.appendSlice(self.opts.label);
|
||||
self.opts.label = self.label.items;
|
||||
|
|
@ -83,6 +89,7 @@ pub fn State(ctx_type: type) type {
|
|||
label: std.ArrayList(u8),
|
||||
opts: Options(ctx_type),
|
||||
text: std.ArrayList(u8),
|
||||
icon_width: c_int,
|
||||
cursor: ?usize = 0,
|
||||
|
||||
const Self = @This();
|
||||
|
|
|
|||
|
|
@ -14,13 +14,15 @@ pub const scroll_lines = 3;
|
|||
pub fn Options(context: type) type {
|
||||
return struct {
|
||||
ctx: Context,
|
||||
style: Widget.Style.Type,
|
||||
|
||||
on_click: *const fn (ctx: context, button: *Button.State(*State(Context))) void = do_nothing,
|
||||
on_click4: *const fn (menu: **State(Context), button: *Button.State(*State(Context))) void = do_nothing_click,
|
||||
on_click5: *const fn (menu: **State(Context), button: *Button.State(*State(Context))) void = do_nothing_click,
|
||||
on_render: *const fn (ctx: context, button: *Button.State(*State(Context)), theme: *const Widget.Theme, selected: bool) bool = on_render_default,
|
||||
on_layout: *const fn (ctx: context, button: *Button.State(*State(Context))) Widget.Layout = on_layout_default,
|
||||
on_resize: *const fn (ctx: context, menu: *State(Context), box: Widget.Box) void = on_resize_default,
|
||||
prepare_resize: *const fn (ctx: context, menu: *State(Context), box: Widget.Box) Widget.Box = prepare_resize_default,
|
||||
after_resize: *const fn (ctx: context, menu: *State(Context), box: Widget.Box) void = after_resize_default,
|
||||
on_scroll: ?EventHandler = null,
|
||||
|
||||
pub const Context = context;
|
||||
|
|
@ -46,23 +48,26 @@ pub fn Options(context: type) type {
|
|||
return .{ .static = 1 };
|
||||
}
|
||||
|
||||
pub fn on_resize_default(_: context, state: *State(Context), box_: Widget.Box) void {
|
||||
pub fn prepare_resize_default(_: context, state: *State(Context), box_: Widget.Box) Widget.Box {
|
||||
var box = box_;
|
||||
box.h = if (box_.h == 0) state.menu.widgets.items.len else box_.h;
|
||||
state.container.resize(box);
|
||||
return box;
|
||||
}
|
||||
|
||||
pub fn after_resize_default(_: context, _: *State(Context), _: Widget.Box) void {}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts: Options(ctx_type)) !*State(ctx_type) {
|
||||
const self = try allocator.create(State(ctx_type));
|
||||
errdefer allocator.destroy(self);
|
||||
const container = try WidgetList.createH(allocator, parent, @typeName(@This()), .dynamic);
|
||||
const container = try WidgetList.createHStyled(allocator, parent, @typeName(@This()), .dynamic, opts.style);
|
||||
self.* = .{
|
||||
.allocator = allocator,
|
||||
.menu = try WidgetList.createV(allocator, container.plane, @typeName(@This()), .dynamic),
|
||||
.container = container,
|
||||
.container_widget = container.widget(),
|
||||
.frame_widget = null,
|
||||
.scrollbar = if (tui.config().show_scrollbars)
|
||||
if (opts.on_scroll) |on_scroll| (try scrollbar_v.create(allocator, parent, null, on_scroll)).dynamic_cast(scrollbar_v).? else null
|
||||
else
|
||||
|
|
@ -72,7 +77,8 @@ pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts:
|
|||
self.menu.ctx = self;
|
||||
self.menu.on_render = State(ctx_type).on_render_menu;
|
||||
container.ctx = self;
|
||||
container.on_resize = State(ctx_type).on_resize_container;
|
||||
container.prepare_resize = State(ctx_type).prepare_resize;
|
||||
container.after_resize = State(ctx_type).after_resize;
|
||||
try container.add(self.menu.widget());
|
||||
if (self.scrollbar) |sb| try container.add(sb.widget());
|
||||
return self;
|
||||
|
|
@ -84,6 +90,7 @@ pub fn State(ctx_type: type) type {
|
|||
menu: *WidgetList,
|
||||
container: *WidgetList,
|
||||
container_widget: Widget,
|
||||
frame_widget: ?Widget,
|
||||
scrollbar: ?*scrollbar_v,
|
||||
opts: options_type,
|
||||
selected: ?usize = null,
|
||||
|
|
@ -146,9 +153,14 @@ pub fn State(ctx_type: type) type {
|
|||
self.render_idx = 0;
|
||||
}
|
||||
|
||||
fn on_resize_container(ctx: ?*anyopaque, _: *WidgetList, box: Widget.Box) void {
|
||||
fn prepare_resize(ctx: ?*anyopaque, _: *WidgetList, box: Widget.Box) Widget.Box {
|
||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||
self.opts.on_resize(self.*.opts.ctx, self, box);
|
||||
return self.opts.prepare_resize(self.*.opts.ctx, self, box);
|
||||
}
|
||||
|
||||
fn after_resize(ctx: ?*anyopaque, _: *WidgetList, box: Widget.Box) void {
|
||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||
self.opts.after_resize(self.*.opts.ctx, self, box);
|
||||
}
|
||||
|
||||
pub fn on_layout(self: **Self, button: *Button.State(*Self)) Widget.Layout {
|
||||
|
|
@ -170,7 +182,7 @@ pub fn State(ctx_type: type) type {
|
|||
}
|
||||
|
||||
pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool {
|
||||
return self.menu.walk(walk_ctx, f, &self.container_widget);
|
||||
return self.menu.walk(walk_ctx, f, if (self.frame_widget) |*frame| frame else &self.container_widget);
|
||||
}
|
||||
|
||||
pub fn count(self: *Self) usize {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ pub const Error = (cbor.Error || cbor.JsonEncodeError || error{
|
|||
ThespianSpawnFailed,
|
||||
NoProject,
|
||||
ProjectManagerFailed,
|
||||
InvalidProjectDirectory,
|
||||
SendFailed,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ pub const Box = @import("Box.zig");
|
|||
pub const Theme = @import("theme");
|
||||
pub const themes = @import("themes").themes;
|
||||
pub const scopes = @import("themes").scopes;
|
||||
pub const Style = @import("WidgetStyle.zig");
|
||||
|
||||
ptr: *anyopaque,
|
||||
plane: *Plane,
|
||||
|
|
|
|||
|
|
@ -26,25 +26,35 @@ widgets: ArrayList(WidgetState),
|
|||
layout_: Layout,
|
||||
layout_empty: bool = true,
|
||||
direction: Direction,
|
||||
box: ?Widget.Box = null,
|
||||
deco_box: Widget.Box,
|
||||
ctx: ?*anyopaque = null,
|
||||
on_render: *const fn (ctx: ?*anyopaque, theme: *const Widget.Theme) void = on_render_default,
|
||||
after_render: *const fn (ctx: ?*anyopaque, theme: *const Widget.Theme) void = on_render_default,
|
||||
on_resize: *const fn (ctx: ?*anyopaque, self: *Self, pos_: Widget.Box) void = on_resize_default,
|
||||
prepare_resize: *const fn (ctx: ?*anyopaque, self: *Self, box: Widget.Box) Widget.Box = prepare_resize_default,
|
||||
after_resize: *const fn (ctx: ?*anyopaque, self: *Self, box: Widget.Box) void = after_resize_default,
|
||||
on_layout: *const fn (ctx: ?*anyopaque, self: *Self) Widget.Layout = on_layout_default,
|
||||
style: Widget.Style.Type,
|
||||
|
||||
pub fn createH(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout) error{OutOfMemory}!*Self {
|
||||
return createHStyled(allocator, parent, name, layout_, .none);
|
||||
}
|
||||
|
||||
pub fn createHStyled(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout, style: Widget.Style.Type) error{OutOfMemory}!*Self {
|
||||
const self = try allocator.create(Self);
|
||||
errdefer allocator.destroy(self);
|
||||
self.* = try init(allocator, parent, name, .horizontal, layout_, Box{});
|
||||
self.* = try init(allocator, parent, name, .horizontal, layout_, Box{}, style);
|
||||
self.plane.hide();
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn createV(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout) !*Self {
|
||||
return createVStyled(allocator, parent, name, layout_, .none);
|
||||
}
|
||||
|
||||
pub fn createVStyled(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout, style: Widget.Style.Type) !*Self {
|
||||
const self = try allocator.create(Self);
|
||||
errdefer allocator.destroy(self);
|
||||
self.* = try init(allocator, parent, name, .vertical, layout_, Box{});
|
||||
self.* = try init(allocator, parent, name, .vertical, layout_, Box{}, style);
|
||||
self.plane.hide();
|
||||
return self;
|
||||
}
|
||||
|
|
@ -57,15 +67,21 @@ pub fn createBox(allocator: Allocator, parent: Plane, name: [:0]const u8, dir: D
|
|||
return self;
|
||||
}
|
||||
|
||||
fn init(allocator: Allocator, parent: Plane, name: [:0]const u8, dir: Direction, layout_: Layout, box: Box) !Self {
|
||||
return .{
|
||||
.plane = try Plane.init(&box.opts(name), parent),
|
||||
fn init(allocator: Allocator, parent: Plane, name: [:0]const u8, dir: Direction, layout_: Layout, box_: Box, style: Widget.Style.Type) !Self {
|
||||
var self: Self = .{
|
||||
.plane = undefined,
|
||||
.parent = parent,
|
||||
.allocator = allocator,
|
||||
.widgets = ArrayList(WidgetState).init(allocator),
|
||||
.layout_ = layout_,
|
||||
.direction = dir,
|
||||
.style = style,
|
||||
.deco_box = undefined,
|
||||
};
|
||||
const padding = Widget.Style.from_type(self.style).padding;
|
||||
self.deco_box = self.from_client_box(box_, padding);
|
||||
self.plane = try Plane.init(&self.deco_box.opts(name), parent);
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn widget(self: *Self) Widget {
|
||||
|
|
@ -147,18 +163,27 @@ pub fn update(self: *Self) void {
|
|||
}
|
||||
|
||||
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
||||
const widget_style = Widget.Style.from_type(self.style);
|
||||
const padding = widget_style.padding;
|
||||
for (self.widgets.items) |*w| if (!w.layout.eql(w.widget.layout())) {
|
||||
self.refresh_layout();
|
||||
self.refresh_layout(padding);
|
||||
break;
|
||||
};
|
||||
|
||||
self.on_render(self.ctx, theme);
|
||||
self.render_decoration(theme, widget_style);
|
||||
|
||||
const client_box = self.to_client_box(self.deco_box, padding);
|
||||
|
||||
var more = false;
|
||||
for (self.widgets.items) |*w|
|
||||
for (self.widgets.items) |*w| {
|
||||
const widget_box = w.widget.box();
|
||||
if (client_box.y + client_box.h <= widget_box.y) break;
|
||||
if (client_box.x + client_box.w <= widget_box.x) break;
|
||||
if (w.widget.render(theme)) {
|
||||
more = true;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
self.after_render(self.ctx, theme);
|
||||
return more;
|
||||
|
|
@ -166,6 +191,41 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
|||
|
||||
fn on_render_default(_: ?*anyopaque, _: *const Widget.Theme) void {}
|
||||
|
||||
fn render_decoration(self: *Self, theme: *const Widget.Theme, widget_style: *const Widget.Style) void {
|
||||
const style = Widget.Style.theme_style_from_type(self.style, theme);
|
||||
const padding = widget_style.padding;
|
||||
const border = widget_style.border;
|
||||
const plane = &self.plane;
|
||||
const box = self.deco_box;
|
||||
|
||||
plane.set_style(style);
|
||||
plane.fill(" ");
|
||||
|
||||
if (padding.top > 0 and padding.left > 0) put_at_pos(plane, 0, 0, border.nw);
|
||||
if (padding.top > 0 and padding.right > 0) put_at_pos(plane, 0, box.w - 1, border.ne);
|
||||
if (padding.bottom > 0 and padding.left > 0 and box.h > 0) put_at_pos(plane, box.h - 1, 0, border.sw);
|
||||
if (padding.bottom > 0 and padding.right > 0 and box.h > 0) put_at_pos(plane, box.h - 1, box.w - 1, border.se);
|
||||
|
||||
{
|
||||
const start: usize = if (padding.left > 0) 1 else 0;
|
||||
const end: usize = if (padding.right > 0 and box.w > 0) box.w - 1 else box.w;
|
||||
if (padding.top > 0) for (start..end) |x| put_at_pos(plane, 0, x, border.n);
|
||||
if (padding.bottom > 0) for (start..end) |x| put_at_pos(plane, box.h - 1, x, border.s);
|
||||
}
|
||||
|
||||
{
|
||||
const start: usize = if (padding.top > 0) 1 else 0;
|
||||
const end: usize = if (padding.bottom > 0 and box.h > 0) box.h - 1 else box.h;
|
||||
if (padding.left > 0) for (start..end) |y| put_at_pos(plane, y, 0, border.w);
|
||||
if (padding.right > 0) for (start..end) |y| put_at_pos(plane, y, box.w - 1, border.e);
|
||||
}
|
||||
}
|
||||
|
||||
inline fn put_at_pos(plane: *Plane, y: usize, x: usize, egc: []const u8) void {
|
||||
plane.cursor_move_yx(@intCast(y), @intCast(x)) catch return;
|
||||
plane.putchar(egc);
|
||||
}
|
||||
|
||||
pub fn receive(self: *Self, from_: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
||||
if (try m.match(.{ "H", tp.more }))
|
||||
return false;
|
||||
|
|
@ -176,6 +236,13 @@ pub fn receive(self: *Self, from_: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
fn get_size_a_const(self: *Self, pos: *const Widget.Box) usize {
|
||||
return switch (self.direction) {
|
||||
.vertical => pos.h,
|
||||
.horizontal => pos.w,
|
||||
};
|
||||
}
|
||||
|
||||
fn get_size_a(self: *Self, pos: *Widget.Box) *usize {
|
||||
return switch (self.direction) {
|
||||
.vertical => &pos.h,
|
||||
|
|
@ -183,6 +250,13 @@ fn get_size_a(self: *Self, pos: *Widget.Box) *usize {
|
|||
};
|
||||
}
|
||||
|
||||
fn get_size_b_const(self: *Self, pos: *const Widget.Box) usize {
|
||||
return switch (self.direction) {
|
||||
.vertical => pos.w,
|
||||
.horizontal => pos.h,
|
||||
};
|
||||
}
|
||||
|
||||
fn get_size_b(self: *Self, pos: *Widget.Box) *usize {
|
||||
return switch (self.direction) {
|
||||
.vertical => &pos.w,
|
||||
|
|
@ -190,6 +264,13 @@ fn get_size_b(self: *Self, pos: *Widget.Box) *usize {
|
|||
};
|
||||
}
|
||||
|
||||
fn get_loc_a_const(self: *Self, pos: *const Widget.Box) usize {
|
||||
return switch (self.direction) {
|
||||
.vertical => pos.y,
|
||||
.horizontal => pos.x,
|
||||
};
|
||||
}
|
||||
|
||||
fn get_loc_a(self: *Self, pos: *Widget.Box) *usize {
|
||||
return switch (self.direction) {
|
||||
.vertical => &pos.y,
|
||||
|
|
@ -197,6 +278,13 @@ fn get_loc_a(self: *Self, pos: *Widget.Box) *usize {
|
|||
};
|
||||
}
|
||||
|
||||
fn get_loc_b_const(self: *Self, pos: *const Widget.Box) usize {
|
||||
return switch (self.direction) {
|
||||
.vertical => pos.x,
|
||||
.horizontal => pos.y,
|
||||
};
|
||||
}
|
||||
|
||||
fn get_loc_b(self: *Self, pos: *Widget.Box) *usize {
|
||||
return switch (self.direction) {
|
||||
.vertical => &pos.x,
|
||||
|
|
@ -204,28 +292,62 @@ fn get_loc_b(self: *Self, pos: *Widget.Box) *usize {
|
|||
};
|
||||
}
|
||||
|
||||
fn refresh_layout(self: *Self) void {
|
||||
return if (self.box) |box| self.handle_resize(box);
|
||||
fn refresh_layout(self: *Self, padding: Widget.Style.Margin) void {
|
||||
return self.handle_resize(self.to_client_box(self.deco_box, padding));
|
||||
}
|
||||
|
||||
pub fn handle_resize(self: *Self, pos: Widget.Box) void {
|
||||
self.on_resize(self.ctx, self, pos);
|
||||
pub fn handle_resize(self: *Self, box: Widget.Box) void {
|
||||
const padding = Widget.Style.from_type(self.style).padding;
|
||||
const client_box_ = self.prepare_resize(self.ctx, self, self.to_client_box(box, padding));
|
||||
self.deco_box = self.from_client_box(client_box_, padding);
|
||||
self.do_resize(padding);
|
||||
self.after_resize(self.ctx, self, self.to_client_box(self.deco_box, padding));
|
||||
}
|
||||
|
||||
fn on_resize_default(_: ?*anyopaque, self: *Self, pos: Widget.Box) void {
|
||||
self.resize(pos);
|
||||
pub inline fn to_client_box(_: *const Self, box_: Widget.Box, padding: Widget.Style.Margin) Widget.Box {
|
||||
const total_y_padding = padding.top + padding.bottom;
|
||||
const total_x_padding = padding.left + padding.right;
|
||||
var box = box_;
|
||||
box.y += padding.top;
|
||||
box.h -= if (box.h > total_y_padding) total_y_padding else box.h;
|
||||
box.x += padding.left;
|
||||
box.w -= if (box.w > total_x_padding) total_x_padding else box.w;
|
||||
return box;
|
||||
}
|
||||
|
||||
inline fn from_client_box(_: *const Self, box_: Widget.Box, padding: Widget.Style.Margin) Widget.Box {
|
||||
const total_y_padding = padding.top + padding.bottom;
|
||||
const total_x_padding = padding.left + padding.right;
|
||||
const y = if (box_.y < padding.top) padding.top else box_.y;
|
||||
const x = if (box_.x < padding.left) padding.left else box_.x;
|
||||
var box = box_;
|
||||
box.y = y - padding.top;
|
||||
box.h += total_y_padding;
|
||||
box.x = x - padding.left;
|
||||
box.w += total_x_padding;
|
||||
return box;
|
||||
}
|
||||
|
||||
fn prepare_resize_default(_: ?*anyopaque, _: *Self, box: Widget.Box) Widget.Box {
|
||||
return box;
|
||||
}
|
||||
|
||||
fn after_resize_default(_: ?*anyopaque, _: *Self, _: Widget.Box) void {}
|
||||
|
||||
fn on_layout_default(_: ?*anyopaque, self: *Self) Widget.Layout {
|
||||
return self.layout_;
|
||||
}
|
||||
|
||||
pub fn resize(self: *Self, pos_: Widget.Box) void {
|
||||
self.box = pos_;
|
||||
var pos = pos_;
|
||||
self.plane.move_yx(@intCast(pos.y), @intCast(pos.x)) catch return;
|
||||
self.plane.resize_simple(@intCast(pos.h), @intCast(pos.w)) catch return;
|
||||
const total = self.get_size_a(&pos).*;
|
||||
pub fn resize(self: *Self, box: Widget.Box) void {
|
||||
return self.handle_resize(box);
|
||||
}
|
||||
|
||||
fn do_resize(self: *Self, padding: Widget.Style.Margin) void {
|
||||
const client_box = self.to_client_box(self.deco_box, padding);
|
||||
const deco_box = self.deco_box;
|
||||
self.plane.move_yx(@intCast(deco_box.y), @intCast(deco_box.x)) catch return;
|
||||
self.plane.resize_simple(@intCast(deco_box.h), @intCast(deco_box.w)) catch return;
|
||||
const total = self.get_size_a_const(&client_box);
|
||||
var avail = total;
|
||||
var statics: usize = 0;
|
||||
var dynamics: usize = 0;
|
||||
|
|
@ -245,7 +367,7 @@ pub fn resize(self: *Self, pos_: Widget.Box) void {
|
|||
|
||||
const dyn_size = avail / if (dynamics > 0) dynamics else 1;
|
||||
const rounded: usize = if (dyn_size * dynamics < avail) avail - dyn_size * dynamics else 0;
|
||||
var cur_loc: usize = self.get_loc_a(&pos).*;
|
||||
var cur_loc: usize = self.get_loc_a_const(&client_box);
|
||||
var first = true;
|
||||
|
||||
for (self.widgets.items) |*w| {
|
||||
|
|
@ -261,8 +383,8 @@ pub fn resize(self: *Self, pos_: Widget.Box) void {
|
|||
self.get_loc_a(&w_pos).* = cur_loc;
|
||||
cur_loc += size;
|
||||
|
||||
self.get_size_b(&w_pos).* = self.get_size_b(&pos).*;
|
||||
self.get_loc_b(&w_pos).* = self.get_loc_b(&pos).*;
|
||||
self.get_size_b(&w_pos).* = self.get_size_b_const(&client_box);
|
||||
self.get_loc_b(&w_pos).* = self.get_loc_b_const(&client_box);
|
||||
w.widget.resize(w_pos);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
220
src/tui/WidgetStyle.zig
Normal file
220
src/tui/WidgetStyle.zig
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
padding: Margin = Margin.@"0",
|
||||
border: Border = Border.blank,
|
||||
|
||||
pub const Type = enum {
|
||||
none,
|
||||
palette,
|
||||
panel,
|
||||
home,
|
||||
};
|
||||
|
||||
pub const Padding = struct {
|
||||
pub const Unit = u16;
|
||||
};
|
||||
|
||||
pub const Margin = struct {
|
||||
const Unit = Padding.Unit;
|
||||
|
||||
top: Unit,
|
||||
bottom: Unit,
|
||||
left: Unit,
|
||||
right: Unit,
|
||||
|
||||
const @"0": Margin = .{ .top = 0, .bottom = 0, .left = 0, .right = 0 };
|
||||
const @"1": Margin = .{ .top = 1, .bottom = 1, .left = 1, .right = 1 };
|
||||
const @"2": Margin = .{ .top = 2, .bottom = 2, .left = 2, .right = 2 };
|
||||
const @"3": Margin = .{ .top = 3, .bottom = 3, .left = 3, .right = 3 };
|
||||
const @"1/2": Margin = .{ .top = 1, .bottom = 1, .left = 2, .right = 2 };
|
||||
const @"2/1": Margin = .{ .top = 2, .bottom = 2, .left = 1, .right = 1 };
|
||||
const @"2/3": Margin = .{ .top = 2, .bottom = 2, .left = 3, .right = 3 };
|
||||
const @"2/4": Margin = .{ .top = 2, .bottom = 2, .left = 4, .right = 4 };
|
||||
|
||||
const @"top/bottom/1": Margin = .{ .top = 1, .bottom = 1, .left = 0, .right = 0 };
|
||||
const @"top/bottom/2": Margin = .{ .top = 2, .bottom = 2, .left = 0, .right = 0 };
|
||||
const @"left/right/1": Margin = .{ .top = 0, .bottom = 0, .left = 1, .right = 1 };
|
||||
const @"left/right/2": Margin = .{ .top = 0, .bottom = 0, .left = 2, .right = 2 };
|
||||
};
|
||||
|
||||
pub const Border = struct {
|
||||
nw: []const u8,
|
||||
n: []const u8,
|
||||
ne: []const u8,
|
||||
e: []const u8,
|
||||
se: []const u8,
|
||||
s: []const u8,
|
||||
sw: []const u8,
|
||||
w: []const u8,
|
||||
|
||||
const blank: Border = .{ .nw = " ", .n = " ", .ne = " ", .e = " ", .se = " ", .s = " ", .sw = " ", .w = " " };
|
||||
const box: Border = .{ .nw = "┌", .n = "─", .ne = "┐", .e = "│", .se = "┘", .s = "─", .sw = "└", .w = "│" };
|
||||
const @"rounded box": Border = .{ .nw = "╭", .n = "─", .ne = "╮", .e = "│", .se = "╯", .s = "─", .sw = "╰", .w = "│" };
|
||||
const @"double box": Border = .{ .nw = "╔", .n = "═", .ne = "╗", .e = "║", .se = "╝", .s = "═", .sw = "╚", .w = "║" };
|
||||
const @"single/double box (top/bottom)": Border = .{ .nw = "╓", .n = "─", .ne = "╖", .e = "║", .se = "╜", .s = "─", .sw = "╙", .w = "║" };
|
||||
const @"single/double box (left/right)": Border = .{ .nw = "╒", .n = "═", .ne = "╕", .e = "│", .se = "╛", .s = "═", .sw = "╘", .w = "│" };
|
||||
const @"dotted box (braille)": Border = .{ .nw = "⡏", .n = "⠉", .ne = "⢹", .e = "⢸", .se = "⣸", .s = "⣀", .sw = "⣇", .w = "⡇" };
|
||||
const @"thick box (half)": Border = .{ .nw = "▛", .n = "▀", .ne = "▜", .e = "▐", .se = "▟", .s = "▄", .sw = "▙", .w = "▌" };
|
||||
const @"thick box (sextant)": Border = .{ .nw = "🬕", .n = "🬂", .ne = "🬨", .e = "▐", .se = "🬷", .s = "🬭", .sw = "🬲", .w = "▌" };
|
||||
const @"thick box (octant)": Border = .{ .nw = "", .n = "🮂", .ne = "", .e = "▐", .se = "", .s = "▂", .sw = "", .w = "▌" };
|
||||
const @"extra thick box": Border = .{ .nw = "█", .n = "▀", .ne = "█", .e = "█", .se = "█", .s = "▄", .sw = "█", .w = "█" };
|
||||
const @"round thick box": Border = .{ .nw = "█", .n = "▀", .ne = "█", .e = "█", .se = "█", .s = "▄", .sw = "█", .w = "█" };
|
||||
};
|
||||
|
||||
const compact: @This() = .{};
|
||||
|
||||
const spacious: @This() = .{
|
||||
.padding = Margin.@"1",
|
||||
.border = Border.blank,
|
||||
};
|
||||
|
||||
const boxed: @This() = .{
|
||||
.padding = Margin.@"1",
|
||||
.border = Border.box,
|
||||
};
|
||||
|
||||
const rounded_boxed: @This() = .{
|
||||
.padding = Margin.@"1",
|
||||
.border = Border.@"rounded box",
|
||||
};
|
||||
|
||||
const double_boxed: @This() = .{
|
||||
.padding = Margin.@"1",
|
||||
.border = Border.@"double box",
|
||||
};
|
||||
|
||||
const single_double_top_bottom_boxed: @This() = .{
|
||||
.padding = Margin.@"1",
|
||||
.border = Border.@"single/double box (top/bottom)",
|
||||
};
|
||||
|
||||
const single_double_left_right_boxed: @This() = .{
|
||||
.padding = Margin.@"1",
|
||||
.border = Border.@"single/double box (left/right)",
|
||||
};
|
||||
|
||||
const dotted_boxed: @This() = .{
|
||||
.padding = Margin.@"1",
|
||||
.border = Border.@"dotted box (braille)",
|
||||
};
|
||||
|
||||
const thick_boxed: @This() = .{
|
||||
.padding = Margin.@"1/2",
|
||||
.border = Border.@"thick box (octant)",
|
||||
};
|
||||
|
||||
const extra_thick_boxed: @This() = .{
|
||||
.padding = Margin.@"1/2",
|
||||
.border = Border.@"extra thick box",
|
||||
};
|
||||
|
||||
const bars_top_bottom: @This() = .{
|
||||
.padding = Margin.@"top/bottom/1",
|
||||
.border = Border.@"thick box (octant)",
|
||||
};
|
||||
|
||||
const bars_left_right: @This() = .{
|
||||
.padding = Margin.@"left/right/1",
|
||||
.border = Border.@"thick box (octant)",
|
||||
};
|
||||
|
||||
pub fn from_type(style_type: Type) *const @This() {
|
||||
return switch (style_type) {
|
||||
.none => none_style,
|
||||
.palette => palette_style,
|
||||
.panel => panel_style,
|
||||
.home => home_style,
|
||||
};
|
||||
}
|
||||
|
||||
pub const Styles = enum {
|
||||
compact,
|
||||
spacious,
|
||||
boxed,
|
||||
double_boxed,
|
||||
rounded_boxed,
|
||||
single_double_top_bottom_boxed,
|
||||
single_double_left_right_boxed,
|
||||
dotted_boxed,
|
||||
thick_boxed,
|
||||
extra_thick_boxed,
|
||||
bars_top_bottom,
|
||||
bars_left_right,
|
||||
};
|
||||
|
||||
pub fn from_tag(tag: Styles) *const @This() {
|
||||
return switch (tag) {
|
||||
.compact => &compact,
|
||||
.spacious => &spacious,
|
||||
.boxed => &boxed,
|
||||
.double_boxed => &double_boxed,
|
||||
.rounded_boxed => &rounded_boxed,
|
||||
.single_double_top_bottom_boxed => &single_double_top_bottom_boxed,
|
||||
.single_double_left_right_boxed => &single_double_left_right_boxed,
|
||||
.dotted_boxed => &dotted_boxed,
|
||||
.thick_boxed => &thick_boxed,
|
||||
.extra_thick_boxed => &extra_thick_boxed,
|
||||
.bars_top_bottom => &bars_top_bottom,
|
||||
.bars_left_right => &bars_left_right,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn next_tag(tag: Styles) Styles {
|
||||
const new_value = @intFromEnum(tag) + 1;
|
||||
return if (new_value > @intFromEnum(Styles.bars_left_right)) .compact else @enumFromInt(new_value);
|
||||
}
|
||||
|
||||
pub fn set_type_style(style_type: Type, tag: Styles) void {
|
||||
const ref = type_style(style_type);
|
||||
ref.* = from_tag(tag);
|
||||
}
|
||||
|
||||
pub fn set_next_style(style_type: Type) void {
|
||||
const tag_ref = type_tag(style_type);
|
||||
const new_tag = next_tag(tag_ref.*);
|
||||
const style_ref = type_style(style_type);
|
||||
tag_ref.* = new_tag;
|
||||
style_ref.* = from_tag(new_tag);
|
||||
}
|
||||
|
||||
var none_style: *const @This() = from_tag(none_tag_default);
|
||||
var palette_style: *const @This() = from_tag(palette_tag_default);
|
||||
var panel_style: *const @This() = from_tag(panel_tag_default);
|
||||
var home_style: *const @This() = from_tag(home_tag_default);
|
||||
|
||||
fn type_style(style_type: Type) **const @This() {
|
||||
return switch (style_type) {
|
||||
.none => &none_style,
|
||||
.palette => &palette_style,
|
||||
.panel => &panel_style,
|
||||
.home => &home_style,
|
||||
};
|
||||
}
|
||||
|
||||
const none_tag_default: Styles = .compact;
|
||||
const palette_tag_default: Styles = .compact;
|
||||
const panel_tag_default: Styles = .compact;
|
||||
const home_tag_default: Styles = .compact;
|
||||
|
||||
var none_tag: Styles = none_tag_default;
|
||||
var palette_tag: Styles = palette_tag_default;
|
||||
var panel_tag: Styles = panel_tag_default;
|
||||
var home_tag: Styles = home_tag_default;
|
||||
|
||||
fn type_tag(style_type: Type) *Styles {
|
||||
return switch (style_type) {
|
||||
.none => &none_tag,
|
||||
.palette => &palette_tag,
|
||||
.panel => &panel_tag,
|
||||
.home => &home_tag,
|
||||
};
|
||||
}
|
||||
|
||||
const Widget = @import("Widget.zig");
|
||||
|
||||
pub fn theme_style_from_type(style_type: Type, theme: *const Widget.Theme) Widget.Theme.Style {
|
||||
return switch (style_type) {
|
||||
.none => theme.editor,
|
||||
.palette => .{ .fg = theme.editor_widget_border.fg, .bg = theme.editor_widget.bg },
|
||||
.panel => .{ .fg = theme.editor_widget_border.fg, .bg = theme.editor.bg },
|
||||
.home => .{ .fg = theme.editor_widget_border.fg, .bg = theme.editor.bg },
|
||||
};
|
||||
}
|
||||
|
|
@ -33,8 +33,10 @@ view_rows: usize = 0,
|
|||
view_cols: usize = 0,
|
||||
entries: std.ArrayList(Entry) = undefined,
|
||||
selected: ?usize = null,
|
||||
box: Widget.Box = .{},
|
||||
|
||||
const path_column_ratio = 4;
|
||||
const widget_style_type: Widget.Style.Type = .panel;
|
||||
|
||||
const Entry = struct {
|
||||
path: []const u8,
|
||||
|
|
@ -56,6 +58,7 @@ pub fn create(allocator: Allocator, parent: Plane) !Widget {
|
|||
.entries = std.ArrayList(Entry).init(allocator),
|
||||
.menu = try Menu.create(*Self, allocator, tui.plane(), .{
|
||||
.ctx = self,
|
||||
.style = widget_style_type,
|
||||
.on_render = handle_render_menu,
|
||||
.on_scroll = EventHandler.bind(self, Self.handle_scroll),
|
||||
.on_click4 = mouse_click_button4,
|
||||
|
|
@ -84,11 +87,14 @@ fn scrollbar_style(sb: *scrollbar_v, theme: *const Widget.Theme) Widget.Theme.St
|
|||
}
|
||||
|
||||
pub fn handle_resize(self: *Self, pos: Widget.Box) void {
|
||||
const padding = Widget.Style.from_type(widget_style_type).padding;
|
||||
self.plane.move_yx(@intCast(pos.y), @intCast(pos.x)) catch return;
|
||||
self.plane.resize_simple(@intCast(pos.h), @intCast(pos.w)) catch return;
|
||||
self.menu.container_widget.resize(pos);
|
||||
self.view_rows = pos.h;
|
||||
self.view_cols = pos.w;
|
||||
self.box = pos;
|
||||
self.menu.container.resize(self.box);
|
||||
const client_box = self.menu.container.to_client_box(pos, padding);
|
||||
self.view_rows = client_box.h;
|
||||
self.view_cols = client_box.w;
|
||||
self.update_scrollbar();
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +113,7 @@ pub fn add_item(self: *Self, entry_: Entry) !void {
|
|||
const writer = label.writer();
|
||||
cbor.writeValue(writer, idx) catch return;
|
||||
self.menu.add_item_with_handler(label.items, handle_menu_action) catch return;
|
||||
self.menu.container_widget.resize(Widget.Box.from(self.plane));
|
||||
self.menu.resize(self.box);
|
||||
self.update_scrollbar();
|
||||
}
|
||||
|
||||
|
|
@ -160,8 +166,8 @@ fn handle_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), th
|
|||
button.plane.home();
|
||||
}
|
||||
const entry = &self.entries.items[idx];
|
||||
const pointer = if (selected) "⏵" else " ";
|
||||
_ = button.plane.print("{s} ", .{pointer}) catch {};
|
||||
button.plane.set_style(style_label);
|
||||
tui.render_pointer(&button.plane, selected);
|
||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
var removed_prefix: usize = 0;
|
||||
const max_len = self.view_cols / path_column_ratio;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const style = struct {
|
|||
\\open_recent_project
|
||||
\\find_in_files
|
||||
\\open_command_palette
|
||||
\\select_task
|
||||
\\run_task
|
||||
\\add_task
|
||||
\\open_config
|
||||
\\open_gui_config
|
||||
|
|
@ -48,7 +48,7 @@ const style = struct {
|
|||
\\open_recent_project
|
||||
\\find_in_files
|
||||
\\open_command_palette
|
||||
\\select_task
|
||||
\\run_task
|
||||
\\add_task
|
||||
\\open_config
|
||||
\\open_keybind_config
|
||||
|
|
@ -70,6 +70,8 @@ fire: ?Fire = null,
|
|||
commands: Commands = undefined,
|
||||
menu: *Menu.State(*Self),
|
||||
menu_w: usize = 0,
|
||||
menu_label_max: usize = 0,
|
||||
menu_count: usize = 0,
|
||||
menu_len: usize = 0,
|
||||
max_desc_len: usize = 0,
|
||||
input_namespace: []const u8,
|
||||
|
|
@ -79,6 +81,8 @@ home_style_bufs: [][]const u8,
|
|||
|
||||
const Self = @This();
|
||||
|
||||
const widget_style_type: Widget.Style.Type = .home;
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
|
||||
const logger = log.logger("home");
|
||||
const self = try allocator.create(Self);
|
||||
|
|
@ -95,7 +99,11 @@ pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
|
|||
.allocator = allocator,
|
||||
.parent = parent.plane.*,
|
||||
.plane = n,
|
||||
.menu = try Menu.create(*Self, allocator, w.plane.*, .{ .ctx = self, .on_render = menu_on_render }),
|
||||
.menu = try Menu.create(*Self, allocator, w.plane.*, .{
|
||||
.ctx = self,
|
||||
.style = widget_style_type,
|
||||
.on_render = menu_on_render,
|
||||
}),
|
||||
.input_namespace = keybind.get_namespace(),
|
||||
.home_style = home_style,
|
||||
.home_style_bufs = home_style_bufs,
|
||||
|
|
@ -103,7 +111,6 @@ pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
|
|||
try self.commands.init(self);
|
||||
var it = std.mem.splitAny(u8, self.home_style.menu_commands, "\n ");
|
||||
while (it.next()) |command_name| {
|
||||
self.menu_len += 1;
|
||||
const id = command.get_id(command_name) orelse {
|
||||
logger.print("{s} is not defined", .{command_name});
|
||||
continue;
|
||||
|
|
@ -112,11 +119,14 @@ pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
|
|||
logger.print("{s} has no description", .{command_name});
|
||||
continue;
|
||||
};
|
||||
self.menu_count += 1;
|
||||
var hints = std.mem.splitScalar(u8, keybind_mode.keybind_hints.get(command_name) orelse "", ',');
|
||||
const hint = hints.first();
|
||||
self.max_desc_len = @max(self.max_desc_len, description.len + hint.len + 5);
|
||||
try self.add_menu_command(command_name, description, hint, self.menu);
|
||||
}
|
||||
const padding = Widget.Style.from_type(widget_style_type).padding;
|
||||
self.menu_len = self.menu_count + padding.top + padding.bottom;
|
||||
self.position_menu(15, 9);
|
||||
return w;
|
||||
}
|
||||
|
|
@ -145,7 +155,9 @@ fn add_menu_command(self: *Self, command_name: []const u8, description: []const
|
|||
_ = try writer.write(leader);
|
||||
try writer.print(" :{s}", .{hint});
|
||||
const label = fis.getWritten();
|
||||
self.menu_w = @max(self.menu_w, label.len + 1);
|
||||
const padding = Widget.Style.from_type(widget_style_type).padding;
|
||||
self.menu_label_max = @max(self.menu_label_max, label.len);
|
||||
self.menu_w = self.menu_label_max + 2 + padding.left + padding.right;
|
||||
}
|
||||
|
||||
var value = std.ArrayList(u8).init(self.allocator);
|
||||
|
|
@ -228,8 +240,8 @@ fn menu_on_render(self: *Self, button: *Button.State(*Menu.State(*Self)), theme:
|
|||
} else {
|
||||
button.plane.set_style_bg_transparent(style_text);
|
||||
}
|
||||
const pointer = if (selected) "⏵" else " ";
|
||||
_ = button.plane.print("{s}{s}", .{ pointer, description }) catch {};
|
||||
tui.render_pointer(&button.plane, selected);
|
||||
_ = button.plane.print("{s}", .{description}) catch {};
|
||||
if (button.active or button.hover or selected) {
|
||||
button.plane.set_style(style_leader);
|
||||
} else {
|
||||
|
|
@ -323,13 +335,13 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
|||
_ = self.plane.print("{s}", .{debug_warning_text}) catch return false;
|
||||
}
|
||||
|
||||
const more = self.menu.render(theme);
|
||||
const more = self.menu.container.render(theme);
|
||||
return more or self.fire != null;
|
||||
}
|
||||
|
||||
fn position_menu(self: *Self, y: usize, x: usize) void {
|
||||
const box = Widget.Box.from(self.plane);
|
||||
self.menu.resize(.{ .y = box.y + y, .x = box.x + x, .w = self.menu_w });
|
||||
self.menu.resize(.{ .y = box.y + y, .x = box.x + x, .w = self.menu_w, .h = self.menu_len });
|
||||
}
|
||||
|
||||
fn center(self: *Self, non_centered: usize, w: usize) usize {
|
||||
|
|
@ -388,6 +400,15 @@ const cmds = struct {
|
|||
}
|
||||
pub const home_menu_activate_meta: Meta = .{};
|
||||
|
||||
pub fn home_next_widget_style(self: *Self, _: Ctx) Result {
|
||||
Widget.Style.set_next_style(widget_style_type);
|
||||
const padding = Widget.Style.from_type(widget_style_type).padding;
|
||||
self.menu_len = self.menu_count + padding.top + padding.bottom;
|
||||
self.menu_w = self.menu_label_max + 2 + padding.left + padding.right;
|
||||
tui.need_render();
|
||||
}
|
||||
pub const home_next_widget_style_meta: Meta = .{};
|
||||
|
||||
pub fn home_sheeran(self: *Self, _: Ctx) Result {
|
||||
self.fire = if (self.fire) |*fire| ret: {
|
||||
fire.deinit();
|
||||
|
|
|
|||
|
|
@ -322,6 +322,7 @@ const cmds = struct {
|
|||
if (!try ctx.args.match(.{tp.extract(&project_dir)}))
|
||||
return;
|
||||
try self.check_all_not_dirty();
|
||||
try project_manager.open(project_dir);
|
||||
for (self.editors.items) |editor| {
|
||||
editor.clear_diagnostics();
|
||||
try editor.close_file(.{});
|
||||
|
|
@ -332,7 +333,6 @@ const cmds = struct {
|
|||
try self.toggle_panel_view(filelist_view, false);
|
||||
self.buffer_manager.deinit();
|
||||
self.buffer_manager = Buffer.Manager.init(self.allocator);
|
||||
try project_manager.open(project_dir);
|
||||
const project = tp.env.get().str("project");
|
||||
tui.rdr().set_terminal_working_directory(project);
|
||||
if (self.top_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" });
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ const Widget = @import("../../Widget.zig");
|
|||
pub const label = "Switch buffers";
|
||||
pub const name = " buffer";
|
||||
pub const description = "buffer";
|
||||
const dirty_indicator = "";
|
||||
const hidden_indicator = "-";
|
||||
pub const icon = " ";
|
||||
|
||||
pub const Entry = struct {
|
||||
label: []const u8,
|
||||
|
|
@ -27,12 +26,7 @@ pub fn load_entries(palette: *Type) !usize {
|
|||
const buffers = try buffer_manager.list_most_recently_used(palette.allocator);
|
||||
defer palette.allocator.free(buffers);
|
||||
for (buffers) |buffer| {
|
||||
const indicator = if (buffer.is_dirty())
|
||||
dirty_indicator
|
||||
else if (buffer.is_hidden())
|
||||
hidden_indicator
|
||||
else
|
||||
"";
|
||||
const indicator = tui.get_buffer_state_indicator(buffer);
|
||||
(try palette.entries.addOne()).* = .{
|
||||
.label = buffer.get_file_path(),
|
||||
.icon = buffer.file_type_icon orelse "",
|
||||
|
|
@ -61,50 +55,7 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v
|
|||
}
|
||||
|
||||
pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
|
||||
const style_base = theme.editor_widget;
|
||||
const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget;
|
||||
const style_hint = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_label;
|
||||
button.plane.set_base_style(style_base);
|
||||
button.plane.erase();
|
||||
button.plane.home();
|
||||
button.plane.set_style(style_label);
|
||||
if (button.active or button.hover or selected) {
|
||||
button.plane.fill(" ");
|
||||
button.plane.home();
|
||||
}
|
||||
|
||||
button.plane.set_style(style_hint);
|
||||
const pointer = if (selected) "⏵" else " ";
|
||||
_ = button.plane.print("{s}", .{pointer}) catch {};
|
||||
|
||||
var iter = button.opts.label;
|
||||
var file_path_: []const u8 = undefined;
|
||||
var icon: []const u8 = undefined;
|
||||
var color: u24 = undefined;
|
||||
if (!(cbor.matchString(&iter, &file_path_) catch false)) @panic("invalid buffer file path");
|
||||
if (!(cbor.matchString(&iter, &icon) catch false)) @panic("invalid buffer file type icon");
|
||||
if (!(cbor.matchInt(u24, &iter, &color) catch false)) @panic("invalid buffer file type color");
|
||||
if (tui.config().show_fileicons) {
|
||||
tui.render_file_icon(&button.plane, icon, color);
|
||||
_ = button.plane.print(" ", .{}) catch {};
|
||||
}
|
||||
button.plane.set_style(style_label);
|
||||
_ = button.plane.print(" {s} ", .{file_path_}) catch {};
|
||||
|
||||
var indicator: []const u8 = undefined;
|
||||
if (!(cbor.matchString(&iter, &indicator) catch false))
|
||||
indicator = "";
|
||||
button.plane.set_style(style_hint);
|
||||
_ = button.plane.print_aligned_right(0, "{s} ", .{indicator}) catch {};
|
||||
|
||||
var index: usize = 0;
|
||||
var len = cbor.decodeArrayHeader(&iter) catch return false;
|
||||
while (len > 0) : (len -= 1) {
|
||||
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
|
||||
tui.render_match_cell(&button.plane, 0, index + 4, theme) catch break;
|
||||
} else break;
|
||||
}
|
||||
return false;
|
||||
return tui.render_file_item_cbor(&button.plane, button.opts.label, button.active, selected, button.hover, theme);
|
||||
}
|
||||
|
||||
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_
|
|||
pub const label = label_;
|
||||
pub const name = " file type";
|
||||
pub const description = "file type";
|
||||
pub const icon = " ";
|
||||
|
||||
pub const Entry = struct {
|
||||
label: []const u8,
|
||||
|
|
@ -84,20 +85,18 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_
|
|||
}
|
||||
|
||||
button.plane.set_style(style_hint);
|
||||
const pointer = if (selected) "⏵" else " ";
|
||||
_ = button.plane.print("{s}", .{pointer}) catch {};
|
||||
tui.render_pointer(&button.plane, selected);
|
||||
|
||||
var iter = button.opts.label;
|
||||
var description_: []const u8 = undefined;
|
||||
var icon: []const u8 = undefined;
|
||||
var icon_: []const u8 = undefined;
|
||||
var color: u24 = undefined;
|
||||
if (!(cbor.matchString(&iter, &description_) catch false)) @panic("invalid file_type description");
|
||||
if (!(cbor.matchString(&iter, &icon) catch false)) @panic("invalid file_type icon");
|
||||
if (!(cbor.matchString(&iter, &icon_) catch false)) @panic("invalid file_type icon");
|
||||
if (!(cbor.matchInt(u24, &iter, &color) catch false)) @panic("invalid file_type color");
|
||||
if (tui.config().show_fileicons) {
|
||||
tui.render_file_icon(&button.plane, icon, color);
|
||||
_ = button.plane.print(" ", .{}) catch {};
|
||||
}
|
||||
|
||||
tui.render_file_icon(&button.plane, icon_, color);
|
||||
|
||||
button.plane.set_style(style_label);
|
||||
_ = button.plane.print("{s} ", .{description_}) catch {};
|
||||
|
||||
|
|
@ -119,12 +118,12 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_
|
|||
|
||||
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
|
||||
var description_: []const u8 = undefined;
|
||||
var icon: []const u8 = undefined;
|
||||
var icon_: []const u8 = undefined;
|
||||
var color: u24 = undefined;
|
||||
var name_: []const u8 = undefined;
|
||||
var iter = button.opts.label;
|
||||
if (!(cbor.matchString(&iter, &description_) catch false)) return;
|
||||
if (!(cbor.matchString(&iter, &icon) catch false)) return;
|
||||
if (!(cbor.matchString(&iter, &icon_) catch false)) return;
|
||||
if (!(cbor.matchInt(u24, &iter, &color) catch false)) return;
|
||||
if (!(cbor.matchString(&iter, &name_) catch false)) return;
|
||||
if (!allow_previous) if (previous_file_type) |prev| if (std.mem.eql(u8, prev, name_))
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ const ModalBackground = @import("../../ModalBackground.zig");
|
|||
|
||||
const Self = @This();
|
||||
const max_recent_files: usize = 25;
|
||||
const widget_style_type: Widget.Style.Type = .palette;
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
f: usize = 0,
|
||||
|
|
@ -33,7 +34,7 @@ logger: log.Logger,
|
|||
query_pending: bool = false,
|
||||
need_reset: bool = false,
|
||||
need_select_first: bool = true,
|
||||
longest: usize = 0,
|
||||
longest: usize,
|
||||
commands: Commands = undefined,
|
||||
buffer_manager: ?*BufferManager,
|
||||
|
||||
|
|
@ -48,21 +49,25 @@ pub fn create(allocator: std.mem.Allocator) !tui.Mode {
|
|||
.modal = try ModalBackground.create(*Self, allocator, tui.mainview_widget(), .{ .ctx = self }),
|
||||
.menu = try Menu.create(*Self, allocator, tui.plane(), .{
|
||||
.ctx = self,
|
||||
.style = widget_style_type,
|
||||
.on_render = on_render_menu,
|
||||
.on_resize = on_resize_menu,
|
||||
.prepare_resize = prepare_resize_menu,
|
||||
}),
|
||||
.logger = log.logger(@typeName(Self)),
|
||||
.inputbox = (try self.menu.add_header(try InputBox.create(*Self, self.allocator, self.menu.menu.parent, .{
|
||||
.ctx = self,
|
||||
.label = inputbox_label,
|
||||
.padding = 2,
|
||||
.icon = " ",
|
||||
}))).dynamic_cast(InputBox.State(*Self)) orelse unreachable,
|
||||
.buffer_manager = tui.get_buffer_manager(),
|
||||
.longest = inputbox_label.len,
|
||||
};
|
||||
try self.commands.init(self);
|
||||
try tui.message_filters().add(MessageFilter.bind(self, receive_project_manager));
|
||||
self.query_pending = true;
|
||||
try project_manager.request_recent_files(max_recent_files);
|
||||
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = max_menu_width() + 2 });
|
||||
self.do_resize();
|
||||
try mv.floating_views.add(self.modal.widget());
|
||||
try mv.floating_views.add(self.menu.container_widget);
|
||||
var mode = try keybind.mode("overlay/palette", allocator, .{
|
||||
|
|
@ -85,7 +90,7 @@ pub fn deinit(self: *Self) void {
|
|||
}
|
||||
|
||||
inline fn menu_width(self: *Self) usize {
|
||||
return @max(@min(self.longest, max_menu_width()) + 2, inputbox_label.len + 2);
|
||||
return @max(@min(self.longest + 3, max_menu_width()) + 5, inputbox_label.len + 2);
|
||||
}
|
||||
|
||||
inline fn menu_pos_x(self: *Self) usize {
|
||||
|
|
@ -99,58 +104,23 @@ inline fn max_menu_width() usize {
|
|||
return @max(15, width - (width / 5));
|
||||
}
|
||||
|
||||
fn on_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool {
|
||||
const style_base = theme.editor_widget;
|
||||
const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget;
|
||||
const style_keybind = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_base;
|
||||
button.plane.set_base_style(style_base);
|
||||
button.plane.erase();
|
||||
button.plane.home();
|
||||
button.plane.set_style(style_label);
|
||||
if (button.active or button.hover or selected) {
|
||||
button.plane.fill(" ");
|
||||
button.plane.home();
|
||||
}
|
||||
var file_path: []const u8 = undefined;
|
||||
var file_type: []const u8 = undefined;
|
||||
var file_icon: []const u8 = undefined;
|
||||
var file_color: u24 = undefined;
|
||||
var iter = button.opts.label; // label contains cbor, first the file name, then multiple match indexes
|
||||
if (!(cbor.matchString(&iter, &file_path) catch false)) file_path = "#ERROR#";
|
||||
if (!(cbor.matchString(&iter, &file_type) catch false)) file_type = file_type_config.default.name;
|
||||
if (!(cbor.matchString(&iter, &file_icon) catch false)) file_icon = file_type_config.default.icon;
|
||||
if (!(cbor.matchInt(u24, &iter, &file_color) catch false)) file_icon = file_type_config.default.icon;
|
||||
|
||||
button.plane.set_style(style_keybind);
|
||||
const dirty = if (self.buffer_manager) |bm| if (bm.is_buffer_dirty(file_path)) "" else " " else " ";
|
||||
const pointer = if (selected) "⏵" else dirty;
|
||||
_ = button.plane.print("{s}", .{pointer}) catch {};
|
||||
|
||||
if (tui.config().show_fileicons) {
|
||||
tui.render_file_icon(&button.plane, file_icon, file_color);
|
||||
_ = button.plane.print(" ", .{}) catch {};
|
||||
}
|
||||
|
||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
var removed_prefix: usize = 0;
|
||||
const max_len = max_menu_width() - 2;
|
||||
button.plane.set_style(style_label);
|
||||
_ = button.plane.print("{s} ", .{
|
||||
if (file_path.len > max_len) root.shorten_path(&buf, file_path, &removed_prefix, max_len) else file_path,
|
||||
}) catch {};
|
||||
|
||||
var index: usize = 0;
|
||||
var len = cbor.decodeArrayHeader(&iter) catch return false;
|
||||
while (len > 0) : (len -= 1) {
|
||||
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
|
||||
tui.render_match_cell(&button.plane, 0, index + 4, theme) catch break;
|
||||
} else break;
|
||||
}
|
||||
return false;
|
||||
fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool {
|
||||
return tui.render_file_item_cbor(&button.plane, button.opts.label, button.active, selected, button.hover, theme);
|
||||
}
|
||||
|
||||
fn on_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) void {
|
||||
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = self.menu_width() });
|
||||
fn prepare_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) Widget.Box {
|
||||
return self.prepare_resize();
|
||||
}
|
||||
|
||||
fn prepare_resize(self: *Self) Widget.Box {
|
||||
const w = self.menu_width();
|
||||
const x = self.menu_pos_x();
|
||||
const h = self.menu.menu.widgets.items.len;
|
||||
return .{ .y = 0, .x = x, .w = w, .h = h };
|
||||
}
|
||||
|
||||
fn do_resize(self: *Self) void {
|
||||
self.menu.resize(self.prepare_resize());
|
||||
}
|
||||
|
||||
fn menu_action_open_file(menu: **Menu.State(*Self), button: *Button.State(*Menu.State(*Self))) void {
|
||||
|
|
@ -164,19 +134,19 @@ fn menu_action_open_file(menu: **Menu.State(*Self), button: *Button.State(*Menu.
|
|||
fn add_item(
|
||||
self: *Self,
|
||||
file_name: []const u8,
|
||||
file_type: []const u8,
|
||||
file_icon: []const u8,
|
||||
file_color: u24,
|
||||
indicator: []const u8,
|
||||
matches: ?[]const u8,
|
||||
) !void {
|
||||
var label = std.ArrayList(u8).init(self.allocator);
|
||||
defer label.deinit();
|
||||
const writer = label.writer();
|
||||
try cbor.writeValue(writer, file_name);
|
||||
try cbor.writeValue(writer, file_type);
|
||||
try cbor.writeValue(writer, file_icon);
|
||||
try cbor.writeValue(writer, file_color);
|
||||
if (matches) |cb| _ = try writer.write(cb);
|
||||
try cbor.writeValue(writer, indicator);
|
||||
if (matches) |cb| _ = try writer.write(cb) else try cbor.writeValue(writer, &[_]usize{});
|
||||
try self.menu.add_item_with_handler(label.items, menu_action_open_file);
|
||||
}
|
||||
|
||||
|
|
@ -206,8 +176,9 @@ fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void
|
|||
tp.extract_cbor(&matches),
|
||||
})) {
|
||||
if (self.need_reset) self.reset_results();
|
||||
try self.add_item(file_name, file_type, file_icon, file_color, matches);
|
||||
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = self.menu_width() });
|
||||
const indicator = if (self.buffer_manager) |bm| tui.get_file_state_indicator(bm, file_name) else "";
|
||||
try self.add_item(file_name, file_icon, file_color, indicator, matches);
|
||||
self.do_resize();
|
||||
if (self.need_select_first) {
|
||||
self.menu.select_down();
|
||||
self.need_select_first = false;
|
||||
|
|
@ -223,8 +194,9 @@ fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void
|
|||
tp.extract(&file_color),
|
||||
})) {
|
||||
if (self.need_reset) self.reset_results();
|
||||
try self.add_item(file_name, file_type, file_icon, file_color, null);
|
||||
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = self.menu_width() });
|
||||
const indicator = if (self.buffer_manager) |bm| tui.get_file_state_indicator(bm, file_name) else "";
|
||||
try self.add_item(file_name, file_icon, file_color, indicator, null);
|
||||
self.do_resize();
|
||||
if (self.need_select_first) {
|
||||
self.menu.select_down();
|
||||
self.need_select_first = false;
|
||||
|
|
@ -398,6 +370,13 @@ const cmds = struct {
|
|||
}
|
||||
pub const overlay_toggle_inputview_meta: Meta = .{};
|
||||
|
||||
pub fn overlay_next_widget_style(self: *Self, _: Ctx) Result {
|
||||
Widget.Style.set_next_style(widget_style_type);
|
||||
self.do_resize();
|
||||
tui.need_render();
|
||||
}
|
||||
pub const overlay_next_widget_style_meta: Meta = .{};
|
||||
|
||||
pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result {
|
||||
return overlay_insert_bytes(self, ctx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ const ModalBackground = @import("../../ModalBackground.zig");
|
|||
pub const Menu = @import("../../Menu.zig");
|
||||
|
||||
const max_menu_width = 80;
|
||||
const widget_style_type: Widget.Style.Type = .palette;
|
||||
|
||||
pub fn Create(options: type) type {
|
||||
return struct {
|
||||
|
|
@ -57,8 +58,10 @@ pub fn Create(options: type) type {
|
|||
}),
|
||||
.menu = try Menu.create(*Self, allocator, tui.plane(), .{
|
||||
.ctx = self,
|
||||
.style = widget_style_type,
|
||||
.on_render = if (@hasDecl(options, "on_render_menu")) options.on_render_menu else on_render_menu,
|
||||
.on_resize = on_resize_menu,
|
||||
.prepare_resize = prepare_resize_menu,
|
||||
.after_resize = after_resize_menu,
|
||||
.on_scroll = EventHandler.bind(self, Self.on_scroll),
|
||||
.on_click4 = mouse_click_button4,
|
||||
.on_click5 = mouse_click_button5,
|
||||
|
|
@ -67,6 +70,8 @@ pub fn Create(options: type) type {
|
|||
.inputbox = (try self.menu.add_header(try InputBox.create(*Self, self.allocator, self.menu.menu.parent, .{
|
||||
.ctx = self,
|
||||
.label = options.label,
|
||||
.padding = 2,
|
||||
.icon = if (@hasDecl(options, "icon")) options.icon else null,
|
||||
}))).dynamic_cast(InputBox.State(*Self)) orelse unreachable,
|
||||
.view_rows = get_view_rows(tui.screen()),
|
||||
.entries = std.ArrayList(Entry).init(allocator),
|
||||
|
|
@ -130,8 +135,7 @@ pub fn Create(options: type) type {
|
|||
if (!(cbor.matchString(&iter, &hint) catch false))
|
||||
hint = "";
|
||||
button.plane.set_style(style_hint);
|
||||
const pointer = if (selected) "⏵" else " ";
|
||||
_ = button.plane.print("{s}", .{pointer}) catch {};
|
||||
tui.render_pointer(&button.plane, selected);
|
||||
button.plane.set_style(style_label);
|
||||
_ = button.plane.print("{s} ", .{label}) catch {};
|
||||
button.plane.set_style(style_hint);
|
||||
|
|
@ -140,25 +144,38 @@ pub fn Create(options: type) type {
|
|||
var len = cbor.decodeArrayHeader(&iter) catch return false;
|
||||
while (len > 0) : (len -= 1) {
|
||||
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
|
||||
tui.render_match_cell(&button.plane, 0, index + 1, theme) catch break;
|
||||
tui.render_match_cell(&button.plane, 0, index + 2, theme) catch break;
|
||||
} else break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn on_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) void {
|
||||
self.do_resize();
|
||||
// self.start_query(0) catch {};
|
||||
fn prepare_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) Widget.Box {
|
||||
return self.prepare_resize();
|
||||
}
|
||||
|
||||
fn do_resize(self: *Self) void {
|
||||
fn prepare_resize(self: *Self) Widget.Box {
|
||||
const screen = tui.screen();
|
||||
const w = @max(@min(self.longest, max_menu_width) + 2 + 1 + self.longest_hint, options.label.len + 2);
|
||||
const w = @max(@min(self.longest + 3, max_menu_width) + 2 + self.longest_hint, options.label.len + 2);
|
||||
const x = if (screen.w > w) (screen.w - w) / 2 else 0;
|
||||
self.view_rows = get_view_rows(screen);
|
||||
const h = @min(self.items + self.menu.header_count, self.view_rows + self.menu.header_count);
|
||||
self.menu.container.resize(.{ .y = 0, .x = x, .w = w, .h = h });
|
||||
return .{ .y = 0, .x = x, .w = w, .h = h };
|
||||
}
|
||||
|
||||
fn after_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) void {
|
||||
return self.after_resize();
|
||||
}
|
||||
|
||||
fn after_resize(self: *Self) void {
|
||||
self.update_scrollbar();
|
||||
// self.start_query(0) catch {};
|
||||
}
|
||||
|
||||
fn do_resize(self: *Self, padding: Widget.Style.Margin) void {
|
||||
const box = self.prepare_resize();
|
||||
self.menu.resize(self.menu.container.to_client_box(box, padding));
|
||||
self.after_resize();
|
||||
}
|
||||
|
||||
fn get_view_rows(screen: Widget.Box) usize {
|
||||
|
|
@ -239,7 +256,8 @@ pub fn Create(options: type) type {
|
|||
var i = n;
|
||||
while (i > 0) : (i -= 1)
|
||||
self.menu.select_down();
|
||||
self.do_resize();
|
||||
const padding = Widget.Style.from_type(widget_style_type).padding;
|
||||
self.do_resize(padding);
|
||||
tui.refresh_hover();
|
||||
self.selection_updated();
|
||||
}
|
||||
|
|
@ -457,7 +475,10 @@ pub fn Create(options: type) type {
|
|||
}
|
||||
}
|
||||
}
|
||||
pub const palette_menu_delete_item_meta: Meta = .{};
|
||||
pub const palette_menu_delete_item_meta: Meta = .{
|
||||
.description = "Delete item",
|
||||
.icon = "",
|
||||
};
|
||||
|
||||
pub fn palette_menu_activate(self: *Self, _: Ctx) Result {
|
||||
self.menu.activate_selected();
|
||||
|
|
@ -511,6 +532,14 @@ pub fn Create(options: type) type {
|
|||
}
|
||||
pub const overlay_toggle_inputview_meta: Meta = .{};
|
||||
|
||||
pub fn overlay_next_widget_style(self: *Self, _: Ctx) Result {
|
||||
Widget.Style.set_next_style(widget_style_type);
|
||||
const padding = Widget.Style.from_type(widget_style_type).padding;
|
||||
self.do_resize(padding);
|
||||
tui.need_render();
|
||||
}
|
||||
pub const overlay_next_widget_style_meta: Meta = .{};
|
||||
|
||||
pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result {
|
||||
return overlay_insert_bytes(self, ctx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,10 +33,8 @@ pub fn load_entries(palette: *Type) !usize {
|
|||
(try palette.entries.addOne()).* = .{ .label = try palette.allocator.dupe(u8, task) };
|
||||
} else return error.InvalidTaskMessageField;
|
||||
}
|
||||
(try palette.entries.addOne()).* = .{
|
||||
.label = try palette.allocator.dupe(u8, " Add new task"),
|
||||
.command = "add_task",
|
||||
};
|
||||
(try palette.entries.addOne()).* = .{ .label = "", .command = "add_task" };
|
||||
(try palette.entries.addOne()).* = .{ .label = "", .command = "palette_menu_delete_item" };
|
||||
return if (palette.entries.items.len == 0) label.len else blk: {
|
||||
var longest: usize = 0;
|
||||
for (palette.entries.items) |item| longest = @max(longest, item.label.len);
|
||||
|
|
@ -60,7 +58,7 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v
|
|||
palette.items += 1;
|
||||
}
|
||||
|
||||
pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
|
||||
pub fn on_render_menu(palette: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
|
||||
var entry: Entry = undefined;
|
||||
var iter = button.opts.label; // label contains cbor entry object and matches
|
||||
if (!(cbor.matchValue(&iter, cbor.extract(&entry)) catch false))
|
||||
|
|
@ -84,11 +82,30 @@ pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.
|
|||
button.plane.set_style(style_label);
|
||||
button.plane.fill(" ");
|
||||
button.plane.home();
|
||||
|
||||
button.plane.set_style(style_hint);
|
||||
const pointer = if (selected) "⏵" else " ";
|
||||
_ = button.plane.print("{s}", .{pointer}) catch {};
|
||||
tui.render_pointer(&button.plane, selected);
|
||||
|
||||
button.plane.set_style(style_label);
|
||||
_ = button.plane.print("{s} ", .{entry.label}) catch {};
|
||||
if (entry.command) |command_name| blk: {
|
||||
button.plane.set_style(style_hint);
|
||||
var label_: std.ArrayListUnmanaged(u8) = .empty;
|
||||
defer label_.deinit(palette.allocator);
|
||||
|
||||
const id = command.get_id(command_name) orelse break :blk;
|
||||
if (command.get_icon(id)) |icon|
|
||||
label_.writer(palette.allocator).print("{s} ", .{icon}) catch {};
|
||||
if (command.get_description(id)) |desc|
|
||||
label_.writer(palette.allocator).print("{s}", .{desc}) catch {};
|
||||
_ = button.plane.print("{s} ", .{label_.items}) catch {};
|
||||
|
||||
const hints = if (tui.input_mode()) |m| m.keybind_hints else @panic("no keybind hints");
|
||||
if (hints.get(command_name)) |hint|
|
||||
_ = button.plane.print_aligned_right(0, "{s} ", .{hint}) catch {};
|
||||
} else {
|
||||
_ = button.plane.print("{s} ", .{entry.label}) catch {};
|
||||
}
|
||||
|
||||
var index: usize = 0;
|
||||
var len = cbor.decodeArrayHeader(&iter) catch return false;
|
||||
while (len > 0) : (len -= 1) {
|
||||
|
|
@ -103,17 +120,13 @@ fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
|
|||
var entry: Entry = undefined;
|
||||
var iter = button.opts.label;
|
||||
if (!(cbor.matchValue(&iter, cbor.extract(&entry)) catch false)) return;
|
||||
var buffer_name = std.ArrayList(u8).init(menu.*.opts.ctx.allocator);
|
||||
defer buffer_name.deinit();
|
||||
buffer_name.writer().print("*{s}*", .{entry.label}) catch {};
|
||||
if (entry.command) |cmd| {
|
||||
if (entry.command) |command_name| {
|
||||
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
|
||||
tp.self_pid().send(.{ "cmd", cmd, .{entry.label} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
|
||||
tp.self_pid().send(.{ "cmd", command_name, .{} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
|
||||
} else {
|
||||
project_manager.add_task(entry.label) catch {};
|
||||
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
|
||||
tp.self_pid().send(.{ "cmd", "create_scratch_buffer", .{ buffer_name.items, "", "conf" } }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
|
||||
tp.self_pid().send(.{ "cmd", "shell_execute_stream", .{entry.label} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
|
||||
project_manager.add_task(entry.label) catch {};
|
||||
tp.self_pid().send(.{ "cmd", "run_task", .{entry.label} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
111
src/tui/tui.zig
111
src/tui/tui.zig
|
|
@ -916,12 +916,11 @@ const cmds = struct {
|
|||
}
|
||||
pub const switch_buffers_meta: Meta = .{ .description = "Switch buffers" };
|
||||
|
||||
pub fn select_task(self: *Self, _: Ctx) Result {
|
||||
return self.enter_overlay_mode(@import("mode/overlay/task_palette.zig").Type);
|
||||
}
|
||||
pub const select_task_meta: Meta = .{ .description = "Run task" };
|
||||
|
||||
pub fn add_task(self: *Self, ctx: Ctx) Result {
|
||||
var task: []const u8 = undefined;
|
||||
if (try ctx.args.match(.{tp.extract(&task)}))
|
||||
return call_add_task(task);
|
||||
|
||||
return enter_mini_mode(self, struct {
|
||||
pub const Type = @import("mode/mini/buffer.zig").Create(@This());
|
||||
pub const create = Type.create;
|
||||
|
|
@ -929,17 +928,42 @@ const cmds = struct {
|
|||
return @import("mode/overlay/task_palette.zig").name;
|
||||
}
|
||||
pub fn select(self_: *Type) void {
|
||||
project_manager.add_task(self_.input.items) catch |e| {
|
||||
const logger = log.logger("tui");
|
||||
logger.err("add_task", e);
|
||||
logger.deinit();
|
||||
};
|
||||
tp.self_pid().send(.{ "cmd", "run_task", .{self_.input.items} }) catch {};
|
||||
command.executeName("exit_mini_mode", .{}) catch {};
|
||||
command.executeName("select_task", .{}) catch {};
|
||||
}
|
||||
}, ctx);
|
||||
}
|
||||
pub const add_task_meta: Meta = .{ .description = "Add task" };
|
||||
pub const add_task_meta: Meta = .{
|
||||
.description = "Add new task",
|
||||
.arguments = &.{.string},
|
||||
.icon = "",
|
||||
};
|
||||
|
||||
fn call_add_task(task: []const u8) void {
|
||||
project_manager.add_task(task) catch |e| {
|
||||
const logger = log.logger("tui");
|
||||
logger.err("add_task", e);
|
||||
logger.deinit();
|
||||
};
|
||||
}
|
||||
|
||||
pub fn run_task(self: *Self, ctx: Ctx) Result {
|
||||
var task: []const u8 = undefined;
|
||||
if (try ctx.args.match(.{tp.extract(&task)})) {
|
||||
var buffer_name = std.ArrayList(u8).init(self.allocator);
|
||||
defer buffer_name.deinit();
|
||||
buffer_name.writer().print("*{s}*", .{task}) catch {};
|
||||
call_add_task(task);
|
||||
tp.self_pid().send(.{ "cmd", "create_scratch_buffer", .{ buffer_name.items, "", "conf" } }) catch |e| self.logger.err("task", e);
|
||||
tp.self_pid().send(.{ "cmd", "shell_execute_stream", .{task} }) catch |e| self.logger.err("task", e);
|
||||
} else {
|
||||
return self.enter_overlay_mode(@import("mode/overlay/task_palette.zig").Type);
|
||||
}
|
||||
}
|
||||
pub const run_task_meta: Meta = .{
|
||||
.description = "Run a task",
|
||||
.arguments = &.{.string},
|
||||
};
|
||||
|
||||
pub fn delete_task(_: *Self, ctx: Ctx) Result {
|
||||
var task: []const u8 = undefined;
|
||||
|
|
@ -1428,7 +1452,19 @@ pub fn message(comptime fmt: anytype, args: anytype) void {
|
|||
tp.self_pid().send(.{ "message", std.fmt.bufPrint(&buf, fmt, args) catch @panic("too large") }) catch {};
|
||||
}
|
||||
|
||||
const dirty_indicator = "";
|
||||
const hidden_indicator = "-";
|
||||
|
||||
pub fn get_file_state_indicator(buffer_manager: *const @import("Buffer").Manager, file_name: []const u8) []const u8 {
|
||||
return if (buffer_manager.get_buffer_for_file(file_name)) |buffer| get_buffer_state_indicator(buffer) else "";
|
||||
}
|
||||
|
||||
pub fn get_buffer_state_indicator(buffer: *const @import("Buffer")) []const u8 {
|
||||
return if (buffer.is_dirty()) dirty_indicator else if (buffer.is_hidden()) hidden_indicator else "";
|
||||
}
|
||||
|
||||
pub fn render_file_icon(self: *renderer.Plane, icon: []const u8, color: u24) void {
|
||||
if (!config().show_fileicons) return;
|
||||
var cell = self.cell_init();
|
||||
_ = self.at_cursor_cell(&cell) catch return;
|
||||
if (!(color == 0xFFFFFF or color == 0x000000 or color == 0x000001)) {
|
||||
|
|
@ -1437,6 +1473,7 @@ pub fn render_file_icon(self: *renderer.Plane, icon: []const u8, color: u24) voi
|
|||
_ = self.cell_load(&cell, icon) catch {};
|
||||
_ = self.putc(&cell) catch {};
|
||||
self.cursor_move_rel(0, 1) catch {};
|
||||
_ = self.print(" ", .{}) catch {};
|
||||
}
|
||||
|
||||
pub fn render_match_cell(self: *renderer.Plane, y: usize, x: usize, theme_: *const Widget.Theme) !void {
|
||||
|
|
@ -1447,6 +1484,56 @@ pub fn render_match_cell(self: *renderer.Plane, y: usize, x: usize, theme_: *con
|
|||
_ = self.putc(&cell) catch {};
|
||||
}
|
||||
|
||||
pub fn render_pointer(self: *renderer.Plane, selected: bool) void {
|
||||
const pointer = if (selected) "⏵ " else " ";
|
||||
_ = self.print("{s}", .{pointer}) catch {};
|
||||
}
|
||||
|
||||
pub fn render_file_item_cbor(self: *renderer.Plane, file_item_cbor: []const u8, active: bool, selected: bool, hover: bool, theme_: *const Widget.Theme) bool {
|
||||
const style_base = theme_.editor_widget;
|
||||
const style_label = if (active) theme_.editor_cursor else if (hover or selected) theme_.editor_selection else theme_.editor_widget;
|
||||
const style_hint = if (find_scope_style(theme_, "entity.name")) |sty| sty.style else style_label;
|
||||
self.set_base_style(style_base);
|
||||
self.erase();
|
||||
self.home();
|
||||
self.set_style(style_label);
|
||||
if (active or hover or selected) {
|
||||
self.fill(" ");
|
||||
self.home();
|
||||
}
|
||||
|
||||
self.set_style(style_hint);
|
||||
render_pointer(self, selected);
|
||||
|
||||
var iter = file_item_cbor;
|
||||
var file_path_: []const u8 = undefined;
|
||||
var icon: []const u8 = undefined;
|
||||
var color: u24 = undefined;
|
||||
if (!(cbor.matchString(&iter, &file_path_) catch false)) @panic("invalid buffer file path");
|
||||
if (!(cbor.matchString(&iter, &icon) catch false)) @panic("invalid buffer file type icon");
|
||||
if (!(cbor.matchInt(u24, &iter, &color) catch false)) @panic("invalid buffer file type color");
|
||||
|
||||
render_file_icon(self, icon, color);
|
||||
|
||||
self.set_style(style_label);
|
||||
_ = self.print("{s} ", .{file_path_}) catch {};
|
||||
|
||||
var indicator: []const u8 = undefined;
|
||||
if (!(cbor.matchString(&iter, &indicator) catch false))
|
||||
indicator = "";
|
||||
self.set_style(style_hint);
|
||||
_ = self.print_aligned_right(0, "{s} ", .{indicator}) catch {};
|
||||
|
||||
var index: usize = 0;
|
||||
var len = cbor.decodeArrayHeader(&iter) catch return false;
|
||||
while (len > 0) : (len -= 1) {
|
||||
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
|
||||
render_match_cell(self, 0, index + 5, theme_) catch break;
|
||||
} else break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn get_or_create_theme_file(self: *Self, allocator: std.mem.Allocator) ![]const u8 {
|
||||
const theme_name = self.theme_.name;
|
||||
if (root.read_theme(allocator, theme_name)) |content| {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue