feat(go): answer what could go where the cursor is - #984
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Central YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThe PR adds Go APIs that derive completion positions and candidates from parser state, with focused fixes ensuring completion visibility and flag ownership match parser behavior.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Reviews (8): Last reviewed commit: "fix(go): a variadic still collecting is ..." | Re-trigger Greptile |
Instruction countsNothing was compared, and so nothing was gated. No series appears on both sides: either the base has no measurements recorded, or the two were measured on different runner classes, which are deliberately not comparable — counts shift between machine types by more than a real regression does. New, nothing to compare against: Only instruction counts gate. Wall clock is shown for context — on identical hardware it moves 4-20% run to run. Measured by tak — instruction-counted CLI benchmarks, stored in this repository's git notes. Shadow comparisonParsing
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit cf338af. Configure here.
`Walk` reads the words before the cursor and reports what it is standing in; `Candidates` says what could go there — subcommands and their aliases, the flags in scope, and the values a `choices` list allows. Both ask the parser rather than re-deriving its rules, and that is the whole design. A completion advertising a flag the parser would refuse is worse than no completion, so the scope a candidate comes from is the scope a token would be resolved in: a global is offered inside a subcommand, a subcommand redeclaring an inherited name offers its own, a hidden flag binds without being advertised, and past a `--` nothing is offered because there is no flag of this CLI to type there. Errors are not failures here. A line being completed is unfinished by definition, so a parse error means "the grammar runs out here" — which is the position being asked about. `missing_flag_value` says the cursor is standing in a flag's value, and the choices for that flag take the position entirely. `help` says the cursor is naming a command to read about, where nothing else belongs. Not included, and listed in the README: turning candidates into the text each shell expects, and running the `complete` scripts a spec can declare. The first is per-shell formatting and the second runs subprocesses; neither belongs in a package whose claim is that a parse does not allocate. One note from writing the tests. The first fixture numbered its keys 10 to 14 in an eight-entry table, so every cold-table lookup missed and the assertions about hiding and choices passed for the wrong reason — the tests only started failing once the keys were dense. The invariant is documented on `Metadata` and it is worth knowing that breaking it fails quietly rather than loudly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four findings on the completion candidates, all of them the same failure in different places: advertising something the parser refuses, which is precisely what this design was meant to rule out. A hidden command was offered. Flags were filtered by `hide` and commands were not, so `hide` kept a command off its parent's help page while tab-completion listed it — including after `help`, where it would have been most discoverable. A help topic offered no aliases. `findNamed` resolves a topic by name or alias exactly as it resolves a command to run, so hiding them there made accepted spellings undiscoverable in the one place someone is looking for a name. An argument that reads only after a `--` had its choices offered before one. Those words come back as `arg_requires_double_dash` — a completion producing a command line the grammar refuses, which is the failure mode the whole approach exists to avoid. It waits for the separator now. And an inherited global was dropped whole when a nearer flag took one of its spellings. A flag answers to several, and a subcommand reclaiming `--jobs` leaves an inherited `-j` and `--workers` binding; withdrawing all three hid two the parser still accepts. Masking is per spelling now, which is the same rule the help pages follow and for the same reason. A spelling is claimed whether or not anything is left of the flag, so something farther away cannot pick it up either. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…elling `Candidates` appended a flag's negation whatever else was in scope, and `flagsInScope` dropped a flag whose primary forms were all taken. Both are the same mistake from opposite sides: a negation was not treated as a spelling. An ancestor's literal `--no-color` beats a nearer flag's negation, because the parser asks for every long across the whole scope before it asks for any negation — so the word was offered twice, described two different ways, one of them wrong. And a nearer command reclaiming `--color` left the inherited flag with nothing but its negation, which still binds and was never offered. The pages already had this rule. It now lives in one place and both halves call it, because a spelling offered by one and not the other is the two of them disagreeing about what the parser does. The tests ask the parser which flag binds the word before asserting who offers it: the claim here is that what is offered is what would be accepted, and only the parser can answer the second half. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… spelling once Two more places where the completion advertised something the parser would not take. Descent stops once a positional of the command has taken a word — `word()` asks `!argFilled && !flagsStopped` before it looks for a subcommand — so after that a name matching a subcommand is a value or a failure. `Position` carried the flag half of that rule and nothing for the command half, so the names were still offered. The parser now says so directly, and the test asks it whether the word still binds before asserting that it is not offered. And a flag may spell its negation the same as its own long form: `flag "--no-color" negate="--no-color"`. That parses, and it was offered twice. usage-lib prints such a flag as `--no-color / --no-color`, so the page saying it twice is the reference's own behaviour and stays; a completion is a list of things to type, where the same thing twice is a repeated row. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…hat is owed Two positions were being treated as one. A flag that has not been given its value refuses a flag-like token, so nothing but its values belongs there. A variadic that already has one *stops* collecting when it meets a flag, and that flag binds — so `--tools a ⌶` was offering the tool names and hiding every flag that works there. Offered now: the variadic's own values, and the flags. Not a subcommand name or the positional behind it, because a plain word would be collected rather than bound. And the exemption that lets a flag spelled `--x` still offer a negation spelled `--x` was reaching past a nearer flag that had claimed the word. The parser binds `--x` to the child; the inherited global was offering it a second time. Both tests ask the parser what binds before asserting who offers it, and both fail on the code above them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`RenderAnswer` turns candidates into the protocol a shell's completion machinery expects. Stacked on #984. | shell | shape | | --- | --- | | bash | `value` | | fish, nu, PowerShell | `value\tdescription` | | zsh | `value\tdescription\tinsert` | zsh takes a third field because what it *displays* and what it *types* are not always the same string. Checked against what `usage complete-word --shell zsh` already emits: ``` use^IInstalls a tool and adds the version to mise.toml.^Iuse$ ``` ## Three rules that look like details and are not **Descriptions are all-or-nothing per answer.** A column that appears on some rows and not others reads as missing data rather than as an absent description. **A description is collapsed onto one line, not truncated at the first break**, so a two-line description still says both halves. The protocols are line-based and a break would look like another candidate. This is why the one-lining *moved here* from `describe` in #984, where it was throwing the second half away — `Candidates` now carries the whole text and the renderer decides how to fit it. **"Paths belong here too" is a whole line**, not a flag on the protocol, because every one of the five shells can already split output into lines and look at the last one. `\x01` opens it because no candidate can contain a control character. ## Naming `RenderAnswer` rather than `Render`, which already belongs to failures. Two things in one package turning a value into text for a terminal is reason enough to say which. ## Still to do Running the `complete` scripts a spec can declare, which means subprocesses — a decision for the layer above this one. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Additive completion-output formatting with broad unit tests; no changes to parsing, binding, or security-sensitive paths. > > **Overview** > Adds **`argv.RenderAnswer`** so completion **`Answer`** values serialize to the line protocol each shell expects: bash values only; fish, nu, and PowerShell use `value` + tab + description; zsh adds a third insert field with **`zshQuote`** for safe typing. > > Rendering enforces **all-or-nothing description columns** (only among candidates that actually emit), **collapses multi-line help with `oneLine`** instead of truncating, **drops candidates whose values contain control chars/tabs/newlines** rather than repairing them, and appends **`\x01files` / `\x01dirs` marker lines** when paths should complete too. > > **`describe` in `complete.go`** now attaches the full short help (including newlines); one-lining moved here from candidate building. README documents **`RenderAnswer`**; tests cover per-shell shapes and edge cases. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f37999a. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

