Skip to content

Commit 14e6ff8

Browse files
committed
Improve batch execution of commands
Improves "pub get", "pub upgrade" and "flutter clean" when run in-batch. We now produce a single notification and report the names of the projects as we go through them. This also means that cancellation works correctly. Fixes #5745 Fixes #5740
1 parent 406b911 commit 14e6ff8

3 files changed

Lines changed: 121 additions & 44 deletions

File tree

‎src/extension/commands/flutter.ts‎

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { SdkUtils } from "../sdk/utils";
2424
import * as util from "../utils";
2525
import { PickableSetting, showInputBoxWithSettings, showSimpleSettingsEditor } from "../utils/vscode/input";
2626
import { getFolderToRunCommandIn } from "../utils/vscode/projects";
27-
import { BaseSdkCommands, commandState, packageNameRegex } from "./sdk";
27+
import { BaseSdkCommands, commandState, OperationProgress, packageNameRegex } from "./sdk";
2828

2929
export class FlutterCommands extends BaseSdkCommands {
3030
private flutterScreenshotPath?: string;
@@ -46,10 +46,28 @@ export class FlutterCommands extends BaseSdkCommands {
4646
this.disposables.push(vs.commands.registerCommand("_flutter.clean", this.flutterClean.bind(this)));
4747
}
4848

49-
private async flutterClean(uri: vs.Uri | vs.Uri[] | undefined): Promise<RunProcessResult | undefined> {
49+
private async flutterClean(
50+
uri: vs.Uri | vs.Uri[] | undefined,
51+
operationProgress?: OperationProgress,
52+
): Promise<RunProcessResult | undefined> {
53+
// If we don't have a parent progress, add one.
54+
if (!operationProgress) {
55+
await vs.window.withProgress({
56+
cancellable: true,
57+
location: vs.ProgressLocation.Notification,
58+
title: "flutter clean",
59+
}, (progress, token) => this.flutterClean(uri, { progressReporter: progress, cancellationToken: token }));
60+
return;
61+
}
62+
63+
// If we are a batch, run for each item.
5064
if (Array.isArray(uri)) {
51-
for (const item of uri)
52-
await this.flutterClean(item);
65+
for (const item of uri) {
66+
if (operationProgress.cancellationToken.isCancellationRequested)
67+
break;
68+
69+
await this.flutterClean(item, operationProgress);
70+
}
5371
return;
5472
}
5573

@@ -60,7 +78,7 @@ export class FlutterCommands extends BaseSdkCommands {
6078
uri = vs.Uri.file(path);
6179
}
6280

63-
return this.runFlutter(["clean"], uri);
81+
return this.runFlutter(["clean"], uri, false, operationProgress);
6482
}
6583

6684
private async flutterCleanAllProjects(): Promise<void> {
@@ -137,7 +155,7 @@ export class FlutterCommands extends BaseSdkCommands {
137155
const tempDir = path.join(os.tmpdir(), "dart-code-cmd-run");
138156
if (!fs.existsSync(tempDir))
139157
fs.mkdirSync(tempDir);
140-
return this.runFlutterInFolder(tempDir, ["doctor", "-v"], "flutter", true, this.workspace.config?.flutterDoctorScript);
158+
return this.runFlutterInFolder(tempDir, ["doctor", "-v"], "flutter", true, undefined, this.workspace.config?.flutterDoctorScript);
141159
}
142160

143161
private async flutterUpgrade() {

‎src/extension/commands/packages.ts‎

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { config } from "../config";
1212
import * as util from "../utils";
1313
import { getExcludedFolders } from "../utils";
1414
import { getFolderToRunCommandIn } from "../utils/vscode/projects";
15-
import { BaseSdkCommands, commandState } from "./sdk";
15+
import { BaseSdkCommands, commandState, OperationProgress } from "./sdk";
1616

1717
let isFetchingPackages = false;
1818
let runPubGetDelayTimer: NodeJS.Timeout | undefined;
@@ -49,13 +49,31 @@ export class PackageCommands extends BaseSdkCommands {
4949
this.setupPubspecWatcher();
5050
}
5151

52-
private async getPackages(uri: string | vs.Uri | vs.Uri[] | undefined) {
52+
private async getPackages(
53+
uri: string | vs.Uri | vs.Uri[] | undefined,
54+
operationProgress?: OperationProgress,
55+
) {
5356
if (!config.enablePub)
5457
return;
5558

59+
// If we don't have a parent progress, add one.
60+
if (!operationProgress) {
61+
await vs.window.withProgress({
62+
cancellable: true,
63+
location: vs.ProgressLocation.Notification,
64+
title: "pub get",
65+
}, (progress, token) => this.getPackages(uri, { progressReporter: progress, cancellationToken: token }));
66+
return;
67+
}
68+
69+
// If we are a batch, run for each item.
5670
if (Array.isArray(uri)) {
57-
for (const item of uri) {
58-
await this.getPackages(item);
71+
const uris = uri.map((item) => typeof item === "string" ? vs.Uri.file(item) : item);
72+
for (const item of uris) {
73+
if (operationProgress.cancellationToken.isCancellationRequested)
74+
break;
75+
76+
await this.getPackages(item, operationProgress);
5977
}
6078
return;
6179
}
@@ -69,21 +87,24 @@ export class PackageCommands extends BaseSdkCommands {
6987
if (typeof uri === "string")
7088
uri = vs.Uri.file(uri);
7189

90+
return this.getPackagesForUri(uri, operationProgress);
91+
}
92+
93+
private async getPackagesForUri(uri: vs.Uri, operationProgress?: OperationProgress) {
7294
// Exclude folders we should never run pub get for.
7395
if (!isValidPubGetTarget(uri).valid)
7496
return;
7597

76-
const additionalArgs = [];
98+
const additionalArgs: string[] = [];
7799
if (config.offline)
78100
additionalArgs.push("--offline");
79101
if (this.dartCapabilities.needsNoExampleForPubGet)
80102
additionalArgs.push("--no-example");
81103

82-
if (util.isInsideFlutterProject(uri)) {
83-
return this.runFlutter(["pub", "get", ...additionalArgs], uri);
84-
} else {
85-
return this.runPub(["get", ...additionalArgs], uri);
86-
}
104+
if (util.isInsideFlutterProject(uri))
105+
return this.runFlutter(["pub", "get", ...additionalArgs], uri, false, operationProgress);
106+
else
107+
return this.runPub(["get", ...additionalArgs], uri, false, operationProgress);
87108
}
88109

89110
private async getPackagesForAllProjects() {
@@ -114,13 +135,31 @@ export class PackageCommands extends BaseSdkCommands {
114135
return this.runPub(["outdated"], uri, true);
115136
}
116137

117-
private async upgradePackages(uri: string | vs.Uri | vs.Uri[] | undefined) {
138+
private async upgradePackages(
139+
uri: string | vs.Uri | vs.Uri[] | undefined,
140+
operationProgress?: OperationProgress
141+
) {
118142
if (!config.enablePub)
119143
return;
120144

145+
// If we don't have a parent progress, add one.
146+
if (!operationProgress) {
147+
await vs.window.withProgress({
148+
cancellable: true,
149+
location: vs.ProgressLocation.Notification,
150+
title: "pub upgrade",
151+
}, (progress, token) => this.upgradePackages(uri, { progressReporter: progress, cancellationToken: token }));
152+
return;
153+
}
154+
155+
// If we are a batch, run for each item.
121156
if (Array.isArray(uri)) {
122-
for (const item of uri) {
123-
await this.upgradePackages(item);
157+
const uris = uri.map((item) => typeof item === "string" ? vs.Uri.file(item) : item);
158+
for (const item of uris) {
159+
if (operationProgress.cancellationToken.isCancellationRequested)
160+
break;
161+
162+
await this.upgradePackages(item, operationProgress);
124163
}
125164
return;
126165
}
@@ -134,14 +173,18 @@ export class PackageCommands extends BaseSdkCommands {
134173
if (typeof uri === "string")
135174
uri = vs.Uri.file(uri);
136175

176+
return this.upgradePackagesForUri(uri, operationProgress);
177+
}
178+
179+
private async upgradePackagesForUri(uri: vs.Uri, operationProgress?: OperationProgress) {
137180
// Exclude folders we should never run pub get for.
138181
if (!isValidPubGetTarget(uri).valid)
139182
return;
140183

141184
if (util.isInsideFlutterProject(uri))
142-
return this.runFlutter(["pub", "upgrade"], uri);
185+
return this.runFlutter(["pub", "upgrade"], uri, false, operationProgress);
143186
else
144-
return this.runPub(["upgrade"], uri);
187+
return this.runPub(["upgrade"], uri, false, operationProgress);
145188
}
146189

147190
private async upgradePackagesMajorVersions(uri: string | vs.Uri | undefined) {

‎src/extension/commands/sdk.ts‎

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export const commandState = {
2525
promptToReloadOnVersionChanges: true,
2626
};
2727

28+
export interface OperationProgress {
29+
progressReporter: vs.Progress<{ message?: string; increment?: number }>;
30+
cancellationToken: vs.CancellationToken;
31+
}
32+
2833
export class BaseSdkCommands implements IAmDisposable {
2934
protected readonly sdks: DartSdks;
3035
protected readonly disposables: vs.Disposable[] = [];
@@ -37,11 +42,12 @@ export class BaseSdkCommands implements IAmDisposable {
3742
}
3843

3944
protected async runCommandForWorkspace(
40-
handler: (folder: string, args: string[], shortPath: string, alwaysShowOutput: boolean) => Promise<RunProcessResult | undefined>,
45+
handler: (folder: string, args: string[], shortPath: string, alwaysShowOutput: boolean, operationProgress?: OperationProgress) => Promise<RunProcessResult | undefined>,
4146
placeHolder: string,
4247
args: string[],
4348
selection: vs.Uri | undefined,
4449
alwaysShowOutput = false,
50+
operationProgress?: OperationProgress,
4551
): Promise<RunProcessResult | undefined> {
4652
const folderToRunCommandIn = await getFolderToRunCommandIn(this.logger, placeHolder, selection);
4753
if (!folderToRunCommandIn)
@@ -50,20 +56,25 @@ export class BaseSdkCommands implements IAmDisposable {
5056
const containingWorkspace = vs.workspace.getWorkspaceFolder(vs.Uri.file(folderToRunCommandIn));
5157
const containingWorkspacePath = containingWorkspace ? fsPath(containingWorkspace.uri) : undefined;
5258

53-
// Display the relative path from the workspace root to the folder we're running, or if they're
54-
// the same then the folder name we're running in.
55-
const shortPath = containingWorkspacePath
56-
? path.relative(containingWorkspacePath, folderToRunCommandIn) || path.basename(folderToRunCommandIn)
57-
: path.basename(folderToRunCommandIn);
59+
// Display the relative path from the workspace root to the folder we're running up to two segments.
60+
let shortPath = path.basename(folderToRunCommandIn);
61+
if (containingWorkspacePath) {
62+
const relativePath = path.relative(containingWorkspacePath, folderToRunCommandIn);
63+
if (relativePath) {
64+
shortPath = relativePath.includes(path.sep)
65+
? relativePath.split(path.sep).slice(-2).join(path.sep)
66+
: relativePath;
67+
}
68+
}
5869

59-
return handler(folderToRunCommandIn, args, shortPath, alwaysShowOutput);
70+
return handler(folderToRunCommandIn, args, shortPath, alwaysShowOutput, operationProgress);
6071
}
6172

62-
protected runFlutter(args: string[], selection: vs.Uri | undefined, alwaysShowOutput = false): Promise<RunProcessResult | undefined> {
63-
return this.runCommandForWorkspace(this.runFlutterInFolder.bind(this), `Select the folder to run "flutter ${args.join(" ")}" in`, args, selection, alwaysShowOutput);
73+
protected runFlutter(args: string[], selection: vs.Uri | undefined, alwaysShowOutput = false, operationProgress?: OperationProgress): Promise<RunProcessResult | undefined> {
74+
return this.runCommandForWorkspace(this.runFlutterInFolder.bind(this), `Select the folder to run "flutter ${args.join(" ")}" in`, args, selection, alwaysShowOutput, operationProgress);
6475
}
6576

66-
protected runFlutterInFolder(folder: string, args: string[], shortPath: string | undefined, alwaysShowOutput = false, customScript?: CustomScript): Promise<RunProcessResult | undefined> {
77+
protected runFlutterInFolder(folder: string, args: string[], shortPath: string | undefined, alwaysShowOutput = false, operationProgress?: OperationProgress, customScript?: CustomScript): Promise<RunProcessResult | undefined> {
6778
if (!this.sdks.flutter)
6879
throw new Error("Flutter SDK not available");
6980

@@ -77,25 +88,25 @@ export class BaseSdkCommands implements IAmDisposable {
7788
.concat(config.for(vs.Uri.file(folder)).flutterAdditionalArgs)
7889
.concat(execution.args);
7990

80-
return this.runCommandInFolder(shortPath, folder, execution.executable, allArgs, alwaysShowOutput);
91+
return this.runCommandInFolder(shortPath, folder, execution.executable, allArgs, alwaysShowOutput, operationProgress);
8192
}
8293

83-
protected runPub(args: string[], selection: vs.Uri | undefined, alwaysShowOutput = false): Promise<RunProcessResult | undefined> {
84-
return this.runCommandForWorkspace(this.runPubInFolder.bind(this), `Select the folder to run "pub ${args.join(" ")}" in`, args, selection, alwaysShowOutput);
94+
protected runPub(args: string[], selection: vs.Uri | undefined, alwaysShowOutput = false, operationProgress?: OperationProgress): Promise<RunProcessResult | undefined> {
95+
return this.runCommandForWorkspace(this.runPubInFolder.bind(this), `Select the folder to run "pub ${args.join(" ")}" in`, args, selection, alwaysShowOutput, operationProgress);
8596
}
8697

87-
protected runPubInFolder(folder: string, args: string[], shortPath: string, alwaysShowOutput = false): Promise<RunProcessResult | undefined> {
98+
protected runPubInFolder(folder: string, args: string[], shortPath: string, alwaysShowOutput = false, operationProgress?: OperationProgress): Promise<RunProcessResult | undefined> {
8899
if (!this.sdks.dart)
89100
throw new Error("Dart SDK not available");
90101

91102
args = args.concat(...config.for(vs.Uri.file(folder)).pubAdditionalArgs);
92103

93104
const pubExecution = getPubExecutionInfo(this.dartCapabilities, this.sdks.dart, args);
94105

95-
return this.runCommandInFolder(shortPath, folder, pubExecution.executable, pubExecution.args, alwaysShowOutput);
106+
return this.runCommandInFolder(shortPath, folder, pubExecution.executable, pubExecution.args, alwaysShowOutput, operationProgress);
96107
}
97108

98-
protected async runCommandInFolder(shortPath: string | undefined, folder: string, binPath: string, args: string[], alwaysShowOutput: boolean): Promise<RunProcessResult | undefined> {
109+
protected async runCommandInFolder(shortPath: string | undefined, folder: string, binPath: string, args: string[], alwaysShowOutput: boolean, operationProgress?: OperationProgress): Promise<RunProcessResult | undefined> {
99110
shortPath = shortPath || path.basename(folder);
100111
const commandName = path.basename(binPath).split(".")[0]; // Trim file extension.
101112

@@ -113,11 +124,7 @@ export class BaseSdkCommands implements IAmDisposable {
113124
return Promise.resolve(undefined);
114125
}
115126

116-
return await vs.window.withProgress({
117-
cancellable: true,
118-
location: vs.ProgressLocation.Notification,
119-
title: `${commandName} ${args.join(" ")}`,
120-
}, (progress, token) => {
127+
const runWithProgress = async (progress: vs.Progress<{ message?: string; increment?: number }>, token: vs.CancellationToken) => {
121128
if (existingProcess) {
122129
progress.report({ message: "terminating previous command..." });
123130
existingProcess.cancel();
@@ -127,7 +134,7 @@ export class BaseSdkCommands implements IAmDisposable {
127134

128135
const process = new ChainedProcess(() => {
129136
channel.appendLine(`[${shortPath}] ${commandName} ${args.join(" ")}`);
130-
progress.report({ message: "running..." });
137+
progress.report({ message: `${shortPath}...` });
131138
const proc = safeToolSpawn(folder, binPath, args);
132139
channels.runProcessInOutputChannel(proc, channel);
133140
this.logger.info(`(PROC ${proc.pid}) Spawned ${binPath} ${args.join(" ")} in ${folder}`, LogCategory.CommandProcesses);
@@ -148,7 +155,16 @@ export class BaseSdkCommands implements IAmDisposable {
148155
token.onCancellationRequested(() => process.cancel());
149156

150157
return process.completed;
151-
});
158+
};
159+
160+
if (operationProgress)
161+
return runWithProgress(operationProgress.progressReporter, operationProgress.cancellationToken);
162+
163+
return await vs.window.withProgress({
164+
cancellable: true,
165+
location: vs.ProgressLocation.Notification,
166+
title: `${commandName} ${args.join(" ")}`,
167+
}, (progress, token) => runWithProgress(progress, token));
152168
}
153169

154170
public dispose(): any {

0 commit comments

Comments
 (0)