Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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)

AltStyle によって変換されたページ (->オリジナル) /