Add pull request comments to the markdown preview by MichaelNusair · Pull Request #9074 · microsoft/vscode-pull-request-github · GitHub
Skip to content

Add pull request comments to the markdown preview - #9074

Open
Michael Nusair‬‏ (MichaelNusair) wants to merge 10 commits into
microsoft:mainfrom
MichaelNusair:markdown-preview-comments
Open

Michael Nusair‬‏ (MichaelNusair) wants to merge 10 commits into
microsoft:mainfrom
MichaelNusair:markdown-preview-comments

Conversation

@MichaelNusair

@MichaelNusair Michael Nusair‬‏ (MichaelNusair) commented Oct 19, 2026 •

Copy link
Copy Markdown
markdown-preview-comments-demo.mp4

Fixes #8607

What

Reviewers can now comment on markdown files while reading them rendered in the built-in markdown preview. This includes the side-by-side rendered diff, so they don't have to switch to the raw source first.

  • Hovering over a block that's part of the diff (a paragraph, heading, list item, table row or code block) outlines it and shows a + in the left margin.
  • A block that already has comment threads shows a comment-count badge in the right margin.
  • Clicking either one opens the source beside the preview with the regular comment widget on that block's lines, limited to the diff hunk. Clicking a badge expands the existing thread instead.

Comments go through the existing comment controllers, so pending reviews, single comments and suggestions work the same as in the editor.

It works for:

  • files of the checked-out PR (file), and the base side of its diff (review)
  • both sides of the diff of a PR that isn't checked out (pr)

How

The extension can't add UI to the markdown preview webview directly, so this uses the markdown extension's contribution points:

  1. markdown.markdownItPlugins. extendMarkdownIt, exported from activate, does two things:

    • It adds a data-pr-end-line attribute to every block. The preview already has data-line, so the script knows each block's line span.
    • At render time it adds a hidden element with the commenting ranges and thread locations of PR files, keyed by document URI. This happens at render time because the markdown extension caches tokens.

    When the rendered diff highlights changes within lines, it renders a marked-up copy of the text, so markdown-it isn't told which document it's rendering. In that case the data for all open markdown documents is rendered, and the preview script picks its own document using the source in the preview's settings.

    The data is computed asynchronously and cached. When it changes, for example after a thread is added or after local edits move the ranges, previews are refreshed with markdown.preview.refresh.

  2. markdown.previewScripts. webviews/markdownPreviewComments draws the + buttons and badges in an overlay, so the rendered content's layout is left alone.

  3. URI handler. Preview scripts can't post messages to their extension, so the buttons are vscode://github.vscode-pull-request-github/comment-on-markdown-preview?document=<id>&start=&end= links. The markdown preview lets vscode: links through. The id is opaque and only issued for documents that have been rendered in a preview, so outside links can't target arbitrary files. The handler limits the lines to the diff hunk and runs workbench.action.addComment with that range.

ReviewCommentController gets getMarkdownPreviewCommentInfo. ReviewCommentController, ReviewManager and ReviewsManager get an onDidChangeCommentThreads event, so previews refresh when threads change. For PRs that aren't checked out, the data comes from the PR's commenting range provider and reviewThreadsCache.

Known limitations / open questions

  • One-time confirmation. The first click shows VS Code's "Allow 'GitHub Pull Requests' extension to open this URI?" dialog, because webview links aren't opened as trusted. Checking "Don't ask again" removes it. A cleaner long-term fix would be a markdown preview API that lets contributed preview scripts message their own extension. I'm happy to open a VS Code issue for that.
  • Desktop only. On vscode.dev, webviews don't forward vscode: links (MainThreadWebviews.isSupportedLink).
  • Not in the new Markdown Editor. The Markdown Editor (vscode.markdown.editor) doesn't load contributed preview scripts or markdown-it plugins, so it would need support in VS Code itself.
  • Comments are still written in the editor's comment widget, not inline in the preview. This keeps the change small and reuses the existing comment UI.

