Skip to content

Feat/auto copy last code block element - #800

Merged
will-lamerton merged 7 commits into
Nano-Collective:mainfrom
AryanNandanwar:feat/auto-copy-last-code-block-element
Aug 9, 2026
Merged

Feat/auto copy last code block element#800
will-lamerton merged 7 commits into
Nano-Collective:mainfrom
AryanNandanwar:feat/auto-copy-last-code-block-element

Conversation

@AryanNandanwar

Copy link
Copy Markdown
Contributor

Description

Adds a keyboard-friendly way to copy AI output from the Nanocoder VS Code chat webview (closes #759).

In the sidebar chat you can:

  • Type /copy to copy the last full assistant response to the clipboard
  • Type /copy code to copy only the last fenced code block (<pre><code> in the messages DOM)
  • Use Ctrl+Alt+Shift+C / Cmd+Alt+Shift+C (or Nanocoder: Copy Last Code Block from the Command Palette) to copy the last code block

Copy is scoped to assistant messages only (via an agent-markdown marker), so user echoes and thought boxes are ignored. The webview finds the text; the extension host writes it with vscode.env.clipboard.writeText and shows a short in-webview toast.

The chosen hotkey avoids conflicts with VS Code’s Ctrl+Shift+C (external terminal) and Cursor’s Ctrl+Alt+C (confetti). Terminal CLI /copy behavior is unchanged.

Type of Change

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

Changeset

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

Docs-only or internal chores need no changeset (or run pnpm changeset --empty to note that intentionally).

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

(Webview UI lives under plugins/vscode/, which is outside the root AVA/Biome suite; verified via TypeScript check, extension build, and manual testing below.)

Manual Testing

  • Tested in Nanocoder VS Code/Cursor chat webview (ACP session)
  • Tested with Ollama
  • Tested with OpenRouter
  • Tested with OpenAI-compatible API
  • Tested MCP integration (if applicable)

Manual checks:

  • /copy copies the last full assistant response
  • /copy code copies the last fenced code block (indentation preserved, no extra trailing blank line)
  • Empty chat shows a clear empty-state toast
  • User-typed / thought-box code blocks are not selected
  • Hotkey / Command Palette path copies the last code block when the chat view is focused
  • Extension packages successfully (pnpm run build:vscode)

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)

@AryanNandanwar

Copy link
Copy Markdown
Contributor Author

I'll update it with the changeset

@AryanNandanwar
AryanNandanwar marked this pull request as draft August 6, 2026 18:05
@AryanNandanwar
AryanNandanwar marked this pull request as ready for review August 6, 2026 18:06
@AryanNandanwar

Copy link
Copy Markdown
Contributor Author

Hey @akramcodez Please review this when you get the time.

@akramcodez

Copy link
Copy Markdown
Collaborator

Hi @AryanNandanwar, thanks for taking this on and for the detailed PR description!

I reviewed the code and noticed a major discrepancy between the plan you outlined in the issue thread and the actual implementation in this PR.

1. Architecture Flaw: Backend vs. Frontend Clipboard

  • What is wrong: The PR uses the clipboardy npm package inside source/acp/acp-agent.ts (the backend CLI engine) to write to the clipboard.
  • Why it is wrong: Nanocoder's ACP backend often runs in isolated environments (like DevContainers, Docker, WSL, or remote SSH). If you use clipboardy on the backend, it will attempt to write to the remote server's clipboard (which often fails or goes nowhere), rather than the user's local operating system clipboard.
  • What should be done: As you originally proposed in the issue thread, this must be handled entirely on the VS Code frontend. The webview should use DOM querying to find the code block text, send a postMessage to the extension host (plugins/vscode/src/chat-webview-provider.ts), and then the extension host must use vscode.env.clipboard.writeText(text). Please revert the changes to source/acp/acp-agent.ts and source/commands/copy.ts.

2. Unrelated File Commits

  • What is wrong: The PR includes several unrelated files: .devcontainer/devcontainer-lock.json, .gitignore, badges/forks.svg, and badges/npm-downloads-monthly.svg.
  • What should be done: These look like accidental commits. Please revert these files so the PR only contains changes strictly related to the feature.

3. Test Coverage

  • What is wrong: The PR description states "New features include passing tests in .spec.ts/tsx files", but no test files were actually added or modified in the diff.
  • What should be done: If this is implemented purely in the Webview (which lacks a test harness right now), it's okay to note that it's manually tested. Just uncheck the automated tests box so the PR description accurately reflects the code.

Once the clipboard logic is moved to vscode.env.clipboard.writeText and the unrelated files are dropped, we can get this merged!

@AryanNandanwar

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback@akramcodez ! I'll update the PR accordingly

@AryanNandanwar
AryanNandanwar force-pushed the feat/auto-copy-last-code-block-element branch from 048f98a to 0d2ee7d Compare August 8, 2026 13:46
@AryanNandanwar

Copy link
Copy Markdown
Contributor Author

Hey @akramcodez, changes have been updated. Here is a brief summary of what was addressed:

Clipboardy Removal: Replaced clipboardy with direct DOM extraction and VS Code extension round-tripping.

Targeted Code Selection: The webview queries .agent-markdown pre code (scoped strictly to assistant prose, skipping user echoes and thought boxes), takes the final block, and sends a typed copyToClipboard message to the host.

