diff --git a/KEYMAP.md b/KEYMAP.md index 24ace3f..4788762 100644 --- a/KEYMAP.md +++ b/KEYMAP.md @@ -295,17 +295,16 @@ Opening a result returns to the editor without leaving behind permanent splits. | Keys | Surface | | --- | --- | | `Space f` | files panel / file tree / finder / recent | -| `Space P` | Pi/local trusted tool panel | +| `Space a` or `Space P` | Pi/local trusted assistant/tool panel (`a` is the no-shift mobile path; `P` is the attached-keyboard mnemonic) | | `Space s` | search: file/project/symbol | | `Space d` | diagnostics | -| `Space g` | go/navigation | +| `g` rail | go/navigation (`gg`, `G`, `g u/d`, `g a/A`, diagnostics) | | `Space l` | language/LSP tools | -| `Space t` | tasks: build/test/check/lint jobs | +| `Space j` or `Space t` | jobs/tasks: build/test/check/lint output and cancellation (`j` is the no-shift mobile path; `t` remains the task mnemonic) | | `Space b` | buffers/build output depending on rail label | | `Space v` | version control/git workbench | | `Space !` | terminal escape hatch/job command | -| `Space p` | coding symbol rail | -| `Space P` | Pi/local trusted tool panel | +| `Space y` or `Space p` | coding symbol rail (`y` is the no-shift alternate kept free for symbols; `p` is the historical/programming mnemonic) | Panel controls are consistent: @@ -334,11 +333,11 @@ Tool operations are first-class commands, not hidden side effects. | `Space l` `f` | format current buffer | | `Space l` `F` or `Space l` `w` | toggle/show format-on-save policy for this source build | | `Space l` `d` | show provider details for symbol/diagnostic under cursor | -| `Space t` `l` | lint current file | -| `Space t` `L` | lint project/workspace | -| `Space t` `b` | build | -| `Space t` `t` | test | -| `Space t` `c` | check/typecheck | +| `Space j`/`Space t` `l` | lint current file | +| `Space j`/`Space t` `L` | lint project/workspace | +| `Space j`/`Space t` `b` | build | +| `Space j`/`Space t` `t` | test | +| `Space j`/`Space t` `c` | check/typecheck | | `Space d` | diagnostics panel | | `Space d` `n` | next diagnostic | | `Space d` `p` | previous diagnostic | diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index fa9dd21..9b2a4e4 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -30,6 +30,7 @@ Rows are redgate TSV requirements: `ringidsummary [tag]`. 1 017 Insert and Select mode SHALL return to Normal via Escape and Ctrl-[ as equivalent terminal events while preserving literal Insert-mode Space. [mobile] 1 018 Select mode SHALL keep bare movement keys as selection-extending motions and provide a non-conflicting mobile object-selection rail for word, line, indent, parameter, enclosing form, and diagnostic ranges. [mobile] 1 019 Hover, signature help, and parameter navigation SHALL share a discoverable language rail, with any attached-keyboard accelerator documented as an alias rather than the only path. [mobile] +1 020 Files, search, jobs, Pi/assistant, and symbol panels SHALL have documented top-rail mobile paths, with shifted or mnemonic aliases treated as accelerators rather than the only discoverable path. [mobile] ## ui diff --git a/src/leader.zig b/src/leader.zig index b4cfd19..a97ca12 100644 --- a/src/leader.zig +++ b/src/leader.zig @@ -105,7 +105,7 @@ pub const Leader = struct { if (self.message) |message| return message; return switch (self.mode) { .idle => "", - .rail => "leader: w save q quit Q discard f files P Pi o open p symbols s search r repeat", + .rail => "leader: w save q quit f files s search j jobs a/P Pi p/y symbols", .symbol_rail => symbol_mod.rail_status, .search_rail => "search: f current file p project s symbols", .language_rail => "language: h hover s sig n/p param f fmt o imports a actions", @@ -163,7 +163,7 @@ pub const Leader = struct { self.mode = .diagnostic_rail; return .diagnostics_open; } - if (std.mem.eql(u8, text, "t")) { + if (std.mem.eql(u8, text, "t") or std.mem.eql(u8, text, "j")) { self.mode = .tool_rail; return .none; } @@ -184,11 +184,11 @@ pub const Leader = struct { self.mode = .idle; return .file_picker; } - if (std.mem.eql(u8, text, "P")) { + if (std.mem.eql(u8, text, "P") or std.mem.eql(u8, text, "a")) { self.mode = .idle; return .pi_panel; } - if (std.mem.eql(u8, text, "p")) { + if (std.mem.eql(u8, text, "p") or std.mem.eql(u8, text, "y")) { self.mode = .symbol_rail; return .none; } @@ -457,7 +457,7 @@ test "regular: space opens a visible leader rail and write dispatches" { defer leader.deinit(); try expectActionTag(.none, try leader.handleEvent(input.normalize(" "))); - try std.testing.expectEqualStrings("leader: w save q quit Q discard f files P Pi o open p symbols s search r repeat", leader.status()); + try std.testing.expectEqualStrings("leader: w save q quit f files s search j jobs a/P Pi p/y symbols", leader.status()); try expectActionTag(.save, try leader.handleEvent(input.normalize("w"))); try std.testing.expect(!leader.isActive()); @@ -622,3 +622,25 @@ test "regular: language rail groups hover signature and parameter navigation" { try expectActionTag(.none, try leader.handleEvent(input.normalize("l"))); try expectActionTag(.language_parameter_previous, try leader.handleEvent(input.normalize("p"))); } + +test "regular: top rail exposes unshifted panel aliases" { + var leader = Leader.init(std.testing.allocator); + defer leader.deinit(); + + try expectActionTag(.none, try leader.handleEvent(input.normalize(" "))); + try std.testing.expect(std.mem.indexOf(u8, leader.status(), "f files") != null); + try std.testing.expect(std.mem.indexOf(u8, leader.status(), "s search") != null); + try std.testing.expect(std.mem.indexOf(u8, leader.status(), "j jobs") != null); + try std.testing.expect(std.mem.indexOf(u8, leader.status(), "a/P Pi") != null); + try std.testing.expect(std.mem.indexOf(u8, leader.status(), "p/y symbols") != null); + try expectActionTag(.pi_panel, try leader.handleEvent(input.normalize("a"))); + + try expectActionTag(.none, try leader.handleEvent(input.normalize(" "))); + try expectActionTag(.none, try leader.handleEvent(input.normalize("j"))); + try std.testing.expect(std.mem.indexOf(u8, leader.status(), "tools:") != null); + try expectActionTag(.job_build, try leader.handleEvent(input.normalize("b"))); + + try expectActionTag(.none, try leader.handleEvent(input.normalize(" "))); + try expectActionTag(.none, try leader.handleEvent(input.normalize("y"))); + try std.testing.expect(std.mem.indexOf(u8, leader.status(), "symbols:") != null); +} diff --git a/src/tui.zig b/src/tui.zig index cd28371..6859467 100644 --- a/src/tui.zig +++ b/src/tui.zig @@ -5278,7 +5278,8 @@ test "adversarial: unknown physical key names stay rejected and leader help stay defer client.deinit(); try std.testing.expectError(Error.UnknownTraceEvent, client.handleTraceLine("key f1")); try client.handleInput(" "); - try std.testing.expect(std.mem.indexOf(u8, client.leader.status(), "Q discard") != null); + try std.testing.expect(std.mem.indexOf(u8, client.leader.status(), "j jobs") != null); + try std.testing.expect(std.mem.indexOf(u8, client.leader.status(), "a/P Pi") != null); const frame = try client.render(std.testing.allocator); defer std.testing.allocator.free(frame); try assertLinesFit(frame, 72); diff --git a/tools/terminal_e2e.py b/tools/terminal_e2e.py index 4fd4e64..cedca46 100755 --- a/tools/terminal_e2e.py +++ b/tools/terminal_e2e.py @@ -560,8 +560,8 @@ SCENARIOS = [ Scenario("multi-file-coding-loop", 72, 16, "repo-multifile", "attached-keyboard", "multi-file", "truecolor", "file", setup_multifile, (b"A", b"?", b"\x1b", b" ", b"w", b" ", b"o", b"r", b"e", b"p", b"o", b"/", b"a", b"l", b"p", b"h", b"a", b".", b"z", b"i", b"g", b"\r", b"A", b"!", b"\x1b", b" ", b"w", b" ", b"q"), None, ("alpha.zig", "beta", "☻", "mode:normal"), max_key_events=30, expect_files=(("alpha.zig", "alpha!\n"), ("beta.zig", "beta?"))), Scenario("lsp-assisted-coding", 60, 12, "lsp-fixture", "attached-keyboard", "lsp", "truecolor", "file", setup_lsp_assist, (b" ", b"l", b"h", b" ", b"l", b"s", b" ", b"l", b"n", b"q", b" ", b"l", b"p", b"q", b" ", b"d", b"q", b" ", b"d", b"n", b" ", b"l", b"f", b" ", b"w", b" ", b"q"), "ZLS", ("[zls] add(lhs, rhs)", "[zls] add(lhs: i32, rhs: i32) active=rhs", "panel [lsp-parameter]", "diag:fresh:zls", "format:zls:applied", "ZLS", "mode:normal"), max_key_events=27, prelude=LSP_ASSIST_PRELUDE), Scenario("project-search-panel", 60, 12, "repo-search", "attached-keyboard", "project-search", "truecolor", "file", setup_project_search, (b" ", b"s", b"p", b"f", b"i", b"n", b"d", b"m", b"e", b"\r", b"q", b" ", b"q"), "main\n", ("panel [search]", "target.zig", "findme", "mode:normal"), max_key_events=13), - Scenario("file-and-pi-panels", 60, 12, "repo-multifile", "attached-keyboard", "panels", "truecolor", "file", setup_multifile, (b" ", b"f", b"q", b" ", b"P", b"q", b" ", b"q"), "beta\n", ("panel [files]", "alpha.zig", "panel [pi]", "pi_ask:explain_current_buffer", "mode:normal"), required_attrs=("panel-header", "current-line"), max_key_events=8), - Scenario("job-cancel-output-panel", 60, 12, "job", "attached-keyboard", "job-output", "truecolor", "file", setup_empty, (b" ", b"t", b"x", b"q", b" ", b"q"), "", ("panel [job-output]", "job:profile:build", "job:status:cancelled:user", "mode:normal"), max_key_events=6), + Scenario("file-and-pi-panels", 60, 12, "repo-multifile", "attached-keyboard", "panels", "truecolor", "file", setup_multifile, (b" ", b"f", b"q", b" ", b"a", b"q", b" ", b"q"), "beta\n", ("panel [files]", "alpha.zig", "panel [pi]", "pi_ask:explain_current_buffer", "mode:normal"), required_attrs=("panel-header", "current-line"), max_key_events=8), + Scenario("job-cancel-output-panel", 60, 12, "job", "attached-keyboard", "job-output", "truecolor", "file", setup_empty, (b" ", b"j", b"x", b"q", b" ", b"q"), "", ("panel [job-output]", "job:profile:build", "job:status:cancelled:user", "mode:normal"), max_key_events=6), Scenario("dirty-discard-shift-q", 52, 12, "empty", "ios-default-qwertz-space-path", "dirty-discard", "truecolor", "file", setup_empty, tuple(bytes([b]) for b in b" ps Q"), "", ("1│", "☻")), Scenario("directory-panel-narrow", 52, 12, "directory", "ios-default-qwertz-space-path", "panel", "mono", "directory", setup_directory, PANEL_QUIT, None, ("file", "one.zig"), required_attrs=()), ]