Testing

  • src/test/view/markdownPreviewComments.test.ts covers:
    • the block line attributes
    • the preview data for checked-out files and for both sides of a PR that isn't checked out
    • rendering when markdown-it isn't told the document
    • opening the comment widget limited to the diff hunk, and rejecting lines outside the diff
  • webviews/markdownPreviewComments/test/overlay.test.ts covers which document's data the preview script uses, plus its comment links and thread badges.
  • npm run lint, npm run hygiene, npm run test:webviews (83 passing) and the extension tests (1055 passing) all pass.
  • Manually tested on VS Code 1.142 by commenting from the rendered diff of real PRs that change markdown files, both when the source file was already open and when it wasn't. Because of the engine version, this used a build of this change on top of v0.170.0.

🤖 Generated with Claude Code

Lets reviewers comment on markdown files while reading them rendered in the
built-in markdown preview, including the side-by-side rendered diff.

- A markdown-it plugin (`markdown.markdownItPlugins`) records the line span
  of each rendered block, and renders the commenting ranges and comment
  threads of pull request files into the preview, keyed by document URI.
  The rendered diff renders a copy of the text when it highlights changes
  within lines, so the preview script picks its document from the
  preview's settings.
- A preview script (`markdown.previewScripts`) shows a "+" next to blocks
  that are part of the diff, and a comment count next to blocks with
  threads.
- Clicking either opens the source beside the preview with the comment
  widget on the block's lines, clamped to the diff hunk, through a new
  `/comment-on-markdown-preview` URI handler path.

Works for files of the checked out pull request (`file` and the `review`
base side) and for pull requests that aren't checked out (`pr`).

Fixes microsoft#8607

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 19, 2026 10:54
@MichaelNusair

Copy link
Copy Markdown
Author

Copilot AI commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Install dependencies

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Repository routing, thread targeting, keyboard accessibility, and closed-document refresh behavior need correction.

6 open issues
What changed in this PR

Adds pull request commenting controls to Markdown previews and rendered diffs, addressing #8607 while reusing the existing editor comment UI.

Changes:

  • Adds block-level comment buttons and thread-count badges.
  • Connects previews to comment data, refresh events, and URI handling.
  • Adds extension and overlay tests.
File Description
webviews/​markdownPreviewComments/​test/​overlay.test.ts Tests document selection, links, and badges.
webviews/​markdownPreviewComments/​overlay.ts Draws commenting controls.
webviews/​markdownPreviewComments/​index.ts Registers preview interaction and layout listeners.
webpack.config.js Bundles the preview script.
src/​view/​reviewsManager.ts Aggregates thread-change events.
src/​view/​reviewManager.ts Exposes the controller and forwards events.
src/​view/​reviewCommentController.ts Provides preview ranges and thread information.
src/​view/​markdownPreviewComments.ts Connects Markdown rendering to commenting.
src/​uriHandler.ts Handles preview comment links.
src/​test/​view/​markdownPreviewComments.test.ts Tests rendering data and comment actions.
src/​test/​uriHandler.test.ts Updates handler construction.
src/​extension.ts Initializes preview support and exports the plugin.
src/​common/​uri.ts Defines the comment URI route.
package.json Registers Markdown contributions.

Review effort: Balanced

Comment thread src/view/markdownPreviewComments.ts Outdated
Comment thread src/view/markdownPreviewComments.ts Outdated
Comment thread src/view/markdownPreviewComments.ts Outdated
Comment thread src/view/reviewCommentController.ts Outdated
Comment thread webviews/markdownPreviewComments/overlay.ts
Comment thread webviews/markdownPreviewComments/overlay.ts Outdated
- Only update open documents when comment threads change, and forget
  closed documents.
- Prefer the deepest repository for nested repositories and submodules.
- For `pr` documents, find the repository that contains the file before
  matching the remote name and pull request number, which aren't unique
  across repositories.
