add Explain Code / Generate Tests code lenses - #866
Conversation
Reaching the agent about a specific function meant switching to the sidebar and pasting the code in. Every function, method and class now carries two inline links instead. Clicking one reveals the chat view and submits the symbol as a prompt: the instruction, a `file:startLine-endLine` locator and the source fenced with the document's language. The snippet is inlined rather than attached as an `@[file]` chip so the agent sees the one symbol that was clicked instead of the whole file. Symbols come from `vscode.executeDocumentSymbolProvider`, so the lenses follow whatever language servers the user already has and nothing here parses source. Lenses anchor on the symbol's selectionRange - the declaration line - while the command receives the full body range, so a preceding doc comment doesn't push the links away from the signature. A lens can be clicked before the sidebar has ever been opened, so the prompt is held in `_pendingPrompt` and flushed from `_initializeSessionIfReady`, which already runs both on webview ready and on ACP connect - whichever lands last. The two commands are hidden from the palette: they take a uri and a symbol range, so a bare invocation would have nothing to act on. `nanocoder.codeLens` turns the lenses off. Closes Nano-Collective#750.
_isWebviewReady was latched on the first shell and never cleared, so once the Nanocoder view had been disposed - hidden from its container, or moved to another one - the next lens click posted runPrompt into a replacement webview that had not run its script yet. The message went nowhere and the prompt was cleared, so the click did nothing at all. Reset the flag on every resolve and drop the view on dispose. The queued prompt is also bounded now. It used to sit indefinitely when the CLI was down and then fire from onConnectionReady whenever the agent happened to come up, answering about code the user had long moved past, with no feedback in the meantime. It expires after 30s with a warning instead, and the timer is disarmed once the prompt is handed over.
runPrompt drove the composer: it overwrote chat-input and called submitMessage, which then folded in attachedPaths and pendingImages. So clicking Explain Code discarded whatever the user was typing and sent any file chip or pasted image they had staged for a different question, then cleared them. Split the send tail out of submitMessage as dispatchPrompt and route the editor prompt straight through it, leaving the draft and the staged context untouched.
explainCode/generateTests are hidden from the palette but a keybinding or another extension can still invoke them bare. uri and range were assumed present, so openTextDocument(undefined) opened an untitled document and range.start then threw; guard and point the user at the lenses instead. Also dispose the onDidChangeCodeLenses emitter with the extension rather than leaking it, and resolve nanocoder.codeLens against the document so a folder-level override wins in a multi-root workspace - it is declared scope: resource to match.
|
Hey @addyCooks - nice work, this is a clean implementation. Using A few things before merge:
Smaller stuff: |
Ends the turn when a prompt is rejected for a pending permission: the webview had already drawn the user bubble and flipped to the loading state, so with nothing posting prompt_response the composer spun until the user hit Escape. Pre-existing, but a lens click while an approval sat unattended made it easy to hit. Moves NanocoderCodeLensProvider and sendCodeLensPrompt out of extension.ts into code-lens-provider.ts so they can be tested. The sibling specs cited as precedent never actually ran - the root AVA glob only matched source/, and `vscode` is not resolvable outside the extension host - so this also adds a runtime stub behind a test-only tsconfig paths entry and widens the glob to plugins/*/src. That revives acp-client.spec.ts and acp-process-manager.spec.ts as a side effect. The stub is kept out of the packaged .vsix and the bundle still builds with --external:vscode. Caps the source inlined into a lens prompt at 200 lines / 8000 chars, whichever binds first, with a truncation marker. Generate Tests on a large class would otherwise paste the whole body into the conversation. The file:start-end locator survives truncation, so the agent can still read the rest. ChatWebviewProvider now implements Disposable and is registered with the extension's subscriptions, so the pending-prompt timer cannot outlive it. The clear deliberately does not happen in onDidDispose: a view disposal is usually a re-reveal in progress, and dropping the queued prompt there would reintroduce the bug 9a9d5bf fixed. onDidDispose also guards on view identity so a late teardown cannot null out a newer view. Adds Constructor to LENS_SYMBOL_KINDS - every sibling method already got a lens.
c622ea5 to
95d7941
Compare
|
Thanks @will-lamerton! One thing worth flagging: the sibling specs cited as precedent were never actually running, the root AVA glob only matched source/, and vscode isn't resolvable outside the extension host. Getting any spec here to run meant widening the glob to plugins//src/**/.spec.ts and adding a vscode stub behind a test-only paths entry. That revives acp-client.spec.ts and acp-process-manager.spec.ts as a side effect. The stub is .vscodeignored and the bundle still builds with --external:vscode. Happy to split that into its own PR if you'd rather keep this one scoped to the lenses. Unbounded source. Capped at 200 lines / 8000 chars, whichever binds first, with a truncation marker. The file:start-end locator is emitted before the fence and survives truncation. Timer on dispose. ChatWebviewProvider implements Disposable and is registered with the extension's subscriptions. I deliberately did not clear it in onDidDispose — a disposal is usually a re-reveal in progress, and dropping the queued prompt there would reintroduce what 9a9d5bf fixed. onDidDispose guards on view identity as you suggested. Constructor added to LENS_SYMBOL_KINDS. |
|
Looking good to go @addyCooks! Can you fix the conflicts then I think we're good to merge :) |
…ns-actions Resolves three conflicts with main: - package.json: main's ava globs (`plugins/**/*.spec.ts` + `.tsx`) subsume this branch's narrower `plugins/*/src/**/*.spec.ts`, and they pick up the new code lens specs. Took main's side. - webview-protocol.ts: both sides appended to the `ExtensionToWebviewMessage` union. Kept both `ExtensionMessageRunPrompt` (this branch) and `ExtensionMessageMentionCompletions` (Nano-Collective#842). - chat-panel.js: this branch moved the send out of `submitMessage()` into `dispatchPrompt()` so an editor-driven prompt can bypass the composer, while Nano-Collective#842 added a `closeMention()` before the input is cleared. Kept `closeMention()` and the `dispatchPrompt()` delegation, dropping the inline `postMessage` that `dispatchPrompt` now owns — keeping both would have sent every composer message twice.
chat-panel.html loads mention-utils.js ahead of chat-panel.js, and chat-panel.js destructures globalThis.NanocoderMentionUtils at IIFE time. The harness only ran chat-panel.js, so every spec that booted a panel died on `Cannot destructure property 'findMentionQuery' of 'globalThis.NanocoderMentionUtils' as it is undefined` before reaching its assertions — 28 failures across chat-panel-thoughts and chat-panel-tool-cards. The harness (Nano-Collective#847/Nano-Collective#867) and the mention-utils extraction (Nano-Collective#842) landed independently, so neither PR saw the break; it only appears once both are on main. Loading the two scripts into the VM in the same order the page does fixes it.
|
Thanks @will-lamerton! Conflicts resolved, all 11 checks green. The only interesting one was Heads up on one unrelated commit: Happy to split that out if you'd rather keep this PR to code lenses though it |
Resolves the chat-panel.js conflict in favour of main: Nano-Collective#866's resolveEditCardState()/isSettled() refactor already handles a cancelled or denied edit arriving as 'failed', which is what this branch's updateEditCard change was for. The ToolAggregator predicate keeps the branch's 'denied' addition.
#866 landed the constant twice at module top level, which is a redeclaration error. main has been failing test:types, test:lint, test:format, the build and every spec that imports the harness since. The surviving declaration is the mediaUrl() one-liner, matching PANEL_SOURCE next to it; the deleted block's comment is already covered by the file docblock.
Closes #750.
Description
Adds inline
Explain Code/Generate Testscode lenses above every function, method, constructor and class. Clicking one reveals the chat view and submits that symbol as a prompt instruction, afile:startLine-endLinelocator, and the source fenced with the document's language. Previously, asking the agent about a specific function meant switching to the sidebar and pasting the code in.Symbols come from
executeDocumentSymbolProvider, so there is no per-language parsing. Each lens anchors on the symbol'sselectionRange(the name) while the command carriessymbol.range(the whole body). Lenses can be turned off with thenanocoder.codeLenssetting.Inlined source is capped at 200 lines / 8000 characters, whichever binds first, with a truncation marker, so
Generate Testson a large class cannot spend a whole context window on one turn. Thefile:startLine-endLinelocator is emitted before the fence and survives truncation, so the agent can read the rest itself.Also fixed here
Three latent bugs in
ChatWebviewProviderthat a lens click makes much easier to hit:prompt_responseever ended the turn. Pre-existing._isWebviewReadywas not reset on re-resolve, so a queued prompt could post into a fresh shell that had not yet attached its message listener, and be dropped.onDidDisposefrom an already-replaced view could null out the live one.Type of Change
Changeset
pnpm changeset) describing this change for the changelogTesting
Automated Tests
.spec.ts/tsxfilespnpm test:allcompletes successfully)18 specs across two files:
code-lens-provider.spec.ts(10) - kind filtering plus the nested-children walk, thecodeLens: falseshort-circuit, legacy symbols with noselectionRange, the prompt string shape, the truncation boundaries (line cap, char cap, and a single line that busts the char cap), and the command invoked with no lens arguments.chat-webview-provider.spec.ts(8) - a prompt rejected for a pending permission ends the turn, a real turn still ends exactly once,dispose()clears the pending timer, a prompt dropped by dispose is not delivered to a later view, a stale view disposal does not clear a newer view, disposing the live view does clear it, a queued prompt survives a re-reveal, and a re-resolved shell is not treated as ready until it says so.Each fix was checked by reverting it individually and confirming the corresponding spec fails.
Manual Testing
Checklist