flow/src/keybind/parse_vim.zig
CJ van den Berg 0abb21a400
fix: add down level ':' keybindings to vim and helix modes
This is for terminals that don't send modifiers at all.
2024-12-12 16:57:16 +01:00

380 lines
15 KiB
Zig

const std = @import("std");
const input = @import("input");
fn peek(str: []const u8, i: usize) error{OutOfBounds}!u8 {
if (i + 1 < str.len) {
return str[i + 1];
} else return error.OutOfBounds;
}
pub const ParseError = error{
OutOfMemory,
OutOfBounds,
InvalidEscapeSequenceStart,
InvalidInitialCharacter,
InvalidStartOfControlBinding,
InvalidStartOfShiftBinding,
InvalidStartOfDelBinding,
InvalidStartOfEscBinding,
InvalidStartOfHomeBinding,
InvalidCRBinding,
InvalidSpaceBinding,
InvalidDelBinding,
InvalidTabBinding,
InvalidUpBinding,
InvalidEscapeBinding,
InvalidDownBinding,
InvalidLeftBinding,
InvalidRightBinding,
InvalidFunctionKeyNumber,
InvalidFunctionKeyBinding,
InvalidEscapeSequenceDelimiter,
InvalidModifier,
InvalidEscapeSequenceEnd,
InvalidHomeBinding,
InvalidEndBinding,
InvalidBSBinding,
InvalidInsertBinding,
};
var parse_error_buf: [256]u8 = undefined;
pub var parse_error_message: []const u8 = "";
fn parse_error_reset() void {
parse_error_message = "";
}
fn parse_error(e: ParseError, comptime format: anytype, args: anytype) ParseError {
parse_error_message = std.fmt.bufPrint(&parse_error_buf, format, args) catch "error in parse_error";
return e;
}
pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: []const u8) ParseError![]input.KeyEvent {
parse_error_reset();
const State = enum {
base,
escape_sequence_start,
escape_sequence_delimiter,
char_or_key_or_modifier,
modifier,
escape_sequence_end,
function_key,
tab,
space,
del,
cr,
esc,
up,
down,
left,
right,
home,
end,
insert,
bs,
};
var state: State = .base;
var function_key_number: u8 = 0;
var modifiers: input.Mods = 0;
var result = std.ArrayList(input.KeyEvent).init(allocator);
defer result.deinit();
var i: usize = 0;
while (i < str.len) {
switch (state) {
// zig fmt: off
.base => {
switch (str[i]) {
'<' => {
state = .escape_sequence_start;
i += 1;
},
//lowercase characters
'a'...'z',
'0'...'9',
'`', '-', '=', '[', ']', '\\', ':', ';', '\'', ',', '.', '/', => {
try result.append(.{ .key = str[i] });
i += 1;
},
//uppercase letters also allowed here
'A'...'Z', => {
try result.append(.{ .key = std.ascii.toLower(str[i]), .modifiers = input.mod.shift});
i += 1;
},
else => return parse_error(error.InvalidInitialCharacter, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
// zig fmt: on
.escape_sequence_start => {
switch (str[i]) {
'A' => {
state = .modifier;
},
'C' => {
switch (try peek(str, i)) {
'R' => {
state = .cr;
},
'-' => {
state = .modifier;
},
else => return parse_error(
error.InvalidStartOfControlBinding,
"str: {s}, i: {} c: {c}",
.{ str, i, str[i] },
),
}
},
'S' => {
switch (try peek(str, i)) {
'-' => {
state = .modifier;
},
'p' => {
state = .space;
},
else => return parse_error(
error.InvalidStartOfShiftBinding,
"str: {s}, i: {} c: {c}",
.{ str, i, str[i] },
),
}
},
'F' => {
state = .function_key;
i += 1;
},
'T' => {
state = .tab;
},
'U' => {
state = .up;
},
'L' => {
state = .left;
},
'R' => {
state = .right;
},
'I' => {
state = .insert;
},
'B' => {
state = .bs;
},
'E' => {
state = switch (try peek(str, i)) {
's' => .esc,
'n' => .end,
else => return parse_error(
error.InvalidStartOfEscBinding,
"str: {s}, i: {} c: {c}",
.{ str, i, str[i] },
),
};
},
'D' => {
switch (try peek(str, i)) {
'o' => {
state = .down;
},
'-' => {
state = .modifier;
},
'e' => {
state = .del;
},
else => return parse_error(
error.InvalidStartOfDelBinding,
"str: {s}, i: {} c: {c}",
.{ str, i, str[i] },
),
}
},
'H' => {
state = .home;
},
else => return parse_error(
error.InvalidStartOfHomeBinding,
"str: {s}, i: {} c: {c}",
.{ str, i, str[i] },
),
}
},
.insert => {
if (std.mem.indexOf(u8, str[i..], "Insert") == 0) {
try result.append(.{ .key = input.key.insert, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 4;
} else return parse_error(error.InvalidInsertBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.end => {
if (std.mem.indexOf(u8, str[i..], "End") == 0) {
try result.append(.{ .key = input.key.end, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 3;
} else return parse_error(error.InvalidEndBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.home => {
if (std.mem.indexOf(u8, str[i..], "Home") == 0) {
try result.append(.{ .key = input.key.home, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 4;
} else return parse_error(error.InvalidHomeBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.bs => {
if (std.mem.indexOf(u8, str[i..], "BS") == 0) {
try result.append(.{ .key = input.key.backspace, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 2;
} else return parse_error(error.InvalidBSBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.cr => {
if (std.mem.indexOf(u8, str[i..], "CR") == 0) {
try result.append(.{ .event = event, .key = input.key.enter, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 2;
} else return parse_error(error.InvalidCRBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.space => {
if (std.mem.indexOf(u8, str[i..], "Space") == 0) {
try result.append(.{ .event = event, .key = input.key.space, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 5;
} else return parse_error(error.InvalidSpaceBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.del => {
if (std.mem.indexOf(u8, str[i..], "Del") == 0) {
try result.append(.{ .event = event, .key = input.key.delete, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 3;
} else return parse_error(error.InvalidDelBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.tab => {
if (std.mem.indexOf(u8, str[i..], "Tab") == 0) {
try result.append(.{ .event = event, .key = input.key.tab, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 3;
} else return parse_error(error.InvalidTabBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.up => {
if (std.mem.indexOf(u8, str[i..], "Up") == 0) {
try result.append(.{ .event = event, .key = input.key.up, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 2;
} else return parse_error(error.InvalidUpBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.esc => {
if (std.mem.indexOf(u8, str[i..], "Esc") == 0) {
try result.append(.{ .event = event, .key = input.key.escape, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 3;
} else return parse_error(error.InvalidEscapeBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.down => {
if (std.mem.indexOf(u8, str[i..], "Down") == 0) {
try result.append(.{ .event = event, .key = input.key.down, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 4;
} else return parse_error(error.InvalidDownBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.left => {
if (std.mem.indexOf(u8, str[i..], "Left") == 0) {
try result.append(.{ .event = event, .key = input.key.left, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 4;
} else return parse_error(error.InvalidLeftBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.right => {
if (std.mem.indexOf(u8, str[i..], "Right") == 0) {
try result.append(.{ .event = event, .key = input.key.right, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 5;
} else return parse_error(error.InvalidRightBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.function_key => {
switch (str[i]) {
'0'...'9' => {
function_key_number *= 10;
function_key_number += str[i] - '0';
if (function_key_number < 1 or function_key_number > 35)
return parse_error(error.InvalidFunctionKeyNumber, "function_key_number: {}", .{function_key_number});
i += 1;
},
'>' => {
const function_key = input.key.f1 - 1 + function_key_number;
try result.append(.{ .event = event, .key = function_key, .modifiers = modifiers });
modifiers = 0;
function_key_number = 0;
state = .base;
i += 1;
},
else => return parse_error(error.InvalidFunctionKeyBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
.escape_sequence_delimiter => {
switch (str[i]) {
'-' => {
state = .char_or_key_or_modifier;
i += 1;
},
else => return parse_error(error.InvalidEscapeSequenceDelimiter, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
// zig fmt: off
.char_or_key_or_modifier => {
switch (str[i]) {
//lowercase characters only inside the escape sequence
'a'...'z',
'0'...'9',
'`', '-', '=', '[', ']', '\\', ':', ';', '\'', ',', '.', '/',
=> {
try result.append(.{ .key = str[i], .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 1;
},
else => {
state = .escape_sequence_start;
},
}
},
// zig fmt: on
.modifier => {
modifiers |= switch (str[i]) {
'A' => input.mod.alt,
'C' => input.mod.ctrl,
'D' => input.mod.super,
'S' => input.mod.shift,
else => return parse_error(error.InvalidModifier, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
};
state = .escape_sequence_delimiter;
i += 1;
},
.escape_sequence_end => {
switch (str[i]) {
'>' => {
state = .base;
i += 1;
},
else => return parse_error(error.InvalidEscapeSequenceEnd, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
}
}
return result.toOwnedSlice();
}