- Look up the base side threads of a renamed file by its current file name.
- Keep the add-comment links focusable, and reveal them and their block on
  keyboard focus.
- Give each thread of a block its own badge, so threads in different diff
  hunks can each be revealed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 19, 2026 12:12

Copilot AI commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Install dependencies

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Incorrect ranges for hidden unsaved documents, missing nested-list controls, and misleading historical-preview badges remain unresolved.

No open issues 🎉

3 previously missed issues

In code that hasn't changed since last review

Medium severity Use dirty workspace documents for hidden preview diff ranges

src/​view/​reviewCommentController.ts:587

With Auto Save disabled, inserting lines before a diff hunk and then hiding the source editor behind the preview produces incorrect commenting ranges. This call reaches getContentDiff, which reads dirty text only from visibleTextEditors; otherwise it diffs the saved file. The preview renders the unsaved buffer, so controls appear on the wrong blocks and valid clicks can be rejected or target incorrect lines. Look up the document in workspace.textDocuments instead of requiring a visible editor, and cover a hidden dirty document in a regression test.

Medium severity Avoid current thread maps for historical review documents

src/​view/​reviewCommentController.ts:595

A historical review document, such as one opened through pr.diffOutdatedCommentWithHead, can fail the current file-change match because its commit differs. Falling back to reviewQuery.path still loads current-version threads, displaying badges at unrelated lines in the historical content. Clicking them is rejected because that document has no commenting ranges. Only read these current-version thread maps when the review document matches a current file change.

Medium severity Preserve parent-line targets in nested list rendering

webviews/​markdownPreviewComments/​overlay.ts:109

For a tight nested list such as - parent followed by - child, the parent text is rendered directly inside its <li> without a paragraph element. This check discards that <li> because it contains nested blocks, leaving only the child's lines represented. Changes and threads on the parent line therefore get neither an add-comment button nor a badge. Preserve a target for the parent's own source lines without duplicating descendant controls, and add a test for this rendered shape.

6 resolved since last review

Review effort: Balanced

… preview

When the source of a previewed file wasn't open yet, `workbench.action.addComment`
ran before VS Code had the commenting ranges of the new editor, and failed with
"The cursor must be within a commenting range to add a comment." Wait until the
commenting range provider has given VS Code the document's ranges first.

The commenting range providers fire `onDidProvideCommentingRanges`, and the
preview gets ranges through `getCommentingRanges` so that its own requests
don't fire it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 19, 2026 15:18
…e available

A preview rendered before the pull request's commenting range provider was
registered cached empty commenting ranges and never showed the add-comment
links. Update the open previews whenever a commenting range provider is
registered or removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/uriHandler.ts
Comment thread src/view/pullRequestCommentControllerRegistry.ts
Comment thread src/view/markdownPreviewComments.ts Outdated
Comment thread src/view/markdownPreviewComments.ts
Comment thread webviews/markdownPreviewComments/overlay.ts Outdated
Comment thread webviews/markdownPreviewComments/test/overlay.test.ts
Copilot AI balanced review requested due to automatic review settings October 19, 2026 15:36
- Diff the unsaved changes of documents that aren't visible, such as a document
  whose markdown preview is shown instead of its text editor, so that commenting
  ranges match the rendered text.
- Only show the current threads on review documents of the current file changes,
  not on other versions of a file.
- Add comment targets for the own lines of blocks that contain other blocks,
  such as the text of a list item in a tight nested list, and prefer the
  innermost block when hovering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MichaelNusair

Copy link
Copy Markdown
Author

