Skip to content

Commit

Permalink
Fix VSCode plugin not recognizing crates in subfolders (#474)
Browse files Browse the repository at this point in the history
This is a naive implementation where we just search for cargo lockfiles
to find cargo roots.
It's good enough imo and isn't much worse than the previous impl
  • Loading branch information
necauqua committed May 15, 2024
1 parent 922c68f commit 430e8ab
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 71 deletions.
2 changes: 1 addition & 1 deletion vscode-insta/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"homepage": "https://github.com/mitsuhiko/insta",
"icon": "images/icon.png",
"activationEvents": [
"workspaceContains:Cargo.toml",
"workspaceContains:Cargo.lock",
"onLanguage:rust",
"onLanguage:insta-snapshots",
"onView:pendingInstaSnapshots",
Expand Down
37 changes: 19 additions & 18 deletions vscode-insta/src/PendingSnapshotsProvider.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import {
ProviderResult,
TreeDataProvider,
TreeItem,
Event,
WorkspaceFolder,
EventEmitter,
Uri,
TreeDataProvider,
TreeItem,
Uri
} from "vscode";
import { getPendingSnapshots } from "./insta";
import { Snapshot } from "./Snapshot";
import { findCargoRoots } from "./cargo";
import { getPendingSnapshots } from "./insta";

export class PendingSnapshotsProvider implements TreeDataProvider<Snapshot> {
private _onDidChangeTreeData: EventEmitter<
Expand All @@ -19,7 +18,7 @@ export class PendingSnapshotsProvider implements TreeDataProvider<Snapshot> {
public cachedInlineSnapshots: { [key: string]: Snapshot } = {};
private pendingRefresh?: NodeJS.Timeout;

constructor(private workspaceRoot?: WorkspaceFolder) {}
constructor() {}

refresh(): void {
this._onDidChangeTreeData.fire();
Expand Down Expand Up @@ -47,19 +46,21 @@ export class PendingSnapshotsProvider implements TreeDataProvider<Snapshot> {
return element;
}

getChildren(element?: Snapshot): ProviderResult<Snapshot[]> {
const { workspaceRoot } = this;
if (element || !workspaceRoot) {
return Promise.resolve([]);
async getChildren(element?: Snapshot): Promise<Snapshot[]> {
if (element) {
return [];
}
const roots = await findCargoRoots();
if (roots.length === 0) {
return [];
}

return getPendingSnapshots(workspaceRoot.uri).then((snapshots) => {
return snapshots.map((snapshot) => {
if (snapshot.inlineInfo) {
this.cachedInlineSnapshots[snapshot.key] = snapshot;
}
return snapshot;
});
const snapshots = await getPendingSnapshots(roots);
return snapshots.map((snapshot) => {
if (snapshot.inlineInfo) {
this.cachedInlineSnapshots[snapshot.key] = snapshot;
}
return snapshot;
});
}
}
15 changes: 14 additions & 1 deletion vscode-insta/src/cargo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function metadataReferencesInsta(metadata: any): boolean {
return false;
}

export async function projectUsesInsta(root: Uri): Promise<boolean> {
async function checkSingleProject(root: Uri): Promise<boolean> {
const rootCargoToml = Uri.joinPath(root, "Cargo.toml");
try {
await workspace.fs.stat(rootCargoToml);
Expand Down Expand Up @@ -44,3 +44,16 @@ export async function projectUsesInsta(root: Uri): Promise<boolean> {
});
});
}

export async function projectUsesInsta(roots: Uri[]): Promise<boolean> {
const results = await Promise.all(roots.map(checkSingleProject));
return results.some((x) => x);
}

export async function findCargoRoots(): Promise<Uri[]> {
// we search for the lockfile to only include workspace roots
const uris = await workspace.findFiles('**/Cargo.lock');
const roots = uris.map((uri) => Uri.joinPath(uri, '..'));
console.log('found cargo roots', roots);
return roots;
}
63 changes: 31 additions & 32 deletions vscode-insta/src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { platform } from "os";
import {
DocumentFilter,
ExtensionContext,
languages,
workspace,
FileSystemError,
Uri,
commands,
languages,
window,
FileSystemError,
DocumentFilter,
workspace,
} from "vscode";
import { projectUsesInsta } from "./cargo";
import { InlineSnapshotProvider } from "./InlineSnapshotProvider";
import { processAllSnapshots, processInlineSnapshot } from "./insta";
import { PendingSnapshotsProvider } from "./PendingSnapshotsProvider";
import { Snapshot } from "./Snapshot";
import { SnapshotPathProvider } from "./SnapshotPathProvider";
import { findCargoRoots, projectUsesInsta } from "./cargo";
import { processAllSnapshots, processInlineSnapshot } from "./insta";

const INSTA_CONTEXT_NAME = "inInstaSnapshotsProject";
const RUST_FILTER: DocumentFilter = {
Expand Down Expand Up @@ -177,32 +177,29 @@ async function setInstaContext(value: boolean): Promise<void> {
await commands.executeCommand("setContext", INSTA_CONTEXT_NAME, value);
}

function checkInstaContext() {
const rootUri = workspace.workspaceFolders?.[0].uri;
if (rootUri) {
projectUsesInsta(rootUri).then((usesInsta) => setInstaContext(usesInsta));
} else {
setInstaContext(false);
}
async function checkInstaContext() {
const roots = await findCargoRoots();
setInstaContext(roots.length !== 0 && await projectUsesInsta(roots));
}

function performOnAllSnapshots(op: "accept" | "reject") {
const root = workspace.workspaceFolders?.[0];
if (!root) {
return;
}
processAllSnapshots(root.uri, op).then((okay) => {
if (okay) {
window.showInformationMessage(`Successfully ${op}ed all snapshots.`);
} else {
window.showErrorMessage(`Could not ${op} snapshots.`);
findCargoRoots().then((roots) => {
if (roots.length === 0) {
return;
}

processAllSnapshots(roots, op).then((okay) => {
if (okay) {
window.showInformationMessage(`Successfully ${op}ed all snapshots.`);
} else {
window.showErrorMessage(`Could not ${op} snapshots.`);
}
});
});
}

export function activate(context: ExtensionContext): void {
const root = workspace.workspaceFolders?.[0];
const pendingSnapshots = new PendingSnapshotsProvider(root);
const pendingSnapshots = new PendingSnapshotsProvider();
const snapshotPathProvider = new SnapshotPathProvider();

const snapWatcher = workspace.createFileSystemWatcher(
Expand All @@ -212,18 +209,20 @@ export function activate(context: ExtensionContext): void {
snapWatcher.onDidCreate(() => pendingSnapshots.refreshDebounced());
snapWatcher.onDidDelete(() => pendingSnapshots.refreshDebounced());

const cargoTomlWatcher = workspace.createFileSystemWatcher("**/Cargo.toml");
cargoTomlWatcher.onDidChange(() => checkInstaContext());
cargoTomlWatcher.onDidCreate(() => checkInstaContext());
cargoTomlWatcher.onDidDelete(() => checkInstaContext());
const cargoLockWatcher = workspace.createFileSystemWatcher("**/Cargo.lock");
cargoLockWatcher.onDidChange(() => checkInstaContext());
cargoLockWatcher.onDidCreate(() => checkInstaContext());
cargoLockWatcher.onDidDelete(() => checkInstaContext());

if (root) {
projectUsesInsta(root.uri).then((usesInsta) => setInstaContext(usesInsta));
}
findCargoRoots().then((roots) => {
if (roots.length !== 0) {
projectUsesInsta(roots).then((usesInsta) => setInstaContext(usesInsta));
}
})

context.subscriptions.push(
snapWatcher,
cargoTomlWatcher,
cargoLockWatcher,
window.registerTreeDataProvider("pendingInstaSnapshots", pendingSnapshots),
workspace.registerTextDocumentContentProvider(
"instaInlineSnapshot",
Expand Down
40 changes: 21 additions & 19 deletions vscode-insta/src/insta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as cp from "child_process";
import { Uri } from "vscode";
import { Snapshot } from "./Snapshot";

export function getPendingSnapshots(root: Uri): Promise<Snapshot[]> {
async function getPendingSnapshotsFor(results: Snapshot[], root: Uri): Promise<void> {
return new Promise((resolve, reject) => {
let buffer = "";
const child = cp.spawn(
Expand All @@ -19,21 +19,24 @@ export function getPendingSnapshots(root: Uri): Promise<Snapshot[]> {
child.stdout?.setEncoding("utf8");
child.stdout.on("data", (data) => (buffer += data));
child.on("close", (_exitCode) => {
const snapshots = buffer
.split(/\n/g)
.map((line) => {
try {
return new Snapshot(root, JSON.parse(line));
} catch (e) {
return null;
}
})
.filter((x) => x !== null);
resolve(snapshots as any);
for (const line of buffer.split(/\n/g)) {
try {
results.push(new Snapshot(root, JSON.parse(line)))
} catch (e) {
console.error(e);
}
}
resolve();
});
});
}

export async function getPendingSnapshots(roots: Uri[]): Promise<Snapshot[]> {
const results: Snapshot[] = [];
await Promise.all(roots.map((root) => getPendingSnapshotsFor(results, root)));
return results;
}

export function processInlineSnapshot(
snapshot: Snapshot,
op: "accept" | "reject"
Expand All @@ -55,20 +58,19 @@ export function processInlineSnapshot(
});
}

export function processAllSnapshots(
rootUri: Uri,
export async function processAllSnapshots(
roots: Uri[],
op: "accept" | "reject"
): Promise<boolean> {
return new Promise((resolve, reject) => {
const results = await Promise.all(roots.map((rootUri) => new Promise<boolean>((resolve, reject) => {
const child = cp.spawn("cargo", ["insta", op], {
cwd: rootUri.fsPath,
});
if (!child) {
reject(new Error("could not spawn cargo-insta"));
return;
}
child.on("close", (exitCode) => {
resolve(exitCode === 0);
});
});
child.on("close", (exitCode) => resolve(exitCode === 0));
})));
return results.every((x) => x);
}

0 comments on commit 430e8ab

Please sign in to comment.