@@ 35,36 35,32 @@ const Self = @This();
const context = &@import("nfm.zig").context;
-pub const EscapeKey = union(enum) {
- unknown,
- escape,
- arrow_up,
- arrow_down,
- arrow_left,
- arrow_right,
- end,
- home,
- page_up,
- page_down,
- delete,
- insert,
- f1,
- f2,
- f3,
- f4,
- f5,
- f6,
- f7,
- f8,
- f9,
- f10,
- f11,
- f12,
+pub const Event = union(enum) {
+ sigwinch: void,
+ unknown: void,
+ escape: void,
+ arrow_up: void,
+ arrow_down: void,
+ arrow_left: void,
+ arrow_right: void,
+ end: void,
+ home: void,
+ page_up: void,
+ page_down: void,
+ delete: void,
+ insert: void,
+ function: u8,
+ ctrl: u8,
+ alt: u8,
+ ascii: u8,
};
/// The various escape sequences that represent special keys.
+/// TODO[zig] This currently exceeds the operation limit for comptime, so unused
+/// values are commented out. Hopefully in the future comptime can
+/// handle this completely.
const escape_key_codes = std.ComptimeStringMap(
- EscapeKey,
+ Event,
.{
// Legacy
.{ "[A", .arrow_up },
@@ 87,31 83,102 @@ const escape_key_codes = std.ComptimeStringMap(
.{ "[1~", .home },
.{ "[7~", .home },
.{ "[H~", .home },
- .{ "OP", .f1 },
- .{ "OQ", .f2 },
- .{ "OR", .f3 },
- .{ "OS", .f4 },
- .{ "[15~", .f5 },
- .{ "[17~", .f6 },
- .{ "[18~", .f7 },
- .{ "[19~", .f8 },
- .{ "[20~", .f9 },
- .{ "[21~", .f10 },
- .{ "[23~", .f11 },
- .{ "[24~", .f12 },
+ //.{ "OP", .{ .function = 1 } },
+ //.{ "OQ", .{ .function = 2 } },
+ //.{ "OR", .{ .function = 3 } },
+ //.{ "OS", .{ .function = 4 } },
+ //.{ "[15~", .{ .function = 5 } },
+ //.{ "[17~", .{ .function = 6 } },
+ //.{ "[18~", .{ .function = 7 } },
+ //.{ "[19~", .{ .function = 8 } },
+ //.{ "[20~", .{ .function = 9 } },
+ //.{ "[21~", .{ .function = 10 } },
+ //.{ "[23~", .{ .function = 11 } },
+ //.{ "[24~", .{ .function = 12 } },
+ //.{ "a", .{ .alt = 'a' } },
+ //.{ "b", .{ .alt = 'b' } },
+ //.{ "c", .{ .alt = 'c' } },
+ //.{ "d", .{ .alt = 'd' } },
+ //.{ "e", .{ .alt = 'e' } },
+ //.{ "f", .{ .alt = 'f' } },
+ //.{ "g", .{ .alt = 'g' } },
+ //.{ "h", .{ .alt = 'h' } },
+ //.{ "i", .{ .alt = 'i' } },
+ //.{ "j", .{ .alt = 'j' } },
+ //.{ "k", .{ .alt = 'k' } },
+ //.{ "l", .{ .alt = 'l' } },
+ //.{ "m", .{ .alt = 'm' } },
+ //.{ "n", .{ .alt = 'n' } },
+ //.{ "o", .{ .alt = 'o' } },
+ //.{ "p", .{ .alt = 'p' } },
+ //.{ "q", .{ .alt = 'q' } },
+ //.{ "r", .{ .alt = 'r' } },
+ //.{ "s", .{ .alt = 's' } },
+ //.{ "t", .{ .alt = 't' } },
+ //.{ "u", .{ .alt = 'u' } },
+ //.{ "v", .{ .alt = 'v' } },
+ //.{ "w", .{ .alt = 'w' } },
+ //.{ "x", .{ .alt = 'x' } },
+ //.{ "y", .{ .alt = 'y' } },
+ //.{ "z", .{ .alt = 'z' } },
// Kitty
.{ "[27u", .escape },
+ //.{ "[97;5u", .{ .ctrl = 'a' } },
+ //.{ "[98;5u", .{ .ctrl = 'b' } },
+ .{ "[99;5u", .{ .ctrl = 'c' } },
+ //.{ "[100;5u", .{ .ctrl = 'd' } },
+ //.{ "[101;5u", .{ .ctrl = 'e' } },
+ //.{ "[102;5u", .{ .ctrl = 'f' } },
+ //.{ "[103;5u", .{ .ctrl = 'g' } },
+ //.{ "[104;5u", .{ .ctrl = 'h' } },
+ //.{ "[105;5u", .{ .ctrl = 'i' } },
+ //.{ "[106;5u", .{ .ctrl = 'j' } },
+ //.{ "[107;5u", .{ .ctrl = 'k' } },
+ //.{ "[108;5u", .{ .ctrl = 'l' } },
+ //.{ "[109;5u", .{ .ctrl = 'm' } },
+ //.{ "[110;5u", .{ .ctrl = 'n' } },
+ //.{ "[111;5u", .{ .ctrl = 'o' } },
+ //.{ "[112;5u", .{ .ctrl = 'p' } },
+ //.{ "[113;5u", .{ .ctrl = 'q' } },
+ //.{ "[114;5u", .{ .ctrl = 'r' } },
+ //.{ "[115;5u", .{ .ctrl = 's' } },
+ //.{ "[116;5u", .{ .ctrl = 't' } },
+ //.{ "[117;5u", .{ .ctrl = 'u' } },
+ //.{ "[118;5u", .{ .ctrl = 'v' } },
+ //.{ "[119;5u", .{ .ctrl = 'w' } },
+ //.{ "[120;5u", .{ .ctrl = 'x' } },
+ //.{ "[121;5u", .{ .ctrl = 'y' } },
+ //.{ "[122;5u", .{ .ctrl = 'z' } },
+ //.{ "[97;3u", .{ .alt = 'a' } },
+ //.{ "[98;3u", .{ .alt = 'b' } },
+ //.{ "[99;3u", .{ .alt = 'c' } },
+ //.{ "[100;3u", .{ .alt = 'd' } },
+ //.{ "[101;3u", .{ .alt = 'e' } },
+ //.{ "[102;3u", .{ .alt = 'f' } },
+ //.{ "[103;3u", .{ .alt = 'g' } },
+ //.{ "[104;3u", .{ .alt = 'h' } },
+ //.{ "[105;3u", .{ .alt = 'i' } },
+ //.{ "[106;3u", .{ .alt = 'j' } },
+ //.{ "[107;3u", .{ .alt = 'k' } },
+ //.{ "[108;3u", .{ .alt = 'l' } },
+ //.{ "[109;3u", .{ .alt = 'm' } },
+ //.{ "[110;3u", .{ .alt = 'n' } },
+ //.{ "[111;3u", .{ .alt = 'o' } },
+ //.{ "[112;3u", .{ .alt = 'p' } },
+ //.{ "[113;3u", .{ .alt = 'q' } },
+ //.{ "[114;3u", .{ .alt = 'r' } },
+ //.{ "[115;3u", .{ .alt = 's' } },
+ //.{ "[116;3u", .{ .alt = 't' } },
+ //.{ "[117;3u", .{ .alt = 'u' } },
+ //.{ "[118;3u", .{ .alt = 'v' } },
+ //.{ "[119;3u", .{ .alt = 'w' } },
+ //.{ "[120;3u", .{ .alt = 'x' } },
+ //.{ "[121;3u", .{ .alt = 'y' } },
+ //.{ "[122;3u", .{ .alt = 'z' } },
},
);
-pub const Event = union(enum) {
- sigwinch: void,
- ascii_key: u8,
- ctrl_key: u8,
- escape_key: EscapeKey,
-};
-
const Colour = enum {
none,
black,
@@ 292,7 359,6 @@ fn handleSigWinch(_: c_int) callconv(.C) void {
context.ui.render(true, true) catch {};
}
-// TODO .ctrl_key
pub fn nextEvent(self: *Self) !?Event {
if (self.sigwinch) {
self.sigwinch = false;
@@ 322,7 388,7 @@ pub fn nextEvent(self: *Self) !?Event {
termios.cc[vmin] = 0;
try os.tcsetattr(self.tty.handle, .NOW, termios);
- var esc_buffer: [4]u8 = undefined;
+ var esc_buffer: [8]u8 = undefined;
const esc_read = try self.tty.read(&esc_buffer);
termios.cc[vtime] = 0;
@@ 338,13 404,18 @@ pub fn nextEvent(self: *Self) !?Event {
// probably do the same. However this is a low priority goal, as
// this problem does not occur when using kitty keyboard mode.
- if (esc_read == 0) return Event{ .escape_key = .escape };
- return Event{ .escape_key = escape_key_codes.get(esc_buffer[0..esc_read]) orelse .unknown };
- } else {
- return Event{ .ascii_key = buffer[0] };
+ if (esc_read == 0) return .escape;
+ return escape_key_codes.get(esc_buffer[0..esc_read]) orelse .unknown;
+ }
+
+ // Legacy codes for Ctrl-[a-z].
+ // TODO Is there something in the std to take care of this for us?
+ const chars = [_]u8{ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w' };
+ for (chars) |char| {
+ if (buffer[0] == char & '\x1f') return Event{ .ctrl = char };
}
- unreachable;
+ return Event{ .ascii = buffer[0] };
}
pub fn deinit(self: *Self) void {
@@ 190,8 190,7 @@ fn handleEventUserInput(ev: UserInterface.Event) !void {
try context.ui.resize();
try context.ui.render(true, true);
},
- .ctrl_key => {}, // TODO support the typical readline-esque keybinds for user_input mode
- .ascii_key => |key| switch (key) {
+ .ascii => |key| switch (key) {
'\n', '\r' => try handleReturnUserInput(),
127 => { // Backspace
user_input.deleteLeft();
@@ 202,29 201,30 @@ fn handleEventUserInput(ev: UserInterface.Event) !void {
try context.ui.render(false, true);
},
},
- .escape_key => |key| switch (key) {
- .escape => {
- context.mode.setNav();
- try context.ui.render(false, true);
- },
- .arrow_left => {
- user_input.moveCursorLeft();
- try context.ui.render(false, true);
- },
- .arrow_right => {
- user_input.moveCursorRight();
- try context.ui.render(false, true);
- },
- .delete => {
- user_input.deleteRight();
- try context.ui.render(false, true);
- },
- else => {},
+ .escape => {
+ context.mode.setNav();
+ try context.ui.render(false, true);
+ },
+ .arrow_left => {
+ user_input.moveCursorLeft();
+ try context.ui.render(false, true);
},
+ .arrow_right => {
+ user_input.moveCursorRight();
+ try context.ui.render(false, true);
+ },
+ .delete => {
+ user_input.deleteRight();
+ try context.ui.render(false, true);
+ },
+ .ctrl => {}, // TODO support the typical readline-esque keybinds for user_input mode
+ else => {},
}
}
fn handleReturnUserInput() !void {
+ // Keybinds for the user input prompt are hardcoded, as configuration makes
+ // little sense here.
const user_input = &context.mode.user_input;
switch (user_input.operation) {
.command => {
@@ 270,7 270,7 @@ fn handleReturnUserInput() !void {
try context.ui.render(false, true);
return;
};
- try handleKeyNav('n');
+ try handleAsciiKeyNav('n');
},
.select => {
var select = Regex.compile(context.gpa, user_input.buffer.items) catch {
@@ 298,34 298,36 @@ fn handleReturnUserInput() !void {
}
fn handleEventNav(ev: UserInterface.Event) !void {
+ // TODO make navigation mode keybind configurable
switch (ev) {
.sigwinch => {
try context.ui.resize();
try context.ui.render(true, true);
},
- .ascii_key => |ch| try handleKeyNav(ch),
- .ctrl_key => {}, // TODO
- .escape_key => |key| switch (key) {
- // These will never return false, so we can safely
- // discard the return value.
- .arrow_up => try handleKeyNav('k'),
- .arrow_down => try handleKeyNav('j'),
- .arrow_left => try handleKeyNav('h'),
- .arrow_right => try handleKeyNav('l'),
- .end => try handleKeyNav('G'),
- .home => try handleKeyNav('g'),
- .escape => try handleEscapeNav(),
- .page_up => {
- // Scroll 80% of the terminal height.
- context.selection_delta -|= 4 * @intCast(isize, @divFloor(context.ui.height, 5));
- try context.ui.render(true, false);
- },
- .page_down => {
- context.selection_delta +|= 4 * @intCast(isize, @divFloor(context.ui.height, 5));
- try context.ui.render(true, false);
- },
- else => {}, // TODO maybe find useful features for these keys
+ .arrow_up => try handleAsciiKeyNav('k'),
+ .arrow_down => try handleAsciiKeyNav('j'),
+ .arrow_left => try handleAsciiKeyNav('h'),
+ .arrow_right => try handleAsciiKeyNav('l'),
+ .end => try handleAsciiKeyNav('G'),
+ .home => try handleAsciiKeyNav('g'),
+ .escape => try handleEscapeNav(),
+ .page_up => {
+ // Scroll 80% of the terminal height.
+ context.selection_delta -|= 4 * @intCast(isize, @divFloor(context.ui.height, 5));
+ try context.ui.render(true, false);
+ },
+ .page_down => {
+ context.selection_delta +|= 4 * @intCast(isize, @divFloor(context.ui.height, 5));
+ try context.ui.render(true, false);
+ },
+ .ctrl => |ch| {
+ if (ch == 'c') {
+ context.mode.setMessage(.info, "Press q to exit");
+ try context.ui.render(false, true);
+ }
},
+ .ascii => |ch| try handleAsciiKeyNav(ch),
+ else => {}, // TODO maybe find useful features for these keys
}
}
@@ 372,7 374,7 @@ fn timespecDiffLessThanOneSecond(a: os.timespec, b: os.timespec) bool {
return diff <= time.ns_per_s;
}
-fn handleKeyNav(key: u8) !void {
+fn handleAsciiKeyNav(key: u8) !void {
const title_dirty = context.mode != .nav;
context.mode.setNav();
switch (key) {