Addressed the three "previously missed issues" from the second Copilot review in 21bd4a8:

  • Use dirty workspace documents for hidden preview diff ranges: getContentDiff now finds the document in workspace.textDocuments instead of visibleTextEditors, so unsaved changes count even when the source editor is hidden behind the preview. Covered by diffs the unsaved changes of a document that is not visible.
  • Avoid current thread maps for historical review documents: getMarkdownPreviewCommentInfo only reads the thread maps for review documents that match a current file change, and otherwise returns no threads. Covered by does not show the current threads on other versions of a file.
  • Preserve parent-line targets in nested list rendering: a block that contains other blocks now gets a target for its own lines before them, like the parent text of a tight nested list item. Children keep their own targets, and hover prefers the innermost block. Covered by adds comment links to the text of list items that contain other lists.

Each test fails without its fix. Extension tests: 1048 passing. Webview tests: 81 passing.

Two more fixes came from recording the demo:

  • ecb59b6 waits until a newly opened editor has its commenting ranges before running workbench.action.addComment. Before, the first click failed with "The cursor must be within a commenting range".
  • a247aa2 refreshes previews when a PR's commenting range provider is registered.

- Require the start and end lines in comment links from the preview, since
  `Number(null)` is 0.
- Type `PRCommentControllerRegistry` as a `CommentingRangeProvider2`, since its
  providers can return an object with the ranges, which is needed for file
  comments.
- Cancel the scheduled update of a closed document.
- Dispose the cancellation token source used to get commenting ranges.
- Measure the blocks with comment links when rendering instead of on every
  pointer move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Comment actions can target the wrong editor, and thread badges can be rejected or use stale buffer locations.

No open issues 🎉

3 previously missed issues

In code that hasn't changed since last review

Medium severity Allow badges outside commenting ranges to reveal existing threads

src/​view/​markdownPreviewComments.ts:190

Thread badges can appear outside commentingRanges, but this guard rejects their clicks instead of revealing the thread. For example, when viewing changes since the last review, the provider returns only newer hunks while getPullRequestInfo includes current threads from the whole PR. A badge for an earlier hunk in the same file therefore only shows a warning. Give badge links an existing-thread target and reveal that thread separately from the range validation used to add new comments.

Medium severity Abort comment commands when the active editor changes

src/​view/​markdownPreviewComments.ts:203

If the user switches editors while commenting ranges are loading (up to three seconds), workbench.action.addComment runs in the newly active editor using the original document's lines. Its arguments contain no document URI. After the wait, abort if the editor returned by showTextDocument is no longer active.

Medium severity Map workspace threads against the current unsaved buffer

src/​view/​reviewCommentController.ts:616

If workspace threads are initialized while the Markdown buffer already has unsaved edits, these cached ranges can point to the wrong blocks. createWorkspaceCommentThread maps thread locations using the on-disk diffWithHEAD, but getCommentingRanges accounts for the unsaved buffer. Inserting unsaved lines above a thread can therefore place its badge on another paragraph and give it the wrong link target. Compute preview thread locations from the current buffer too, starting from the original PR coordinates so existing working-tree adjustments are not applied twice.

6 resolved since last review

Review effort: Balanced

Copilot AI commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Install dependencies

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

- Thread badges reveal their thread with `workbench.action.focusCommentOnCurrentLine`
  instead of adding a comment, so that threads outside of the commenting ranges,
  such as when viewing the changes since the last review, can be opened.
- Don't run comment commands when the active editor changed while waiting for
  the commenting ranges, since they act on the active editor.
- Place the threads of workspace files by the current text, mapping their pull
  request lines through the diff that includes unsaved changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 19, 2026 17:36
@MichaelNusair

Copy link
Copy Markdown
Author

Addressed the three "previously missed issues" from the latest Copilot review in 4e16322:

  • Allow badges outside commenting ranges to reveal existing threads: thread badges now link with thread=1. The handler skips the commenting-range check for them, puts the cursor on the thread's last line (where threads are anchored), and runs workbench.action.focusCommentOnCurrentLine, which expands and focuses the thread without toggling it. Covered by reveals an existing thread, even outside of the commenting ranges.
  • Abort comment commands when the active editor changes: after waiting for the commenting ranges, nothing runs if the file is no longer the active editor's document. Covered by does not open the comment widget when the active editor changed while opening the document.
  • Map workspace threads against the current unsaved buffer: for workspace files, the preview now places threads by mapping their pull request lines (startLine/endLine from reviewThreadsCache) through getContentDiff, which includes unsaved changes. This starts from the PR coordinates, so the diffWithHEAD adjustment isn't applied twice. Covered by places the threads of a workspace file by its current text, including unsaved changes.

