Skip to content

Commit 783ebc2

Browse files
authored
feat(vscode-ext): support source opening (#817)
## What does this PR do? It enables quick opening of the source file in VSCode using CMD+click with the Extension. ### Preview <img width="1270" height="524" alt="image" src="https://github.com/user-attachments/assets/5cfe6730-dfcd-449d-ae98-059b6b8115b4" />
1 parent dd34e41 commit 783ebc2

File tree

2 files changed

+150
-1
lines changed

2 files changed

+150
-1
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import * as path from "path";
2+
import {
3+
DocumentLink,
4+
DocumentLinkProvider,
5+
Position,
6+
Range,
7+
Selection,
8+
TextDocument,
9+
Uri,
10+
window,
11+
workspace,
12+
} from "vscode";
13+
14+
type SourceLink = {
15+
sourcePath: string;
16+
range: Range;
17+
line?: number;
18+
};
19+
20+
export const SOURCE_COMMAND = "mitsuhiko.insta.open-source-location";
21+
22+
function extractSourceLink(document: TextDocument): SourceLink | undefined {
23+
let sourcePath: string | undefined;
24+
let lineNumber: number | undefined;
25+
let sourceLineIndex = -1;
26+
27+
// Simple pass through all lines
28+
for (let i = 0; i < document.lineCount; i++) {
29+
const text = document.lineAt(i).text;
30+
31+
// Find source path
32+
if (text.startsWith("source:")) {
33+
sourcePath = text.slice(7).trim(); // "source:" is 7 characters
34+
sourceLineIndex = i;
35+
}
36+
37+
// Find assertion line (optional)
38+
if (text.startsWith("assertion_line:")) {
39+
const lineStr = text.slice(15).trim(); // "assertion_line:" is 15 characters
40+
lineNumber = parseInt(lineStr, 10);
41+
}
42+
}
43+
44+
if (sourcePath && sourceLineIndex >= 0) {
45+
// Create a simple range for the entire source line
46+
const line = document.lineAt(sourceLineIndex);
47+
const range = new Range(
48+
new Position(sourceLineIndex, 0),
49+
new Position(sourceLineIndex, line.text.length)
50+
);
51+
52+
return {
53+
sourcePath,
54+
range,
55+
line: lineNumber,
56+
};
57+
}
58+
59+
return undefined;
60+
}
61+
62+
async function resolveSourceUri(
63+
document: TextDocument,
64+
sourcePath: string
65+
): Promise<Uri | undefined> {
66+
// Handle absolute paths
67+
if (path.isAbsolute(sourcePath)) {
68+
return Uri.file(sourcePath);
69+
}
70+
71+
// For relative paths, resolve against workspace root
72+
// Insta snapshots are always workspace-relative
73+
const workspaceFolder = workspace.getWorkspaceFolder(document.uri);
74+
if (workspaceFolder) {
75+
return Uri.file(path.join(workspaceFolder.uri.fsPath, sourcePath));
76+
}
77+
78+
return undefined;
79+
}
80+
81+
export class SnapshotDocumentLinkProvider implements DocumentLinkProvider {
82+
async provideDocumentLinks(
83+
document: TextDocument
84+
): Promise<DocumentLink[]> {
85+
const info = extractSourceLink(document);
86+
if (!info) {
87+
return [];
88+
}
89+
90+
const target = await resolveSourceUri(document, info.sourcePath);
91+
if (!target) {
92+
return [];
93+
}
94+
95+
const payload = encodeURIComponent(
96+
JSON.stringify({
97+
target: target.toString(true),
98+
line: info.line,
99+
})
100+
);
101+
102+
const link = new DocumentLink(
103+
info.range,
104+
Uri.parse(`command:${SOURCE_COMMAND}?${payload}`)
105+
);
106+
link.tooltip = "Open snapshot source";
107+
return [link];
108+
}
109+
}
110+
111+
export async function openSourceDocument(payload?: {
112+
target?: string;
113+
line?: number;
114+
}) {
115+
if (!payload?.target) {
116+
return;
117+
}
118+
119+
const uri = Uri.parse(payload.target);
120+
try {
121+
const document = await workspace.openTextDocument(uri);
122+
const editor = await window.showTextDocument(document, { preview: true });
123+
if (payload.line !== undefined) {
124+
const zeroBasedLine = Math.max(payload.line - 1, 0);
125+
const position = new Position(zeroBasedLine, 0);
126+
editor.selection = new Selection(position, position);
127+
editor.revealRange(new Range(position, position));
128+
}
129+
} catch (error) {
130+
const message =
131+
error instanceof Error ? error.message : "Unknown error opening file";
132+
window.showErrorMessage(`Could not open source file: ${message}`);
133+
}
134+
}

vscode-insta/src/extension.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ import { InlineSnapshotProvider } from "./InlineSnapshotProvider";
1313
import { PendingSnapshotsProvider } from "./PendingSnapshotsProvider";
1414
import { Snapshot } from "./Snapshot";
1515
import { SnapshotPathProvider } from "./SnapshotPathProvider";
16+
import {
17+
SnapshotDocumentLinkProvider,
18+
openSourceDocument,
19+
SOURCE_COMMAND
20+
} from "./SnapshotDocumentLinkProvider";
1621
import { findCargoRoots, projectUsesInsta } from "./cargo";
1722
import { processAllSnapshots, processInlineSnapshot } from "./insta";
1823

@@ -201,6 +206,7 @@ function performOnAllSnapshots(op: "accept" | "reject") {
201206
export function activate(context: ExtensionContext): void {
202207
const pendingSnapshots = new PendingSnapshotsProvider();
203208
const snapshotPathProvider = new SnapshotPathProvider();
209+
const snapshotDocumentLinkProvider = new SnapshotDocumentLinkProvider();
204210

205211
const snapWatcher = workspace.createFileSystemWatcher(
206212
"**/*.{snap,snap.new,pending-snap}"
@@ -224,6 +230,14 @@ export function activate(context: ExtensionContext): void {
224230
snapWatcher,
225231
cargoLockWatcher,
226232
window.registerTreeDataProvider("pendingInstaSnapshots", pendingSnapshots),
233+
languages.registerDocumentLinkProvider(
234+
{ language: "insta-snapshots" },
235+
snapshotDocumentLinkProvider
236+
),
237+
languages.registerDocumentLinkProvider(
238+
{ scheme: "instaInlineSnapshot" },
239+
snapshotDocumentLinkProvider
240+
),
227241
workspace.registerTextDocumentContentProvider(
228242
"instaInlineSnapshot",
229243
new InlineSnapshotProvider(pendingSnapshots)
@@ -267,6 +281,7 @@ export function activate(context: ExtensionContext): void {
267281
),
268282
commands.registerCommand("mitsuhiko.insta.reject-all-snapshots", () =>
269283
performOnAllSnapshots("reject")
270-
)
284+
),
285+
commands.registerCommand(SOURCE_COMMAND, openSourceDocument)
271286
);
272287
}

0 commit comments

Comments
 (0)