diff --git a/package.json b/package.json
index 39fa3d0f..a0e9c724 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,11 @@
},
{
"command": "mesonbuild.build",
- "title": "Meson: Build"
+ "title": "Meson: Build",
+ "icon": {
+ "dark": "res/build-icon-dark.svg",
+ "light": "res/build-icon-light.svg"
+ }
},
{
"command": "mesonbuild.test",
@@ -87,6 +91,36 @@
{
"command": "mesonbuild.restartLanguageServer",
"title": "Meson: Restart Language Server"
+ },
+ {
+ "command": "mesonbuild.node.reconfigure",
+ "title": "Reconfigure",
+ "icon": {
+ "dark": "res/meson_32.svg",
+ "light": "res/meson_32.svg"
+ }
+ },
+ {
+ "command": "mesonbuild.node.build",
+ "title": "Build",
+ "icon": {
+ "dark": "res/build-icon-dark.svg",
+ "light": "res/build-icon-light.svg"
+ }
+ },
+ {
+ "command": "mesonbuild.node.clean",
+ "title": "Clean"
+ },
+ {
+ "command": "mesonbuild.node.runAll",
+ "title": "Run all",
+ "icon": "$(testing-run-all-icon)"
+ },
+ {
+ "command": "mesonbuild.node.run",
+ "title": "Run",
+ "icon": "$(testing-run-icon)"
}
],
"configuration": {
@@ -388,10 +422,65 @@
},
"menus": {
"view/item/context": [
+ {
+ "command": "mesonbuild.node.reconfigure",
+ "when": "view == meson-project && viewItem == meson-projectroot",
+ "group": "build@0"
+ },
+ {
+ "command": "mesonbuild.node.build",
+ "when": "view == meson-project && viewItem == meson-projectroot",
+ "group": "inline"
+ },
+ {
+ "command": "mesonbuild.node.build",
+ "when": "view == meson-project && viewItem == meson-projectroot",
+ "group": "build@1"
+ },
+ {
+ "command": "mesonbuild.node.clean",
+ "when": "view == meson-project && viewItem == meson-projectroot",
+ "group": "build@2"
+ },
+ {
+ "command": "mesonbuild.node.build",
+ "when": "view == meson-project && viewItem == meson-target",
+ "group": "inline@0"
+ },
{
"command": "mesonbuild.openBuildFile",
"when": "view == meson-project && viewItem == meson-target",
+ "group": "inline@1"
+ },
+ {
+ "command": "mesonbuild.node.build",
+ "when": "view == meson-project && viewItem == meson-target",
+ "group": "build@0"
+ },
+ {
+ "command": "mesonbuild.openBuildFile",
+ "when": "view == meson-project && viewItem == meson-target",
+ "group": "build@1"
+ },
+ {
+ "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 +494,26 @@
{
"command": "mesonbuild.openBuildFile",
"when": "false"
+ },
+ {
+ "command": "mesonbuild.node.reconfigure",
+ "when": "false"
+ },
+ {
+ "command": "mesonbuild.node.build",
+ "when": "false"
+ },
+ {
+ "command": "mesonbuild.node.clean",
+ "when": "false"
+ },
+ {
+ "command": "mesonbuild.node.run",
+ "when": "false"
+ },
+ {
+ "command": "mesonbuild.node.runAll",
+ "when": "false"
}
]
},
diff --git a/res/build-icon-dark.svg b/res/build-icon-dark.svg
new file mode 100644
index 00000000..2cd8a0c9
--- /dev/null
+++ b/res/build-icon-dark.svg
@@ -0,0 +1,8 @@
+
diff --git a/res/build-icon-light.svg b/res/build-icon-light.svg
new file mode 100644
index 00000000..79a8a633
--- /dev/null
+++ b/res/build-icon-light.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/extension.ts b/src/extension.ts
index 7f80ce91..8388cc24 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 { IBuildableNode, IRunnableNode } from "./treeview/nodes/base";
export let extensionPath: string;
export let workspaceState: vscode.Memento;
@@ -201,6 +202,31 @@ export async function activate(ctx: vscode.ExtensionContext) {
}),
);
+ ctx.subscriptions.push(
+ vscode.commands.registerCommand("mesonbuild.node.reconfigure", async () => {
+ runFirstTask("reconfigure");
+ }),
+ );
+
+ ctx.subscriptions.push(
+ vscode.commands.registerCommand("mesonbuild.node.build", async (node: IBuildableNode) => node.build()),
+ );
+
+ ctx.subscriptions.push(
+ vscode.commands.registerCommand("mesonbuild.node.clean", async () => {
+ runFirstTask("clean");
+ }),
+ );
+
+ // 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..fdf57b38 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";
@@ -68,29 +68,29 @@ function createReconfigureTask(buildDir: string, sourceDir: string) {
export async function getMesonTasks(buildDir: string, sourceDir: string) {
try {
const defaultBuildTask = new vscode.Task(
- { type: "meson", mode: "build" },
+ { type: "meson", mode: "build", target: pseudoAllTarget },
"Build all targets",
"Meson",
new vscode.ShellExecution(extensionConfiguration("mesonPath"), ["compile", "-C", buildDir]),
"$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..ae0b6ec1 100644
--- a/src/treeview/nodes/base.ts
+++ b/src/treeview/nodes/base.ts
@@ -31,3 +31,13 @@ export abstract class BaseDirectoryNode extends BaseNode {
abstract buildFileTree(fpaths: T[]): FolderMap | Thenable>;
}
+
+// A node in the meson tree view that can be built.
+export interface IBuildableNode {
+ build(): Thenable;
+}
+
+// A node in the meson tree view that can be run.
+export interface IRunnableNode {
+ run(): Thenable;
+}
diff --git a/src/treeview/nodes/targets.ts b/src/treeview/nodes/targets.ts
index d61eea57..695cd992 100644
--- a/src/treeview/nodes/targets.ts
+++ b/src/treeview/nodes/targets.ts
@@ -5,7 +5,7 @@ import { BaseNode } from "../basenode";
import { Target, Targets } from "../../types";
import { TargetSourcesRootNode, TargetGeneratedSourcesRootNode } from "./sources";
import { extensionRelative, getTargetName } from "../../utils";
-import { BaseDirectoryNode } from "./base";
+import { BaseDirectoryNode, IBuildableNode } from "./base";
export class TargetDirectoryNode extends BaseDirectoryNode {
constructor(parentId: string, folder: string, targets: Targets) {
@@ -72,7 +72,7 @@ export class TargetDirectoryNode extends BaseDirectoryNode {
}
}
-export class TargetNode extends BaseNode {
+export class TargetNode extends BaseNode implements IBuildableNode {
constructor(
parentId: string,
private readonly target: Target,
@@ -103,7 +103,7 @@ export class TargetNode extends BaseNode {
}
}
- override async getTreeItem() {
+ override getTreeItem() {
const item = super.getTreeItem() as vscode.TreeItem;
item.label = this.target.name;
@@ -112,17 +112,13 @@ export class TargetNode extends BaseNode {
item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
item.contextValue = "meson-target";
- const targetName = await getTargetName(this.target);
-
- item.command = {
- title: `Build ${this.target.name}`,
- command: "mesonbuild.build",
- arguments: [targetName],
- };
-
return item;
}
+ async build() {
+ return vscode.commands.executeCommand("mesonbuild.build", await getTargetName(this.target));
+ }
+
private getIconPath() {
switch (this.target.type) {
case "executable":
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/treeview/nodes/toplevel.ts b/src/treeview/nodes/toplevel.ts
index 4290b0b2..96214bdf 100644
--- a/src/treeview/nodes/toplevel.ts
+++ b/src/treeview/nodes/toplevel.ts
@@ -1,13 +1,14 @@
import * as vscode from "vscode";
import { BaseNode } from "../basenode";
-import { ProjectInfo, Subproject, Targets, Tests } from "../../types";
+import { ProjectInfo, Subproject, Targets, Tests, pseudoAllTarget } from "../../types";
import { extensionRelative } from "../../utils";
import { TargetDirectoryNode } from "./targets";
import { getMesonBenchmarks, getMesonTargets, getMesonTests } from "../../introspection";
import { TestRootNode } from "./tests";
+import { IBuildableNode } from "./base";
-export class ProjectNode extends BaseNode {
+export class ProjectNode extends BaseNode implements IBuildableNode {
constructor(
private readonly project: ProjectInfo,
projectDir: string,
@@ -26,6 +27,9 @@ export class ProjectNode extends BaseNode {
item.iconPath = extensionRelative("res/meson_32.svg");
item.collapsibleState = vscode.TreeItemCollapsibleState.Expanded;
+ // To key in to "when": "view == meson-project && viewItem == test" in package.json.
+ item.contextValue = "meson-projectroot";
+
return item;
}
@@ -60,6 +64,10 @@ export class ProjectNode extends BaseNode {
return children;
}
+
+ build() {
+ return vscode.commands.executeCommand("mesonbuild.build", pseudoAllTarget);
+ }
}
class SubprojectsRootNode extends BaseNode {
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.