The second and third tests fail without their fixes. The first covers new behavior. Extension tests: 1053 passing. Webview tests: 82 passing.

The "Install dependencies" setup-step failure Copilot reported comes from the repository's Copilot code review configuration, not from this PR.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Thread badges cannot reliably select distinct conversations anchored to the same line.

1 open issue

Review effort: Balanced

Comment thread src/view/markdownPreviewComments.ts Outdated

Copilot AI commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Install dependencies

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

Several threads can end on the same line, and
`workbench.action.focusCommentOnCurrentLine` reveals the first one. Thread
badges now link with the GitHub thread id, and the thread is revealed with its
`reveal()` API, through the new `getCommentThread` lookups of the comment
controllers. The command is still used if the thread can't be found.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 19, 2026 17:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The new URI entry point and asynchronous preview lifecycle need human validation, with an unresolved update-tracking issue.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity If VS Code closes the source document while its preview stays visible, forgetDocument() removes…

src/​view/​markdownPreviewComments.ts:186

If VS Code closes the source document while its preview stays visible, forgetDocument() removes its _info entry. Clicking a preview link reopens the source here but does not restore tracking. Subsequent thread-change events therefore skip it in updateAll(), leaving badges stale until another render. Restore the entry and schedule an update when reopening through a preview link; this preserves the existing restriction against updating closed historical documents.

1 resolved since last review

Review effort: Balanced

Copilot AI commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Install dependencies

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

Copilot AI balanced review requested due to automatic review settings October 20, 2026 08:02
@MichaelNusair

Michael Nusair‬‏ (MichaelNusair) commented Oct 20, 2026 •

Copy link
Copy Markdown
Author

Addressed the "previously missed issue" from the latest Copilot review in 80307d1:

  • Restore tracking when a preview link reopens a closed document: commentOnLines now starts tracking the document again when it isn't tracked, and schedules an update. Later thread changes then refresh its preview again. Documents that are closed and not reopened from a preview are still left alone. Covered by tracks a closed document again when a link in its preview reopens it, which fails without the fix.

Extension tests: 1055 passing. Webview tests: 83 passing.

VS Code can close a document while its preview is still shown, which stops its
comment information from being updated. When a link in the preview reopens the
document, track it again and schedule an update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Install dependencies

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Cross-extension rendering and asynchronous editor synchronization need human validation after the remaining overlay issues are corrected.

2 open issues

Review effort: Balanced

Comment thread webviews/markdownPreviewComments/overlay.ts
Comment thread webviews/markdownPreviewComments/overlay.ts
Copilot AI balanced review requested due to automatic review settings October 20, 2026 10:03

Copilot AI commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Install dependencies

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Cross-extension rendering and editor timing require human validation, and keyboard focus preservation remains unresolved.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Preserve comment link focus across layout renders

webviews/​markdownPreviewComments/​overlay.ts:157

Every render removes the focused comment link, including layout-only renders triggered by resizing or an image loading (index.ts:33–35). The replacement link never receives focus, so a keyboard user can no longer activate it with Enter. Before removing the overlay, capture the focused link's href if focus is inside it. After rebuilding, focus the matching replacement with { preventScroll: true }. Add coverage for both add links and thread badges retaining focus after another render.

2 resolved since last review

Review effort: Balanced

@MichaelNusair

Copy link
Copy Markdown
Author

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.

Ability to add comments in Markdown Preview Mode

2 participants