diff --git a/KEYMAP.md b/KEYMAP.md index 3b1ee6b..e193b1b 100644 --- a/KEYMAP.md +++ b/KEYMAP.md @@ -208,9 +208,8 @@ such as arrow keys, Home/End, PageUp/PageDown, Escape, or `%`. | `s` | enter Select mode | | `y` then motion/object | yank/copy range | | `p` | paste/put after cursor or replace active selection | -| `Space i` | symbol insertion rail for coding punctuation | -| `Space i` `p` | insert paired delimiter, then place cursor inside | -| `Space i` `q` | insert paired quotes, then place cursor inside | +| `Space p` | symbol insertion rail for coding punctuation: `p` `()`, `b` `[]`, `c` `{}`, `s` `/`, `f` `//`, `x` `*`, `a` `&`, `h` `#`, `d` `$`, `r` `@`, `v` `|`, `q` quotes, `e` single quotes, `t` backticks, `u` underscore | +| `Space p` `p` / `b` / `c` / `q` / `e` / `t` | insert paired delimiters or quotes, then place cursor inside | Delete/change/yank all accept the same object grammar. This keeps operations orthogonal: verbs decide what happens; objects decide what range is affected. diff --git a/src/symbol.zig b/src/symbol.zig index 46b4f69..5a258e0 100644 --- a/src/symbol.zig +++ b/src/symbol.zig @@ -12,6 +12,12 @@ pub const Symbol = enum { brackets, braces, slash, + line_comment, + star, + ampersand, + hash, + dollar, + at, pipe, double_quote, single_quote, @@ -31,6 +37,12 @@ pub const entries = [_]Entry{ .{ .key = "b", .symbol = .brackets, .label = "[]", .protocol_command = "command pair brackets" }, .{ .key = "c", .symbol = .braces, .label = "{}", .protocol_command = "command pair braces" }, .{ .key = "s", .symbol = .slash, .label = "/", .protocol_command = "command insert /" }, + .{ .key = "f", .symbol = .line_comment, .label = "//", .protocol_command = "command insert //" }, + .{ .key = "x", .symbol = .star, .label = "*", .protocol_command = "command insert *" }, + .{ .key = "a", .symbol = .ampersand, .label = "&", .protocol_command = "command insert &" }, + .{ .key = "h", .symbol = .hash, .label = "#", .protocol_command = "command insert #" }, + .{ .key = "d", .symbol = .dollar, .label = "$", .protocol_command = "command insert $" }, + .{ .key = "r", .symbol = .at, .label = "@", .protocol_command = "command insert @" }, .{ .key = "v", .symbol = .pipe, .label = "|", .protocol_command = "command insert |" }, .{ .key = "q", .symbol = .double_quote, .label = "\"\"", .protocol_command = "command pair double_quote" }, .{ .key = "e", .symbol = .single_quote, .label = "''", .protocol_command = "command pair single_quote" }, @@ -38,7 +50,7 @@ pub const entries = [_]Entry{ .{ .key = "u", .symbol = .underscore, .label = "_", .protocol_command = "command insert _" }, }; -pub const rail_status = "symbols: p () b [] c {} s / v | q \"\" e '' t `` u _"; +pub const rail_status = "symbols: p () b [] c {} s / f // x * a & h # d $ r @ v | q \"\" e '' t `` u _"; pub fn lookup(key: []const u8) ?Entry { for (entries) |entry| { @@ -59,6 +71,12 @@ test "regular: symbol rail maps mobile-friendly letters to coding punctuation" { try std.testing.expectEqual(Symbol.brackets, lookup("b").?.symbol); try std.testing.expectEqual(Symbol.braces, lookup("c").?.symbol); try std.testing.expectEqual(Symbol.slash, lookup("s").?.symbol); + try std.testing.expectEqual(Symbol.line_comment, lookup("f").?.symbol); + try std.testing.expectEqual(Symbol.star, lookup("x").?.symbol); + try std.testing.expectEqual(Symbol.ampersand, lookup("a").?.symbol); + try std.testing.expectEqual(Symbol.hash, lookup("h").?.symbol); + try std.testing.expectEqual(Symbol.dollar, lookup("d").?.symbol); + try std.testing.expectEqual(Symbol.at, lookup("r").?.symbol); try std.testing.expectEqual(Symbol.pipe, lookup("v").?.symbol); try std.testing.expectEqual(Symbol.double_quote, lookup("q").?.symbol); try std.testing.expectEqual(Symbol.single_quote, lookup("e").?.symbol); @@ -71,6 +89,12 @@ test "regular: symbol rail protocol commands stay tiny and explicit" { try std.testing.expectEqualStrings("command pair brackets", protocolCommand(.brackets)); try std.testing.expectEqualStrings("command pair braces", protocolCommand(.braces)); try std.testing.expectEqualStrings("command insert /", protocolCommand(.slash)); + try std.testing.expectEqualStrings("command insert //", protocolCommand(.line_comment)); + try std.testing.expectEqualStrings("command insert *", protocolCommand(.star)); + try std.testing.expectEqualStrings("command insert &", protocolCommand(.ampersand)); + try std.testing.expectEqualStrings("command insert #", protocolCommand(.hash)); + try std.testing.expectEqualStrings("command insert $", protocolCommand(.dollar)); + try std.testing.expectEqualStrings("command insert @", protocolCommand(.at)); try std.testing.expectEqualStrings("command insert |", protocolCommand(.pipe)); try std.testing.expectEqualStrings("command pair double_quote", protocolCommand(.double_quote)); try std.testing.expectEqualStrings("command pair single_quote", protocolCommand(.single_quote)); @@ -79,7 +103,7 @@ test "regular: symbol rail protocol commands stay tiny and explicit" { } test "adversarial: unknown symbol key does not map to a symbol" { - try std.testing.expectEqual(@as(?Entry, null), lookup("x")); + try std.testing.expectEqual(@as(?Entry, null), lookup("z")); try std.testing.expectEqual(@as(?Entry, null), lookup("/")); try std.testing.expectEqual(@as(?Entry, null), lookup("")); } diff --git a/tools/terminal_e2e.py b/tools/terminal_e2e.py index 9cdf270..558a825 100755 --- a/tools/terminal_e2e.py +++ b/tools/terminal_e2e.py @@ -507,6 +507,7 @@ IOS_DEFAULT_KEYS = set(b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ") SCENARIOS = [ Scenario("raw-key-chrome-crlf", 48, 16, "empty", "ios-default-qwertz-space-path", "normal-symbol-save", "truecolor", "file", setup_empty, MOBILE_SYMBOL_SAVE_QUIT, "/", ("1│", "☻", "mode:normal"), max_key_events=7), + Scenario("coding-symbol-rail", 56, 12, "empty", "ios-default-qwertz-space-path", "normal-symbols", "truecolor", "file", setup_empty, (b" ", b"p", b"f", b" ", b"p", b"x", b" ", b"p", b"a", b" ", b"p", b"h", b" ", b"p", b"d", b" ", b"p", b"r", b" ", b"w", b" ", b"q"), "//*&#$@", ("symbols:", "mode:normal", "☻"), max_key_events=24), Scenario("ios-normal-replace-shift-path", 44, 12, "existing", "ios-default-qwertz-space-path", "normal-replace", "truecolor", "file", setup_existing, MOBILE_REPLACE_SAVE_QUIT, "xlpha\nbeta", ("1│", "☻", "mode:normal")), Scenario("attached-existing-medium", 72, 18, "existing", "attached-keyboard", "insert-normal", "truecolor", "file", setup_existing, ATTACHED_SAVE_QUIT, "alpha!\nbeta", ("1│", "2│", "☻"), max_key_events=8), Scenario("new-file-mono-narrow", 40, 12, "new", "ios-default-qwertz-space-path", "normal-symbol-save", "mono", "file", setup_new, MOBILE_SYMBOL_SAVE_QUIT, "/", ("1│", "☻", "mode:normal")),