diff --git a/src/config.zig b/src/config.zig index 13cc4e4..af6f055 100644 --- a/src/config.zig +++ b/src/config.zig @@ -3,10 +3,6 @@ const builtin = @import("builtin"); frame_rate: usize = 60, theme: []const u8 = "default", input_mode: []const u8 = "flow", -modestate_show: bool = true, -selectionstate_show: bool = true, -modstate_show: bool = false, -keystate_show: bool = false, gutter_line_numbers: bool = true, gutter_line_numbers_relative: bool = false, vim_normal_gutter_line_numbers_relative: bool = true, @@ -19,3 +15,6 @@ highlight_current_line_gutter: bool = true, show_whitespace: bool = false, animation_min_lag: usize = 0, //milliseconds animation_max_lag: usize = 150, //milliseconds + +top_bar: []const u8 = "", +bottom_bar: []const u8 = "mode file log selection diagnostics linenumber", diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index 109b02e..24a047e 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -34,8 +34,10 @@ widgets: *WidgetList, widgets_widget: Widget, floating_views: WidgetStack, commands: Commands = undefined, -statusbar: *Widget, +top_bar: ?*Widget = null, +bottom_bar: ?*Widget = null, editor: ?*ed.Editor = null, +view_widget_idx: usize, panels: ?*WidgetList = null, last_match_text: ?[]const u8 = null, location_history: location_history, @@ -67,17 +69,23 @@ pub fn create(a: std.mem.Allocator) !Widget { .widgets = undefined, .widgets_widget = undefined, .floating_views = WidgetStack.init(a), - .statusbar = undefined, .location_history = try location_history.create(), .file_stack = std.ArrayList([]const u8).init(a), + .view_widget_idx = 0, }; try self.commands.init(self); const w = Widget.to(self); const widgets = try WidgetList.createV(a, w, @typeName(Self), .dynamic); self.widgets = widgets; self.widgets_widget = widgets.widget(); + if (tui.current().config.top_bar.len > 0) { + self.top_bar = try widgets.addP(try @import("status/bar.zig").create(a, w, tui.current().config.top_bar, .none, null)); + self.view_widget_idx += 1; + } try widgets.add(try Widget.empty(a, self.widgets_widget.plane.*, .dynamic)); - self.statusbar = try widgets.addP(try @import("status/statusbar.zig").create(a, w, EventHandler.bind(self, handle_statusbar_event))); + if (tui.current().config.bottom_bar.len > 0) { + self.bottom_bar = try widgets.addP(try @import("status/bar.zig").create(a, w, tui.current().config.bottom_bar, .grip, EventHandler.bind(self, handle_bottom_bar_event))); + } if (tp.env.get().is("show-input")) self.toggle_inputview_async(); if (tp.env.get().is("show-log")) @@ -145,13 +153,13 @@ pub fn box(self: *const Self) Box { return Box.from(self.plane); } -pub fn handle_statusbar_event(self: *Self, _: tp.pid_ref, m: tp.message) tp.result { +fn handle_bottom_bar_event(self: *Self, _: tp.pid_ref, m: tp.message) tp.result { var y: usize = undefined; if (try m.match(.{ "D", event_type.PRESS, key.BUTTON1, tp.any, tp.any, tp.extract(&y), tp.any, tp.any })) - return self.statusbar_primary_drag(y); + return self.bottom_bar_primary_drag(y); } -fn statusbar_primary_drag(self: *Self, y: usize) tp.result { +fn bottom_bar_primary_drag(self: *Self, y: usize) tp.result { const panels = self.panels orelse blk: { cmds.toggle_panel(self, .{}) catch return; break :blk self.panels.?; @@ -225,7 +233,8 @@ const cmds = struct { pub fn open_project_cwd(self: *Self, _: Ctx) Result { try project_manager.open("."); - _ = try self.statusbar.msg(.{ "PRJ", "open" }); + if (self.top_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" }); + if (self.bottom_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" }); } pub fn open_project_dir(self: *Self, ctx: Ctx) Result { @@ -235,7 +244,8 @@ const cmds = struct { try project_manager.open(project_dir); const project = tp.env.get().str("project"); tui.current().rdr.set_terminal_working_directory(project); - _ = try self.statusbar.msg(.{ "PRJ", "open" }); + if (self.top_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" }); + if (self.bottom_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" }); } pub fn change_project(self: *Self, ctx: Ctx) Result { @@ -257,7 +267,8 @@ const cmds = struct { try project_manager.open(project_dir); const project = tp.env.get().str("project"); tui.current().rdr.set_terminal_working_directory(project); - _ = try self.statusbar.msg(.{ "PRJ", "open" }); + if (self.top_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" }); + if (self.bottom_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" }); if (try project_manager.request_most_recent_file(self.a)) |file_path| { defer self.a.free(file_path); try tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_path } }); @@ -603,16 +614,17 @@ pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn, w: *Widget) bool { fn create_editor(self: *Self) !void { if (self.editor) |editor| if (editor.file_path) |file_path| self.push_file_stack(file_path) catch {}; - self.widgets.replace(0, try Widget.empty(self.a, self.plane, .dynamic)); + self.widgets.replace(self.view_widget_idx, try Widget.empty(self.a, self.plane, .dynamic)); command.executeName("enter_mode_default", .{}) catch {}; var editor_widget = try ed.create(self.a, Widget.to(self)); errdefer editor_widget.deinit(self.a); if (editor_widget.get("editor")) |editor| { - editor.subscribe(EventHandler.to_unowned(self.statusbar)) catch @panic("subscribe unsupported"); + if (self.top_bar) |bar| editor.subscribe(EventHandler.to_unowned(bar)) catch @panic("subscribe unsupported"); + if (self.bottom_bar) |bar| editor.subscribe(EventHandler.to_unowned(bar)) catch @panic("subscribe unsupported"); editor.subscribe(EventHandler.bind(self, handle_editor_event)) catch @panic("subscribe unsupported"); self.editor = if (editor.dynamic_cast(ed.EditorWidget)) |p| &p.editor else null; } else @panic("mainview editor not found"); - self.widgets.replace(0, editor_widget); + self.widgets.replace(self.view_widget_idx, editor_widget); tui.current().resize(); } @@ -637,7 +649,7 @@ fn create_home(self: *Self) !void { if (self.editor) |_| return; var home_widget = try home.create(self.a, Widget.to(self)); errdefer home_widget.deinit(self.a); - self.widgets.replace(0, home_widget); + self.widgets.replace(self.view_widget_idx, home_widget); tui.current().resize(); } diff --git a/src/tui/status/bar.zig b/src/tui/status/bar.zig new file mode 100644 index 0000000..a79dd2f --- /dev/null +++ b/src/tui/status/bar.zig @@ -0,0 +1,29 @@ +const std = @import("std"); + +const status_widget = @import("widget.zig"); +const Widget = @import("../Widget.zig"); +const WidgetList = @import("../WidgetList.zig"); +const tui = @import("../tui.zig"); + +const Self = @This(); + +pub const Style = enum { none, grip }; + +pub fn create(a: std.mem.Allocator, parent: Widget, config: []const u8, style: Style, event_handler: ?Widget.EventHandler) !Widget { + var w = try WidgetList.createH(a, parent, "statusbar", .{ .static = 1 }); + if (style == .grip) w.after_render = render_grip; + w.ctx = w; + var it = std.mem.splitScalar(u8, config, ' '); + while (it.next()) |widget_name| + try w.add(try status_widget.create(widget_name, a, w.plane, event_handler) orelse continue); + return w.widget(); +} + +fn render_grip(ctx: ?*anyopaque, theme: *const Widget.Theme) void { + const w: *WidgetList = @ptrCast(@alignCast(ctx.?)); + if (w.hover()) { + w.plane.set_style(theme.statusbar_hover); + w.plane.cursor_move_yx(0, 0) catch {}; + _ = w.plane.putstr("  ") catch {}; + } +} diff --git a/src/tui/status/diagstate.zig b/src/tui/status/diagstate.zig index e7bee3e..ae51bfd 100644 --- a/src/tui/status/diagstate.zig +++ b/src/tui/status/diagstate.zig @@ -19,7 +19,7 @@ rendered: [:0]const u8 = "", const Self = @This(); -pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) !Widget { +pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) @import("widget.zig").CreateError!Widget { return Button.create_widget(Self, a, parent, .{ .ctx = .{}, .label = "", diff --git a/src/tui/status/expander.zig b/src/tui/status/expander.zig new file mode 100644 index 0000000..6c744a7 --- /dev/null +++ b/src/tui/status/expander.zig @@ -0,0 +1,43 @@ +const std = @import("std"); +const tp = @import("thespian"); +const Plane = @import("renderer").Plane; + +const Widget = @import("../Widget.zig"); + +plane: Plane, +on_event: ?Widget.EventHandler, + +const Self = @This(); + +pub fn create(a: std.mem.Allocator, parent: Plane, event_handler: ?Widget.EventHandler) @import("widget.zig").CreateError!Widget { + const self: *Self = try a.create(Self); + self.* = .{ + .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), + .on_event = event_handler, + }; + return Widget.to(self); +} + +pub fn deinit(self: *Self, a: std.mem.Allocator) void { + self.plane.deinit(); + a.destroy(self); +} + +pub fn layout(_: *Self) Widget.Layout { + return .dynamic; +} + +pub fn render(self: *Self, theme: *const Widget.Theme) bool { + self.plane.set_base_style(" ", theme.statusbar); + self.plane.erase(); + return false; +} + +pub fn receive(self: *Self, from: tp.pid_ref, m: tp.message) error{Exit}!bool { + var btn: u32 = 0; + if (try m.match(.{ "D", tp.any, tp.extract(&btn), tp.more })) { + if (self.on_event) |h| h.send(from, m) catch {}; + return true; + } + return false; +} diff --git a/src/tui/status/filestate.zig b/src/tui/status/filestate.zig index 221174e..b395cd7 100644 --- a/src/tui/status/filestate.zig +++ b/src/tui/status/filestate.zig @@ -34,7 +34,7 @@ file: bool = false, const project_icon = ""; const Self = @This(); -pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) !Widget { +pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) @import("widget.zig").CreateError!Widget { const btn = try Button.create(Self, a, parent, .{ .ctx = .{ .a = a, diff --git a/src/tui/status/keystate.zig b/src/tui/status/keystate.zig index 599044b..15ea117 100644 --- a/src/tui/status/keystate.zig +++ b/src/tui/status/keystate.zig @@ -31,7 +31,7 @@ const Self = @This(); const idle_msg = "🐶"; pub const width = idle_msg.len + 20; -pub fn create(a: Allocator, parent: Plane) !Widget { +pub fn create(a: Allocator, parent: Plane, _: ?Widget.EventHandler) @import("widget.zig").CreateError!Widget { var frame_rate = tp.env.get().num("frame-rate"); if (frame_rate == 0) frame_rate = 60; const self: *Self = try a.create(Self); diff --git a/src/tui/status/linenumstate.zig b/src/tui/status/linenumstate.zig index 775f269..46fefa6 100644 --- a/src/tui/status/linenumstate.zig +++ b/src/tui/status/linenumstate.zig @@ -18,7 +18,7 @@ rendered: [:0]const u8 = "", const Self = @This(); -pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) !Widget { +pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) @import("widget.zig").CreateError!Widget { return Button.create_widget(Self, a, parent, .{ .ctx = .{}, .label = "", diff --git a/src/tui/status/minilog.zig b/src/tui/status/minilog.zig index c657c33..85220a4 100644 --- a/src/tui/status/minilog.zig +++ b/src/tui/status/minilog.zig @@ -26,7 +26,7 @@ const Level = enum { err, }; -pub fn create(a: std.mem.Allocator, parent: Plane, event_handler: ?Widget.EventHandler) !Widget { +pub fn create(a: std.mem.Allocator, parent: Plane, event_handler: ?Widget.EventHandler) @import("widget.zig").CreateError!Widget { const self: *Self = try a.create(Self); self.* = .{ .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), diff --git a/src/tui/status/modestate.zig b/src/tui/status/modestate.zig index 773fda4..4241a65 100644 --- a/src/tui/status/modestate.zig +++ b/src/tui/status/modestate.zig @@ -13,8 +13,9 @@ const Button = @import("../Button.zig"); const command = @import("../command.zig"); const ed = @import("../editor.zig"); const tui = @import("../tui.zig"); +const CreateError = @import("widget.zig").CreateError; -pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) !Widget { +pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) CreateError!Widget { return Button.create_widget(void, a, parent, .{ .ctx = {}, .label = tui.get_mode(), diff --git a/src/tui/status/modstate.zig b/src/tui/status/modstate.zig index c34a799..fde44ec 100644 --- a/src/tui/status/modstate.zig +++ b/src/tui/status/modstate.zig @@ -23,7 +23,7 @@ const Self = @This(); pub const width = 5; -pub fn create(a: Allocator, parent: Plane) !Widget { +pub fn create(a: Allocator, parent: Plane, _: ?Widget.EventHandler) @import("widget.zig").CreateError!Widget { const self: *Self = try a.create(Self); self.* = .{ .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), diff --git a/src/tui/status/selectionstate.zig b/src/tui/status/selectionstate.zig index 0d46cbc..9e5d5b4 100644 --- a/src/tui/status/selectionstate.zig +++ b/src/tui/status/selectionstate.zig @@ -19,7 +19,7 @@ on_event: ?Widget.EventHandler, const Self = @This(); -pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) !Widget { +pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) @import("widget.zig").CreateError!Widget { const self: *Self = try a.create(Self); self.* = .{ .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), diff --git a/src/tui/status/statusbar.zig b/src/tui/status/statusbar.zig deleted file mode 100644 index 6c41bdf..0000000 --- a/src/tui/status/statusbar.zig +++ /dev/null @@ -1,39 +0,0 @@ -const std = @import("std"); - -const Widget = @import("../Widget.zig"); -const WidgetList = @import("../WidgetList.zig"); -const tui = @import("../tui.zig"); - -const Self = @This(); - -pub fn create(a: std.mem.Allocator, parent: Widget, event_handler: ?Widget.EventHandler) !Widget { - var w = try WidgetList.createH(a, parent, "statusbar", .{ .static = 1 }); - w.after_render = render_grip; - w.ctx = w; - if (tui.current().config.modestate_show) try w.add(try @import("modestate.zig").create(a, w.plane, event_handler)); - try w.add(try @import("filestate.zig").create(a, w.plane, event_handler)); - try w.add(try @import("minilog.zig").create(a, w.plane, event_handler)); - if (tui.current().config.selectionstate_show) try w.add(try @import("selectionstate.zig").create(a, w.plane, event_handler)); - try w.add(try @import("diagstate.zig").create(a, w.plane, event_handler)); - try w.add(try @import("linenumstate.zig").create(a, w.plane, event_handler)); - 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(); -} - -fn render_grip(ctx: ?*anyopaque, theme: *const Widget.Theme) void { - const w: *WidgetList = @ptrCast(@alignCast(ctx.?)); - if (w.hover()) { - const w0 = &w.widgets.items[0].widget; - const style = if (tui.current().config.modestate_show) - if (w0.hover()) - theme.editor_selection - else - theme.statusbar_hover - else - theme.statusbar_hover; - w.plane.set_style(style); - w.plane.cursor_move_yx(0, 0) catch {}; - _ = w.plane.putstr("  ") catch {}; - } -} diff --git a/src/tui/status/widget.zig b/src/tui/status/widget.zig new file mode 100644 index 0000000..f6320c4 --- /dev/null +++ b/src/tui/status/widget.zig @@ -0,0 +1,22 @@ +const std = @import("std"); +const Widget = @import("../Widget.zig"); +const Plane = @import("renderer").Plane; + +const widgets = std.static_string_map.StaticStringMap(create_fn).initComptime(.{ + .{ "mode", @import("modestate.zig").create }, + .{ "file", @import("filestate.zig").create }, + .{ "log", @import("minilog.zig").create }, + .{ "selection", @import("selectionstate.zig").create }, + .{ "diagnostics", @import("diagstate.zig").create }, + .{ "linenumber", @import("linenumstate.zig").create }, + .{ "modifiers", @import("modstate.zig").create }, + .{ "keystate", @import("keystate.zig").create }, + .{ "expander", @import("expander.zig").create }, +}); +pub const CreateError = error{ OutOfMemory, Exit }; +const create_fn = *const fn (a: std.mem.Allocator, parent: Plane, event_handler: ?Widget.EventHandler) CreateError!Widget; + +pub fn create(name: []const u8, a: std.mem.Allocator, parent: Plane, event_handler: ?Widget.EventHandler) CreateError!?Widget { + const create_ = widgets.get(name) orelse return null; + return try create_(a, parent, event_handler); +}