Synchronized Rendering: Added flushPendingRender() to force any pending markdown renders before copying, ensuring mid-stream copies read the current DOM instead of the previous frame.

Host Feedback: plugins/vscode/src/chat-webview-provider.ts responds with a copyResult message, which the webview converts into a "Copied N characters" toast.

Unified Trigger Routing:

/copy code typed in the chat input is intercepted locally in submitMessage() and invokes copyLastCodeBlock() without hitting the agent.

/copy alone copies the entire last response as raw markdown.

Ctrl+Alt+Shift+C handles copy events through two entry points: a webview keydown listener (when focused) and a VS Code keybinding on the nanocoder.copyLastCodeBlock command (scoped to focusedView == nanocoder.chatView).

Cleanup: Removed unrelated file modifications (such as .devcontainer/devcontainer-lock.json and .gitignore). Please note that badges/forks.svg and badges/npm-downloads-monthly.svg were not added by me.

PR Checklist: Left the "New features include passing tests in .spec.ts/tsx files" checkbox unchecked.

@will-lamerton

Copy link
Copy Markdown
Member

Hey @AryanNandanwar - thanks for this, the design is right (webview finds the text, host owns the clipboard write) and the agent-markdown marker correctly skips user echoes and thought boxes. Nice catch on the duplicate fs/path imports too, main currently fails tsc in plugins/vscode without that fix.

A few changes needed from my end before merge:

  1. Revert the export in source/commands/copy.ts. findLastAssistantContent is exported but has no consumer, it's leftover from the reverted acp-agent.ts commit. plugins/** is knip-ignored but source/** isn't, so pnpm run test:knip will flag it as an unused export.

  2. Scope copyLastCodeBlock to the last response. querySelectorAll('.agent-markdown pre code') spans the whole transcript, so if the latest reply is prose-only it silently copies a code block from several turns back and still reports "Copied N characters". Please query within the last .agent-markdown container and toast "No code block in the last response" otherwise.

  3. Fix the /copy intercept with attachments. The check runs after chips are folded into text, so /copy with a file attached falls through to the agent and returns "Unrecognized slash command". Match on the raw chatInput.value.trim() before context lines are appended, and clear attachedPaths/pendingImages on the intercept path.

  4. Make /copy discoverable. /help in acp-agent.ts still lists only /clear and /help for the GUI, and there's no slash autocomplete in the webview, so nothing tells users the command exists. Also, /copy code in the terminal silently copies the whole response since copyCommand ignores _args, so the same string behaves differently on the two surfaces. Either teach copy.ts the code argument or have it say it isn't supported there.

Minor, take or leave:

  • Please check whether the chord double-fires. VS Code forwards webview keydowns to the host for keybinding resolution even after preventDefault(), so the document listener and the nanocoder.copyLastCodeBlock keybinding may both run, giving two clipboard writes and two toasts.
  • flushPendingRender duplicates the clear-and-reparse already in the turn finalizer, worth extracting one helper.
  • Add role="status" to the toast so screen reader users get confirmation on a keyboard-first feature.
  • Changeset reads as a CLI change, worth prefixing with "VS Code extension:" since that's where the feature lives.

Thanks :)

@AryanNandanwar
AryanNandanwar force-pushed the feat/auto-copy-last-code-block-element branch 2 times, most recently from ff3c762 to 7e230b2 Compare August 9, 2026 19:52
@AryanNandanwar
AryanNandanwar force-pushed the feat/auto-copy-last-code-block-element branch from 4893044 to 34223b3 Compare August 9, 2026 20:08
@AryanNandanwar

Copy link
Copy Markdown
Contributor Author

Hey @will-lamerton , thanks for the thorough review! Addressed everything below:

Required

  • Reverted the unused findLastAssistantContent export so knip stays clean.
  • Scoped copyLastCodeBlock to the last .agent-markdown container. Prose-only latest replies now toast No code block in the last response instead of pulling an older block.
  • /copy and /copy code now match against chatInput.value.trim() before context chips are folded in, and clear attachedPaths / pendingImages on the intercept path.
  • /help in the GUI now lists /copy and /copy code.
  • On the terminal, /copy code is rejected with This functionality isn't supported in the terminal. so the two surfaces don't silently diverge.
  • Skipped webview slash autocomplete — discovery via /help felt sufficient.

Minor

  • Removed the webview keydown listener; the chord is host-only via nanocoder.copyLastCodeBlock to avoid the double clipboard write / double toast.
  • endCurrentTextBlock now reuses flushPendingRender() instead of duplicating the clear-and-reparse logic.
  • Toast now has role="status" for screen readers.
  • Changeset is prefixed with VS Code extension:.

Ready for another look when you have a moment. Thanks again!

@will-lamerton

Copy link
Copy Markdown
Member

Thanks for this PR @AryanNandanwar - feel free to add yourself as a contributor to our website via a PR which I will approve :)

https://nanocollective.org/contributors
https://github.com/Nano-Collective/organisation

@will-lamerton
will-lamerton merged commit c65f792 into Nano-Collective:main Aug 9, 2026
1 check passed
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] Auto-Copy Last Code Block Command

3 participants