Skip to content
This repository has been archived by the owner on May 9, 2021. It is now read-only.

Commit

Permalink
feat: multi root workspaces
Browse files Browse the repository at this point in the history
Fixes #7
  • Loading branch information
hdevalke committed Nov 16, 2019
1 parent ffb9172 commit 49b92e6
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 68 deletions.
5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: node_js
node_js:
- '8'
- '10'
sudo: false
os:
- linux
Expand All @@ -10,8 +10,7 @@ before_install:
install:
- npm install
- "./node_modules/.bin/vsce package"
script:
- npm test --silent
script: 'true'
deploy:
provider: releases
api_key:
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

All notable changes to the "rust-test-lens" extension will be documented in this file.

Check [Keep a Changelog](https://keepachangelog.com/) for recommendations on how to structure this file.
## 0.2.0

- Support for multi root workspaces ([#7](https://github.com/hdevalke/rust-test-lens/issues/7))

## 0.1.2

Expand Down
19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
"name": "rust-test-lens",
"displayName": "Rust Test Lens",
"description": "Adds a code lens to quickly run or debug a single test for your Rust code.",
"version": "0.1.2",
"version": "0.2.0",
"publisher": "hdevalke",
"engines": {
"vscode": "^1.30.2"
"vscode": "^1.32.0"
},
"categories": [
"Other",
Expand All @@ -15,7 +15,8 @@
"Rust",
"debug",
"codelens",
"lldb"
"lldb",
"multi-root ready"
],
"activationEvents": [
"onCommand:extension.debugTest",
Expand All @@ -35,12 +36,12 @@
"test": "npm run compile && node ./node_modules/vscode/bin/test"
},
"devDependencies": {
"@types/mocha": "^5.2.5",
"@types/node": "^10.12.18",
"tslint": "^5.12.1",
"typescript": "^3.2.4",
"vsce": "^1.54.0",
"vscode": "^1.1.27"
"@types/mocha": "^5.2.7",
"@types/node": "^10.17.5",
"tslint": "^5.20.1",
"typescript": "^3.7.2",
"vsce": "^1.69.0",
"vscode": "^1.1.36"
},
"extensionDependencies": [
"vadimcn.vscode-lldb"
Expand Down
15 changes: 9 additions & 6 deletions src/RustCodeLensProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class RustCodeLensProvider implements CodeLensProvider {
const start = doc.positionAt(startIdx);
const end = doc.positionAt(index);
const range = new Range(start, end);
const debugConfig = this.createDebugConfig(fn, doc.fileName);
const debugConfig = this.createDebugConfig(fn, doc);
if (debugConfig) {
return new CodeLens(range, {
title: 'Debug',
Expand All @@ -76,8 +76,9 @@ export class RustCodeLensProvider implements CodeLensProvider {
}
}

createDebugConfig(fn: string, uri: string): DebugConfiguration | undefined {
const pkg = this.rustTests.getPackage(fn, uri);
createDebugConfig(fn: string, doc: TextDocument):
DebugConfiguration | undefined {
const pkg = this.rustTests.getPackage(fn, doc.uri);
if (pkg) {
const args = fn === "main"
? [
Expand All @@ -90,8 +91,8 @@ export class RustCodeLensProvider implements CodeLensProvider {
`--package=${pkg.name}`
];

const bin = this.rustTests.getBin(uri, pkg);
const filter = this.rustTests.getFilter(uri, pkg, bin);
const bin = this.rustTests.getBin(doc.fileName, pkg);
const filter = this.rustTests.getFilter(doc.fileName, pkg, bin);

if (bin !== undefined && filter.kind === "bin") {
args.push(`--bin=${bin}`);
Expand All @@ -100,10 +101,12 @@ export class RustCodeLensProvider implements CodeLensProvider {
args.push(`--example=${bin}`);
}

args.push(`--manifest-path=${pkg.manifest_path}`);

return {
type: "lldb",
request: "launch",
name: `Debug ${fn} in ${basename(uri)}`,
name: `Debug ${fn} in ${basename(doc.fileName)}`,
cargo: {
args: args,
filter: filter,
Expand Down
35 changes: 25 additions & 10 deletions src/RustTests.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,51 @@
'use strict';
import { Metadata, Package } from "./cargo";
import { basename, dirname } from "path";
import { WorkspaceFolder, workspace, Uri } from "vscode";

export interface Filter {
kind: undefined | string;
name: undefined | string;
}

export class RustTests {
private readonly testMap: Map<string, Package> = new Map<string, Package>();
constructor(private metadata: Metadata) {

private readonly testMap: Map<WorkspaceFolder, Map<string, Package>> =
new Map<WorkspaceFolder, Map<string, Package>>();

constructor(private metadata_map: Map<WorkspaceFolder, Metadata>) {
// sort packages by manifest path length to match the longest path.
metadata.packages.sort((a, b) => {
metadata_map.forEach((metadata) => metadata.packages.sort((a, b) => {
return b.manifest_path.length - a.manifest_path.length;
});
}));
}
/**
* Get the package based on the function name.
* @param fn function name
*/
getPackage(fn: string, uri: string): Package | undefined {
let pkg = this.testMap.get(`${uri}::${fn}`);
getPackage(fn: string, uri: Uri): Package | undefined {
const cws = workspace.getWorkspaceFolder(uri);
if (cws === undefined) {
return undefined;
}
let pkg_map = this.testMap.get(cws);
if (pkg_map === undefined) {
pkg_map = new Map<string, Package>();
this.testMap.set(cws, pkg_map);
}
const fsPath = uri.fsPath;
let pkg = pkg_map.get(`${fsPath}::${fn}`);
if (!pkg) {
if (this.metadata) {
for (pkg of this.metadata.packages) {
const metadata = this.metadata_map.get(cws);
if (metadata) {
for (pkg of metadata.packages) {
let pkg_dir = dirname(pkg.manifest_path);
if (uri.startsWith(pkg_dir)) {
if (fsPath.startsWith(pkg_dir)) {
break;
}
}
if (pkg) {
this.testMap.set(`${uri}::${fn}`, pkg);
pkg_map.set(`${uri}::${fn}`, pkg);
}
}
}
Expand Down
21 changes: 11 additions & 10 deletions src/cargo.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';
// Helper function and interfaces to work with cargo metadata.
import { workspace } from "vscode";
import { WorkspaceFolder } from "vscode";
import { spawn, SpawnOptions } from "child_process";

export interface Target {
Expand All @@ -27,35 +27,36 @@ export interface Metadata {

type StrSink = (data: string) => void;

export async function metadata(onStdErr?: StrSink,
export async function metadata(cws?: WorkspaceFolder, onStdErr?: StrSink,
retry = false): Promise<Metadata> {
let meta = "";
const cargoArgs = [
"metadata",
"--no-deps",
"--format-version=1"];
return runCargo(cargoArgs, data => meta += data, onStdErr)
return runCargo(cws, cargoArgs, data => meta += data, onStdErr)
.then(_ => JSON.parse(meta))
.catch((reason) => {
if (onStdErr) {
onStdErr(`Couldn't get metadata: ${reason}.
Cargo command run: cargo ${cargoArgs.join(' ')}
onStdErr(`Couldn't get metadata: ${reason}.
Cargo command run: cargo ${cargoArgs.join(' ')}
Metadata read so far: ${meta}`);
}
if (retry) {
return metadata(onStdErr);
return metadata(cws, onStdErr);
} else {
return Promise.reject(reason);
}
});
}

async function runCargo(args?: ReadonlyArray<string>, onStdOut?: StrSink,
onStdErr?: StrSink): Promise<number> {
async function runCargo(workspaceFolder?: WorkspaceFolder,
args?: ReadonlyArray<string>, onStdOut?: StrSink,
onStdErr?: StrSink): Promise<number> {
return new Promise<number>((resolve, reject) => {
const workspaceFolders = workspace.workspaceFolders;
const cwd = workspaceFolder ? workspaceFolder.uri.fsPath : undefined;
const options: SpawnOptions = {
cwd: workspaceFolders ? workspaceFolders[0].uri.fsPath : undefined,
cwd,
stdio: ['ignore', 'pipe', 'pipe'],
};

Expand Down
57 changes: 36 additions & 21 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,54 @@
import {
commands, EventEmitter, ExtensionContext, languages, window, debug,
OutputChannel,
workspace
workspace,
WorkspaceFolder
} from 'vscode';
import { RustCodeLensProvider } from './RustCodeLensProvider';
import { RustTests } from './RustTests';
import { metadata } from './cargo';
import { metadata, Metadata } from './cargo';
import * as fs from "fs";
import * as path from "path";

const onDidChange: EventEmitter<void> = new EventEmitter<void>();
export const outputChannel: OutputChannel =
window.createOutputChannel('Rust Test Output');

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: ExtensionContext) {
export async function activate(context: ExtensionContext) {
const config = workspace.getConfiguration("rust-test-lens");
if (config.get("enabled", true)) {
metadata(err => outputChannel.append(err), true)
.then(meta => {
const rustTests: RustTests = new RustTests(meta);
const codeLensProvider = new RustCodeLensProvider(onDidChange,
rustTests);
context.subscriptions.push(languages.registerCodeLensProvider(
{ scheme: 'file', language: 'rust' }, codeLensProvider));
});

let disposable = commands.registerCommand('extension.debugTest',
(debugConfig) => {
const dbgConfigJson = JSON.stringify(debugConfig, null, 2);
outputChannel.appendLine(`Debugging: ${dbgConfigJson}`);
debug.startDebugging(undefined, debugConfig)
.then((r) => console.log("Result", r));
});

context.subscriptions.push(disposable);
if (workspace.workspaceFolders) {
const metaMap = new Map<WorkspaceFolder, Metadata>();
for (const ws of workspace.workspaceFolders) {
if (fs.existsSync(path.join(ws.uri.fsPath, "Cargo.toml"))) {
let meta = await metadata(ws,
err => outputChannel.append(err), true);
metaMap.set(ws, meta);
}
}
if (metaMap.size !== 0) {
const rustTests: RustTests = new RustTests(metaMap);
const codeLensProvider =
new RustCodeLensProvider(onDidChange, rustTests);
context.subscriptions.push(
languages.registerCodeLensProvider(
{ scheme: 'file', language: 'rust' },
codeLensProvider));
let disposable = commands.registerCommand('extension.debugTest',
(debugConfig) => {
const json = JSON.stringify(debugConfig, null, 2);
outputChannel.appendLine(`Debugging: ${json}`);
debug.startDebugging(undefined, debugConfig)
.then((r) => console.log("Result", r));
});
context.subscriptions.push(disposable);
}
}
else {
outputChannel.append("Only workspaces with a `./Cargo.toml` file are supported.");
}
}
}

Expand Down
31 changes: 23 additions & 8 deletions src/test/RustTests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@ import * as assert from 'assert';

import { Metadata } from '../cargo';
import { RustTests } from '../RustTests';
import { WorkspaceFolder, Uri } from 'vscode';

suite("RustTests Tests", function () {

/// skipping tests
/// have to migrate to vscode-test
/// https://code.visualstudio.com/api/working-with-extensions/testing-extension#migrating-from-vscode
suite.skip("RustTests Tests", function () {
const workspaceFolder : WorkspaceFolder = {
uri: Uri.file('./root_crate'),
name: 'name',
index: 0
};
const metadata: Metadata = {
packages: [{
name: "other_sub_crate",
Expand Down Expand Up @@ -38,28 +46,35 @@ suite("RustTests Tests", function () {
};

test("getPackage root test", function () {
let rustTests = new RustTests(metadata);
let pkg = rustTests.getPackage("test_in_root", "./root_crate/test.rs");
let map = new Map<WorkspaceFolder, Metadata>();
map.set(workspaceFolder, metadata);
let rustTests = new RustTests(map);
let pkg = rustTests.getPackage("test_in_root",
Uri.file("./root_crate/test.rs"));
if (pkg === undefined) {
assert.fail("A package should be found");
} else {
assert.equal("root_crate", pkg.name);
}
});
test("getPackage sub_crate test", function () {
let rustTests = new RustTests(metadata);
let map = new Map<WorkspaceFolder, Metadata>();
map.set(workspaceFolder, metadata);
let rustTests = new RustTests(map);
let pkg = rustTests.getPackage("test_in_sub_crate",
"./root_crate/sub_crate/test.rs");
Uri.file("./root_crate/sub_crate/test.rs"));
if (pkg === undefined) {
assert.fail("A package should be found");
} else {
assert.equal("sub_crate", pkg.name);
}
});
test("getPackage other_sub_crate test", function () {
let rustTests = new RustTests(metadata);
let map = new Map<WorkspaceFolder, Metadata>();
map.set(workspaceFolder, metadata);
let rustTests = new RustTests(map);
let pkg = rustTests.getPackage("test_in_other_sub_crate",
"./root_crate/other_sub_crate/test.rs");
Uri.file("./root_crate/other_sub_crate/test.rs"));
if (pkg === undefined) {
assert.fail("A package should be found");
} else {
Expand Down

0 comments on commit 49b92e6

Please sign in to comment.