diff --git a/KEYMAP.md b/KEYMAP.md index 457f674..24ace3f 100644 --- a/KEYMAP.md +++ b/KEYMAP.md @@ -327,6 +327,7 @@ Tool operations are first-class commands, not hidden side effects. | --- | --- | | `Space l` `h` | hover: compact card first, expandable panel for full text | | `Space l` `s` | signature help: active parameter first, expandable panel for overloads/docs | +| `Space l` `n` / `Space l` `p` | next/previous parameter or argument placeholder; `g a`/`g A` are attached-keyboard accelerators for the same intent | | `Space l` `a` | code actions for cursor/selection/diagnostic | | `Space l` `r` | rename | | `Space l` `o` | organize imports | @@ -368,8 +369,10 @@ editor space, and they should not make diagnostics or signature context unreachable. Signature help follows the same rule: show the active overload and parameter -first; move between parameters with the parameter navigation grammar; expand only -when the user asks to read all overloads or full docs. +first; move between parameters with the language rail (`Space l n`/`Space l p`) +so hover, signature, and argument navigation are discoverable together. The Go +rail keeps `g a`/`g A` as attached-keyboard accelerators for the same movement. +Expand only when the user asks to read all overloads or full docs. ### Multiple providers diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 3b7730a..fa9dd21 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -29,6 +29,7 @@ Rows are redgate TSV requirements: `ringidsummary [tag]`. 1 016 Every attached-keyboard or Ctrl/Esc accelerator that performs a core editing, navigation, recovery, or panel action SHALL have a documented visible mobile fallback or rail path. [mobile] 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] ## ui diff --git a/src/leader.zig b/src/leader.zig index 1e0e719..b4cfd19 100644 --- a/src/leader.zig +++ b/src/leader.zig @@ -36,6 +36,8 @@ pub const Action = union(enum) { language_format_policy, language_organize_imports, language_code_actions, + language_parameter_next, + language_parameter_previous, diagnostics_open, diagnostics_next, diagnostics_previous, @@ -106,7 +108,7 @@ pub const Leader = struct { .rail => "leader: w save q quit Q discard f files P Pi o open p symbols s search r repeat", .symbol_rail => symbol_mod.rail_status, .search_rail => "search: f current file p project s symbols", - .language_rail => "language: h hover s sig f fmt o imports a actions (Space path)", + .language_rail => "language: h hover s sig n/p param f fmt o imports a actions", .diagnostic_rail => "diagnostics: d open n/p next/prev f source (arrows/Enter ok)", .tool_rail => "tools: l/L lint b build t test c check x cancel j jump y yank", .open_prompt => "open: type path, Enter opens, Esc cancels", @@ -239,6 +241,8 @@ pub const Leader = struct { if (std.mem.eql(u8, text, "F")) return .language_format_policy; if (std.mem.eql(u8, text, "w")) return .language_format_policy; if (std.mem.eql(u8, text, "a")) return .language_code_actions; + if (std.mem.eql(u8, text, "n")) return .language_parameter_next; + if (std.mem.eql(u8, text, "p")) return .language_parameter_previous; self.message = "language action is not built in this profile yet"; return .{ .not_built = .lsp }; }, @@ -594,3 +598,27 @@ test "regular: leader exposes files and Pi panels" { try expectActionTag(.none, try leader.handleEvent(input.normalize(" "))); try expectActionTag(.pi_panel, try leader.handleEvent(input.normalize("P"))); } + +test "regular: language rail groups hover signature and parameter navigation" { + var leader = Leader.init(std.testing.allocator); + defer leader.deinit(); + + try expectActionTag(.none, try leader.handleEvent(input.normalize(" "))); + try expectActionTag(.none, try leader.handleEvent(input.normalize("l"))); + try std.testing.expect(std.mem.indexOf(u8, leader.status(), "h hover") != null); + try std.testing.expect(std.mem.indexOf(u8, leader.status(), "s sig") != null); + try std.testing.expect(std.mem.indexOf(u8, leader.status(), "n/p param") != null); + try expectActionTag(.hover, try leader.handleEvent(input.normalize("h"))); + + try expectActionTag(.none, try leader.handleEvent(input.normalize(" "))); + try expectActionTag(.none, try leader.handleEvent(input.normalize("l"))); + try expectActionTag(.signature, try leader.handleEvent(input.normalize("s"))); + + try expectActionTag(.none, try leader.handleEvent(input.normalize(" "))); + try expectActionTag(.none, try leader.handleEvent(input.normalize("l"))); + try expectActionTag(.language_parameter_next, try leader.handleEvent(input.normalize("n"))); + + try expectActionTag(.none, try leader.handleEvent(input.normalize(" "))); + try expectActionTag(.none, try leader.handleEvent(input.normalize("l"))); + try expectActionTag(.language_parameter_previous, try leader.handleEvent(input.normalize("p"))); +} diff --git a/src/tui.zig b/src/tui.zig index 7d9b15a..cd28371 100644 --- a/src/tui.zig +++ b/src/tui.zig @@ -2477,6 +2477,8 @@ pub const Client = struct { .language_format_policy => try self.showFormatPolicy(), .language_organize_imports => try self.organizeImports(), .language_code_actions => try self.openCodeActions(), + .language_parameter_next => try self.moveLspParameter(.next), + .language_parameter_previous => try self.moveLspParameter(.previous), .diagnostics_open => try self.openDiagnosticsPanel(), .diagnostics_next => try self.gotoDiagnostic(.next), .diagnostics_previous => try self.gotoDiagnostic(.previous), diff --git a/tools/terminal_e2e.py b/tools/terminal_e2e.py index 50153c0..4fd4e64 100755 --- a/tools/terminal_e2e.py +++ b/tools/terminal_e2e.py @@ -558,7 +558,7 @@ SCENARIOS = [ Scenario("vertical-motion-preferred-column", 56, 12, "ragged", "attached-keyboard", "motion", "truecolor", "file", setup_ragged, (b"L", b"j", b"j", b"i", b"X", b"\x1b", b" ", b"w", b" ", b"q"), "abcdef\nxy\n123456X", ("1│", "3│", "☻", "mode:normal")), Scenario("document-half-page-motions", 56, 10, "tall", "attached-keyboard", "motion", "truecolor", "file", setup_tall, (b"G", b"i", b"X", b"\x1b", b"g", b"g", b"\x04", b"i", b"Y", b"\x1b", b"G", b"g", b"u", b"i", b"Z", b"\x1b", b" ", b"w", b" ", b"q"), "l1\nl2\nl3\nl4\nl5\nZl6\nl7\nl8\nl9\nl10\nYl11\nl12\nl13\nl14\nl15\nXl16", ("11│", "16│", "☻", "mode:normal")), 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"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", "diag:fresh:zls", "format:zls:applied", "ZLS", "mode:normal"), max_key_events=19, prelude=LSP_ASSIST_PRELUDE), + 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),