Summary
Rich Markdown Diff works great for uncommitted changes, but it can't render the diff of a committed Markdown file — i.e. when you select a commit in the Source Control Graph (or GitLens), open one of its changed .md files, and get VS Code's text diff editor comparing two commits.
The $(diff) button does appear in the diff editor's title bar, but clicking it shows:
No Markdown changes are available for this file.
This is the one case where the rendered diff would be most valuable — reviewing a document change after the fact — and it's also confusing, because the button's presence promises it will work.
Environment
- Rich Markdown Diff 1.4.0
- VS Code 1.129.1, Windows 11 (x64)
- Built-in Git extension (
vscode.git) Source Control Graph view
Steps to reproduce
- Commit a change to a
.md file.
- Open the Source Control Graph view, click the commit, and click the changed
.md file. VS Code opens a text diff editor titled file.md (<sha>) ↔ file.md.
- Make sure the file has no uncommitted changes in the working tree.
- Click the Rich Markdown Diff button in the editor title bar.
Expected: rendered diff of the file as it changed in that commit.
Actual: No Markdown changes are available for this file.
Note that step 3 matters: if the file also has uncommitted edits, the command silently shows the working-tree vs HEAD diff instead of the commit diff you clicked on — arguably a second, quieter form of the same bug.
Root cause (from the 1.4.0 sources)
The commit refs are discarded before the comparison is resolved.
-
src/commandTarget.ts — toFileBackedUri() (L66–81) rewrites any git: URI to a plain file: URI by pulling query.path out, dropping query.ref entirely. In getCommandTarget(), the arg instanceof vscode.Uri branch (L212–218) applies that rewrite and hardcodes comparisonHint: "auto" — it never inspects the ref at all.
-
src/gitDiffResolver.ts — even on the object-arg path, getComparisonHintFromUris() (L343–367) only recognizes three ref values: "" (index), "~" (working tree), and HEAD. A commit SHA falls through to "auto". And the type itself has nowhere to put a commit:
export type GitComparisonHint = "auto" | "workingTree" | "index";
-
src/extension.ts — showDiff() (L1302–1364) reads commandTarget.targetUri and .comparisonHint, but never uses the originalUri / modifiedUri that getCommandTarget already returns (they're populated at commandTarget.ts L246–247). So the commit refs are available and then thrown away.
-
resolveSingleFileComparison(fileUri, "auto") therefore resolves HEAD ↔ working tree. For a clean file that yields kind: "cleanHeadToWorkingTree", which isActionableSingleFileComparison() rejects when the document isn't dirty → the message.
The button still shows because the editor/title when clause in package.json includes isInDiffEditor, which is true here, while the command's own gate (rich-markdown-diff.canShowRenderedDiff) is false. The menu condition and the command's precondition disagree.
Suggested fix
Most of the machinery already exists. showTwoFilesDiff() (used by the Explorer's "compare 2 selected files" path, extension.ts L1282–1300) already renders an arbitrary URI pair, and readDocumentText() uses vscode.workspace.openTextDocument(), which resolves git: URIs through the Git extension's content provider. So commit-vs-commit content is already readable — it just never gets requested.
A minimal fix:
- In
getCommandTarget(), stop discarding refs unconditionally: keep the original git: URIs alongside the file-backed targetUri in the vscode.Uri branch too (or at least detect the ref).
- In
showDiff(), before falling through to resolveSingleFileComparison, check whether commandTarget.originalUri / .modifiedUri carry non-HEAD/non-""/non-~ refs. If so, route straight to the two-URI render path with those URIs.
Two details that would need attention:
- Labels.
showTwoFilesDiff labels each side with path.basename(uri.fsPath). For a commit diff both sides have the same basename, so describeUriPair() walks up parent segments, finds no difference, and falls back to the two full paths — which are identical and unhelpful. Ref-aware labels (file.md (c02e3e4^) ↔ file.md (c02e3e4)) would be needed.
- Watching.
showTwoFilesDiff filters watchUris to file: scheme only. That's already the right behavior here — commit blobs are immutable, so there's nothing to watch and no live-refresh needed.
A fuller fix would extend GitComparisonHint / resolveSingleFileComparison with an explicit "commit ref pair" mode so the SCM-context and diff-editor entry points share one resolution path, but the routing shortcut above would cover the common case.
Happy to test a build if that's useful.
Summary
Rich Markdown Diff works great for uncommitted changes, but it can't render the diff of a committed Markdown file — i.e. when you select a commit in the Source Control Graph (or GitLens), open one of its changed
.mdfiles, and get VS Code's text diff editor comparing two commits.The
$(diff)button does appear in the diff editor's title bar, but clicking it shows:This is the one case where the rendered diff would be most valuable — reviewing a document change after the fact — and it's also confusing, because the button's presence promises it will work.
Environment
vscode.git) Source Control Graph viewSteps to reproduce
.mdfile..mdfile. VS Code opens a text diff editor titledfile.md (<sha>) ↔ file.md.Expected: rendered diff of the file as it changed in that commit.
Actual:
No Markdown changes are available for this file.Note that step 3 matters: if the file also has uncommitted edits, the command silently shows the working-tree vs HEAD diff instead of the commit diff you clicked on — arguably a second, quieter form of the same bug.
Root cause (from the 1.4.0 sources)
The commit refs are discarded before the comparison is resolved.
src/commandTarget.ts—toFileBackedUri()(L66–81) rewrites anygit:URI to a plainfile:URI by pullingquery.pathout, droppingquery.refentirely. IngetCommandTarget(), thearg instanceof vscode.Uribranch (L212–218) applies that rewrite and hardcodescomparisonHint: "auto"— it never inspects the ref at all.src/gitDiffResolver.ts— even on the object-arg path,getComparisonHintFromUris()(L343–367) only recognizes three ref values:""(index),"~"(working tree), andHEAD. A commit SHA falls through to"auto". And the type itself has nowhere to put a commit:src/extension.ts—showDiff()(L1302–1364) readscommandTarget.targetUriand.comparisonHint, but never uses theoriginalUri/modifiedUrithatgetCommandTargetalready returns (they're populated at commandTarget.ts L246–247). So the commit refs are available and then thrown away.resolveSingleFileComparison(fileUri, "auto")therefore resolves HEAD ↔ working tree. For a clean file that yieldskind: "cleanHeadToWorkingTree", whichisActionableSingleFileComparison()rejects when the document isn't dirty → the message.The button still shows because the
editor/titlewhenclause inpackage.jsonincludesisInDiffEditor, which is true here, while the command's own gate (rich-markdown-diff.canShowRenderedDiff) is false. The menu condition and the command's precondition disagree.Suggested fix
Most of the machinery already exists.
showTwoFilesDiff()(used by the Explorer's "compare 2 selected files" path, extension.ts L1282–1300) already renders an arbitrary URI pair, andreadDocumentText()usesvscode.workspace.openTextDocument(), which resolvesgit:URIs through the Git extension's content provider. So commit-vs-commit content is already readable — it just never gets requested.A minimal fix:
getCommandTarget(), stop discarding refs unconditionally: keep the originalgit:URIs alongside the file-backedtargetUriin thevscode.Uribranch too (or at least detect the ref).showDiff(), before falling through toresolveSingleFileComparison, check whethercommandTarget.originalUri/.modifiedUricarry non-HEAD/non-""/non-~refs. If so, route straight to the two-URI render path with those URIs.Two details that would need attention:
showTwoFilesDifflabels each side withpath.basename(uri.fsPath). For a commit diff both sides have the same basename, sodescribeUriPair()walks up parent segments, finds no difference, and falls back to the two full paths — which are identical and unhelpful. Ref-aware labels (file.md (c02e3e4^)↔file.md (c02e3e4)) would be needed.showTwoFilesDifffilterswatchUristofile:scheme only. That's already the right behavior here — commit blobs are immutable, so there's nothing to watch and no live-refresh needed.A fuller fix would extend
GitComparisonHint/resolveSingleFileComparisonwith an explicit "commit ref pair" mode so the SCM-context and diff-editor entry points share one resolution path, but the routing shortcut above would cover the common case.Happy to test a build if that's useful.