argv.Walkreads the words before the cursor and reports what it is standing in;argv.Candidatessays what could go there — subcommands and their aliases, the flags in scope, and the values achoiceslist allows.Stacked on #978.
Asking the parser rather than re-deriving it
That is the whole design. A completion advertising a flag the parser would refuse is worse than no completion, so the scope a candidate comes from is the scope a token would be resolved in:
--, nothing is offered — there is no flag of this CLI to type thereErrors are not failures here
A line being completed is unfinished by definition, so a parse error means "the grammar runs out here" — which is the position being asked about.
missing_flag_valuesays the cursor is standing in a flag's value, and that flag's choices take the position entirely.helpsays the cursor is naming a command to read about, where nothing else belongs.Not included
Turning candidates into the text each shell expects, and running the
completescripts a spec can declare. The first is per-shell formatting, the second runs subprocesses, and neither belongs in a package whose claim is that a parse does not allocate. Both are noted in the README.One note from writing the tests
The first fixture numbered its keys 10–14 in an eight-entry table. Both cold tables are indexed by key, so every lookup missed and the assertions about hiding and choices passed for the wrong reason — they only started failing once the keys were dense. The invariant is documented on
Metadata; worth knowing that breaking it fails quietly rather than loudly.🤖 Generated with Claude Code
Note
Medium Risk
New completion surface area mirrors subtle parser/help scope rules; behavior is heavily tested but mistakes would mislead users without breaking parses.
Overview
Adds parser-driven tab completion in
argv:Walkparses words before the cursor into aPosition(command chain, whether flags/subcommands still apply, awaiting flag value, variadic collection, pending arg,helptopic mode), andCandidatesreturns subcommands/aliases, in-scope flag spellings, andchoicesvalues filtered by a partial prefix.Completion reuses the same scope rules as help via
flagsInScope(per-spelling shadowing of inherited globals, negation vs long-form precedence) and refactorseveryFormInScope/negationSurvivesinscope.goso help and completions stay aligned.Parser.SubcommandsPossibleexposes when descent into subcommands has stopped.README documents the completion API and notes that per-shell formatting and
completescripts are still out of scope.Reviewed by Cursor Bugbot for commit 84b303e. Bugbot is set up for automated code reviews on this repo. Configure here.