Skip to content

add Explain Code / Generate Tests code lenses - #866

Open
addyCooks wants to merge 5 commits into
Nano-Collective:mainfrom
addyCooks:feat/vscode-code-lens-actions
Open

add Explain Code / Generate Tests code lenses#866
addyCooks wants to merge 5 commits into
Nano-Collective:mainfrom
addyCooks:feat/vscode-code-lens-actions

Conversation

@addyCooks

Copy link
Copy Markdown
Contributor

Closes #750.

Description

Adds inline Explain Code / Generate Tests code lenses above every function, method and class. Clicking one reveals the chat view and submits that symbol as a prompt instruction, a file:startLine-endLine locator, 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.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Changeset

  • Added a changeset (pnpm changeset) describing this change for the changelog

Testing

Automated Tests

  • New features include passing tests in .spec.ts/tsx files
  • All existing tests pass (pnpm test:all completes successfully)
  • Tests cover both success and error scenarios

Manual Testing

  • Tested with Ollama
  • Tested with OpenRouter
  • Tested with OpenAI-compatible API
  • Tested MCP integration (if applicable)

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Documentation updated (if needed)
  • No breaking changes (or clearly documented)
  • Appropriate logging added using structured logging (see CONTRIBUTING.md)

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.
@will-lamerton

Copy link
Copy Markdown
Member

Hey @addyCooks - nice work, this is a clean implementation. Using executeDocumentSymbolProvider instead of per-language parsing is the right call, and anchoring lenses on selectionRange while passing symbol.range as the payload is a good detail. The _isWebviewReady reset in resolveWebviewView is a real latent bug fix on its own.

A few things before merge:

  1. Stuck spinner on rejected prompts. _handlePrompt early-returns when hasPendingPermissions() is true, but the webview has already appended the user bubble and called setProcessing(true). Nothing posts prompt_response, so the loader spins until the user hits Escape. Pre-existing, but clicking a lens while an approval sits unattended in the sidebar makes it much easier to hit. One-liner in that guard: this.postMessage({type: 'acpUpdate', update: {sessionUpdate: 'prompt_response'}}).

  2. Tests. plugins/vscode/src/ already has AVA specs (acp-client.spec.ts, acp-process-manager.spec.ts), but NanocoderCodeLensProvider and sendCodeLensPrompt are unexported locals in extension.ts so they can't be reached from one. Moving them to src/code-lens-provider.ts matches the rest of src/ and makes three cheap tests possible: kind filtering plus the nested children walk, the codeLens: false short-circuit, and the prompt string shape.

  3. Unbounded inlined source. document.getText(range) has no cap, so Generate Tests on a large class inlines the whole thing. Worth a line/char cap with a truncation marker, or falling back to just the file:start-end locator past a threshold.

Smaller stuff: _pendingPromptTimer is never cleared on dispose, so the timeout warning can fire after the view is gone; and onDidDispose should guard with if (this._view === webviewView) before nulling. Constructor is also missing from LENS_SYMBOL_KINDS while every sibling method gets a lens.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Inline Editor Code Lenses for AI Actions in VS Code Extension

2 participants