diff --git a/package.json b/package.json index 39fa3d0f..ffe1154c 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,16 @@ { "command": "mesonbuild.restartLanguageServer", "title": "Meson: Restart Language Server" + }, + { + "command": "mesonbuild.node.runAll", + "title": "Run all", + "icon": "$(testing-run-all-icon)" + }, + { + "command": "mesonbuild.node.run", + "title": "Run", + "icon": "$(testing-run-icon)" } ], "configuration": { @@ -392,6 +402,26 @@ "command": "mesonbuild.openBuildFile", "when": "view == meson-project && viewItem == meson-target", "group": "inline" + }, + { + "command": "mesonbuild.node.runAll", + "when": "view == meson-project && viewItem == meson-test-root", + "group": "inline" + }, + { + "command": "mesonbuild.node.runAll", + "when": "view == meson-project && viewItem == meson-test-root", + "group": "run" + }, + { + "command": "mesonbuild.node.run", + "when": "view == meson-project && viewItem == meson-test", + "group": "inline" + }, + { + "command": "mesonbuild.node.run", + "when": "view == meson-project && viewItem == meson-test", + "group": "run" } ], "view/title": [ @@ -405,6 +435,14 @@ { "command": "mesonbuild.openBuildFile", "when": "false" + }, + { + "command": "mesonbuild.node.run", + "when": "false" + }, + { + "command": "mesonbuild.node.runAll", + "when": "false" } ] }, diff --git a/src/extension.ts b/src/extension.ts index 7f80ce91..11e0d4ef 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,6 +21,7 @@ import { activateFormatters } from "./formatters"; import { SettingsKey, TaskQuickPickItem } from "./types"; import { createLanguageServerClient } from "./lsp/common"; import { dirname, relative } from "path"; +import { IRunnableNode } from "./treeview/nodes/base"; export let extensionPath: string; export let workspaceState: vscode.Memento; @@ -201,6 +202,15 @@ export async function activate(ctx: vscode.ExtensionContext) { }), ); + // Two commands just to have different icons. + ctx.subscriptions.push( + vscode.commands.registerCommand("mesonbuild.node.runAll", async (node: IRunnableNode) => node.run()), + ); + + ctx.subscriptions.push( + vscode.commands.registerCommand("mesonbuild.node.run", async (node: IRunnableNode) => node.run()), + ); + if (!checkMesonIsConfigured(buildDir)) { let configureOnOpen = configurationChosen || extensionConfiguration(SettingsKey.configureOnOpen); if (configureOnOpen === "ask") { diff --git a/src/tasks.ts b/src/tasks.ts index 6edd3c88..506fcf79 100644 --- a/src/tasks.ts +++ b/src/tasks.ts @@ -1,7 +1,7 @@ import * as vscode from "vscode"; import { getMesonTargets, getMesonTests, getMesonBenchmarks } from "./introspection"; import { extensionConfiguration, getOutputChannel, getTargetName, getEnvDict } from "./utils"; -import { Test, Target } from "./types"; +import { Test, Target, pseudoAllTarget } from "./types"; import { checkMesonIsConfigured } from "./utils"; import { workspaceState } from "./extension"; @@ -75,22 +75,22 @@ export async function getMesonTasks(buildDir: string, sourceDir: string) { "$meson-gcc", ); const defaultTestTask = new vscode.Task( - { type: "meson", mode: "test" }, + { type: "meson", mode: "test", target: pseudoAllTarget }, "Run all tests", "Meson", new vscode.ShellExecution( extensionConfiguration("mesonPath"), - ["test", ...extensionConfiguration("testOptions")], + ["test", ...extensionConfiguration("testOptions"), pseudoAllTarget], { cwd: buildDir }, ), ); const defaultBenchmarkTask = new vscode.Task( - { type: "meson", mode: "benchmark" }, + { type: "meson", mode: "benchmark", target: pseudoAllTarget }, "Run all benchmarks", "Meson", new vscode.ShellExecution( extensionConfiguration("mesonPath"), - ["test", "--benchmark", ...extensionConfiguration("benchmarkOptions")], + ["test", "--benchmark", ...extensionConfiguration("benchmarkOptions"), pseudoAllTarget], { cwd: buildDir }, ), ); diff --git a/src/treeview/nodes/base.ts b/src/treeview/nodes/base.ts index 866c94ac..29bd40b7 100644 --- a/src/treeview/nodes/base.ts +++ b/src/treeview/nodes/base.ts @@ -31,3 +31,8 @@ export abstract class BaseDirectoryNode extends BaseNode { abstract buildFileTree(fpaths: T[]): FolderMap | Thenable>; } + +// A node in the meson tree view that can be run. +export interface IRunnableNode { + run(): Thenable; +} diff --git a/src/treeview/nodes/tests.ts b/src/treeview/nodes/tests.ts index c6ee4bea..d50d0753 100644 --- a/src/treeview/nodes/tests.ts +++ b/src/treeview/nodes/tests.ts @@ -1,16 +1,21 @@ import * as vscode from "vscode"; import { BaseNode } from "../basenode"; -import { Test, Tests } from "../../types"; +import { Test, Tests, pseudoAllTarget } from "../../types"; import { extensionRelative } from "../../utils"; +import { IRunnableNode } from "./base"; -export class TestRootNode extends BaseNode { +function getTestCommand(isBenchmark: boolean): string { + return isBenchmark ? "benchmark" : "test"; +} + +export class TestRootNode extends BaseNode implements IRunnableNode { constructor( parentId: string, private readonly tests: Tests, private readonly isBenchmark: boolean, ) { - super(`${parentId}-${isBenchmark ? "benchmarks" : "tests"}`); + super(`${parentId}-${getTestCommand(isBenchmark)}`); } override getTreeItem() { @@ -21,39 +26,58 @@ export class TestRootNode extends BaseNode { item.collapsibleState = this.tests.length === 0 ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed; + // To key in to "when": "view == meson-project && viewItem == meson-test-root" in package.json. + item.contextValue = "meson-test-root"; + return item; } override getChildren() { return this.tests.map((test) => new TestNode(this.id, test, this.isBenchmark)); } + + run() { + return vscode.commands.executeCommand(`mesonbuild.${getTestCommand(this.isBenchmark)}`, pseudoAllTarget); + } } -class TestNode extends BaseNode { +class TestNode extends BaseNode implements IRunnableNode { + private readonly taskName: string; + private readonly command: string; + constructor( parentId: string, private readonly test: Test, private readonly isBenchmark: boolean, ) { super(`${parentId}-${test.suite[0]}-${test.name}`); + + this.command = getTestCommand(this.isBenchmark); + const project = this.test.suite[0].split(":")[0]; + this.taskName = `${project}:${this.test.name}`; } override getTreeItem() { const item = super.getTreeItem() as vscode.TreeItem; - const project = this.test.suite[0].split(":")[0]; - const name = `${project}:${this.test.name}`; item.label = this.test.name; item.iconPath = extensionRelative("res/meson_32.svg"); item.command = { - title: `Run ${this.isBenchmark ? "benchmark" : "test"}`, - command: `mesonbuild.${this.isBenchmark ? "benchmark" : "test"}`, - arguments: [name], + title: `Run ${this.command}`, + command: `mesonbuild.${this.command}`, + arguments: [this.taskName], }; // No children currently, so don't display toggle. item.collapsibleState = vscode.TreeItemCollapsibleState.None; + // To key in to "when": "view == meson-project && viewItem == meson-test" in package.json. + item.contextValue = "meson-test"; + return item; } + + run() { + return vscode.commands.executeCommand(`mesonbuild.${this.command}`, this.taskName); + } } diff --git a/src/types.ts b/src/types.ts index dd43f0cd..513d5a14 100644 --- a/src/types.ts +++ b/src/types.ts @@ -130,3 +130,5 @@ export enum SettingsKey { languageServer = "languageServer", configureOnOpen = "configureOnOpen", } + +export const pseudoAllTarget = "'*'"; // Quoted for for ShellExecution.