feat: add search box to open recent file mode

This commit is contained in:
CJ van den Berg 2024-03-28 22:18:23 +01:00
parent f88adf9a9d
commit 744c6012a7
2 changed files with 86 additions and 15 deletions

View file

@ -8,6 +8,7 @@ const command = @import("../../command.zig");
const EventHandler = @import("../../EventHandler.zig");
const MessageFilter = @import("../../MessageFilter.zig");
const Button = @import("../../Button.zig");
const InputBox = @import("../../InputBox.zig");
const Menu = @import("../../Menu.zig");
const Widget = @import("../../Widget.zig");
const mainview = @import("../../mainview.zig");
@ -19,8 +20,11 @@ const max_recent_files: usize = 25;
a: std.mem.Allocator,
f: usize = 0,
menu: *Menu.State(*Self),
inputbox: *InputBox.State(*Self),
logger: log.Logger,
count: usize = 0,
query_pending: bool = false,
need_reset: bool = false,
need_select_first: bool = true,
longest: usize = 0,
commands: Commands = undefined,
@ -31,9 +35,14 @@ pub fn create(a: std.mem.Allocator) !tui.Mode {
.a = a,
.menu = try Menu.create(*Self, a, tui.current().mainview, .{ .ctx = self, .on_render = on_render_menu, .on_resize = on_resize_menu }),
.logger = log.logger(@typeName(Self)),
.inputbox = (try self.menu.add_header(try InputBox.create(*Self, self.a, self.menu.menu.parent, .{
.ctx = self,
.label = "Search files by name",
}))).dynamic_cast(InputBox.State(*Self)) orelse unreachable,
};
try self.commands.init(self);
try tui.current().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 = 25, .w = 32 });
try mv.floating_views.add(self.menu.menu_widget);
@ -87,14 +96,22 @@ fn receive_project_manager(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit
fn process_project_manager(self: *Self, m: tp.message) tp.result {
var file_name: []const u8 = undefined;
var query: []const u8 = undefined;
if (try m.match(.{ "PRJ", "recent", tp.extract(&file_name) })) {
if (self.count < max_recent_files) {
self.count += 1;
self.longest = @max(self.longest, file_name.len);
self.menu.add_item_with_handler(file_name, menu_action_open_file) catch |e| return tp.exit_error(e);
self.menu.resize(.{ .y = 0, .x = 25, .w = @min(self.longest, 80) + 2 });
tui.need_render();
if (self.need_reset) self.reset_results();
self.longest = @max(self.longest, file_name.len);
self.menu.add_item_with_handler(file_name, menu_action_open_file) catch |e| return tp.exit_error(e);
self.menu.resize(.{ .y = 0, .x = 25, .w = @min(self.longest, 80) + 2 });
if (self.need_select_first) {
self.menu.select_down();
self.need_select_first = false;
}
tui.need_render();
} else if (try m.match(.{ "PRJ", "recent_done", tp.extract(&query) })) {
self.query_pending = false;
self.need_reset = true;
if (!std.mem.eql(u8, self.inputbox.text.items, query))
try self.start_query();
} else {
self.logger.err("receive", tp.unexpected(m));
}
@ -103,24 +120,25 @@ fn process_project_manager(self: *Self, m: tp.message) tp.result {
pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
var evtype: u32 = undefined;
var keypress: u32 = undefined;
var egc: u32 = undefined;
var modifiers: u32 = undefined;
if (try m.match(.{ "I", tp.extract(&evtype), tp.extract(&keypress), tp.any, tp.string, tp.extract(&modifiers) })) {
try self.mapEvent(evtype, keypress, modifiers);
if (try m.match(.{ "I", tp.extract(&evtype), tp.extract(&keypress), tp.extract(&egc), tp.string, tp.extract(&modifiers) })) {
try self.mapEvent(evtype, keypress, egc, modifiers);
}
return false;
}
fn mapEvent(self: *Self, evtype: u32, keypress: u32, modifiers: u32) tp.result {
fn mapEvent(self: *Self, evtype: u32, keypress: u32, egc: u32, modifiers: u32) tp.result {
return switch (evtype) {
nc.event_type.PRESS => self.mapPress(keypress, modifiers),
nc.event_type.REPEAT => self.mapPress(keypress, modifiers),
nc.event_type.PRESS => self.mapPress(keypress, egc, modifiers),
nc.event_type.REPEAT => self.mapPress(keypress, egc, modifiers),
nc.event_type.RELEASE => self.mapRelease(keypress, modifiers),
else => {},
};
}
fn mapPress(self: *Self, keypress: u32, modifiers: u32) tp.result {
fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result {
const keynormal = if ('a' <= keypress and keypress <= 'z') keypress - ('a' - 'A') else keypress;
return switch (modifiers) {
nc.mod.CTRL => switch (keynormal) {
@ -128,10 +146,13 @@ fn mapPress(self: *Self, keypress: u32, modifiers: u32) tp.result {
'Q' => self.cmd("quit", .{}),
'W' => self.cmd("close_file", .{}),
'E' => self.cmd("open_recent_menu_down", .{}),
'P' => self.cmd("open_recent_menu_up", .{}),
'N' => self.cmd("open_recent_menu_down", .{}),
nc.key.ESC => self.cmd("exit_overlay_mode", .{}),
nc.key.UP => self.cmd("open_recent_menu_up", .{}),
nc.key.DOWN => self.cmd("open_recent_menu_down", .{}),
nc.key.ENTER => self.cmd("open_recent_menu_activate", .{}),
nc.key.BACKSPACE => self.delete_word(),
else => {},
},
nc.mod.CTRL | nc.mod.SHIFT => switch (keynormal) {
@ -147,6 +168,11 @@ fn mapPress(self: *Self, keypress: u32, modifiers: u32) tp.result {
'I' => self.cmd("toggle_inputview", .{}),
else => {},
},
nc.mod.SHIFT => switch (keypress) {
else => if (!nc.key.synthesized_p(keypress))
self.insert_code_point(egc)
else {},
},
0 => switch (keypress) {
nc.key.F09 => self.cmd("theme_prev", .{}),
nc.key.F10 => self.cmd("theme_next", .{}),
@ -156,7 +182,10 @@ fn mapPress(self: *Self, keypress: u32, modifiers: u32) tp.result {
nc.key.UP => self.cmd("open_recent_menu_up", .{}),
nc.key.DOWN => self.cmd("open_recent_menu_down", .{}),
nc.key.ENTER => self.cmd("open_recent_menu_activate", .{}),
else => {},
nc.key.BACKSPACE => self.delete_code_point(),
else => if (!nc.key.synthesized_p(keypress))
self.insert_code_point(egc)
else {},
},
else => {},
};
@ -164,11 +193,50 @@ fn mapPress(self: *Self, keypress: u32, modifiers: u32) tp.result {
fn mapRelease(self: *Self, keypress: u32, _: u32) tp.result {
return switch (keypress) {
nc.key.LCTRL, nc.key.RCTRL => self.cmd("open_recent_menu_activate", .{}),
nc.key.LCTRL, nc.key.RCTRL => if (self.menu.selected orelse 0 > 0) return self.cmd("open_recent_menu_activate", .{}),
else => {},
};
}
fn reset_results(self: *Self) void {
self.need_reset = false;
self.menu.reset_items();
self.menu.selected = null;
self.need_select_first = true;
}
fn start_query(self: *Self) tp.result {
if (self.query_pending) return;
self.query_pending = true;
try project_manager.query_recent_files(max_recent_files, self.inputbox.text.items);
}
fn delete_word(self: *Self) tp.result {
if (std.mem.lastIndexOfAny(u8, self.inputbox.text.items, "/\\. -_")) |pos| {
self.inputbox.text.shrinkRetainingCapacity(pos);
} else {
self.inputbox.text.shrinkRetainingCapacity(0);
}
self.inputbox.cursor = self.inputbox.text.items.len;
return self.start_query();
}
fn delete_code_point(self: *Self) tp.result {
if (self.inputbox.text.items.len > 0) {
self.inputbox.text.shrinkRetainingCapacity(self.inputbox.text.items.len - 1);
self.inputbox.cursor = self.inputbox.text.items.len;
}
return self.start_query();
}
fn insert_code_point(self: *Self, c: u32) tp.result {
var buf: [6]u8 = undefined;
const bytes = nc.ucs32_to_utf8(&[_]u32{c}, &buf) catch |e| return tp.exit_error(e);
self.inputbox.text.appendSlice(buf[0..bytes]) catch |e| return tp.exit_error(e);
self.inputbox.cursor = self.inputbox.text.items.len;
return self.start_query();
}
fn cmd(_: *Self, name_: []const u8, ctx: command.Context) tp.result {
try command.executeName(name_, ctx);
}

View file

@ -299,6 +299,9 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) tp.result {
if (try m.match(.{ "exit", "timeout_error", 125, "Operation aborted." }))
return;
if (try m.match(.{ "PRJ", tp.more })) // drop late project manager query responses
return;
return tp.unexpected(m);
}