Skip to content

Commit 23d8048

Browse files
authored
Extract Ruby fallback cancellable flow (#2911)
### Motivation This PR makes the cancellable logic used in Chruby activation reusable, so that we can use it for two purposes 1. The fallback for when no `.ruby-version` is found (already in place) 2. The fallback for when the version specified in `.ruby-version` is not installed The cancellable flow does a lot of little things, like waiting a few seconds, allowing the user to cancel and then define fallbacks for the system or for the Ruby LSP and implementing it twice would result in a lot of duplication. ### Implementation This PR just extracts the cancellable flow into a function we can customize.
1 parent 35fc5e9 commit 23d8048

File tree

1 file changed

+30
-28
lines changed

1 file changed

+30
-28
lines changed

vscode/src/ruby/chruby.ts

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,13 @@ export class Chruby extends VersionManager {
5454
rubyUri = await this.findRubyUri(versionInfo);
5555
} else {
5656
try {
57-
const fallback = await this.fallbackToLatestRuby();
57+
const fallback = await this.fallbackWithCancellation(
58+
"No .ruby-version file found. Trying to fall back to latest installed Ruby in 10 seconds",
59+
"You can create a .ruby-version file in a parent directory to configure a fallback",
60+
this.findFallbackRuby.bind(this),
61+
this.rubyVersionError.bind(this),
62+
);
63+
5864
versionInfo = fallback.rubyVersion;
5965
rubyUri = fallback.uri;
6066
} catch (error: any) {
@@ -179,7 +185,12 @@ export class Chruby extends VersionManager {
179185
return undefined;
180186
}
181187

182-
private async fallbackToLatestRuby() {
188+
private async fallbackWithCancellation<T>(
189+
title: string,
190+
message: string,
191+
fallbackFn: () => Promise<T>,
192+
errorFn: () => Error,
193+
): Promise<T> {
183194
let gemfileContents;
184195

185196
try {
@@ -190,26 +201,22 @@ export class Chruby extends VersionManager {
190201
// The Gemfile doesn't exist
191202
}
192203

193-
// If the Gemfile includes ruby version restrictions, then trying to fall back to latest Ruby may lead to errors
204+
// If the Gemfile includes ruby version restrictions, then trying to fall back may lead to errors
194205
if (
195206
gemfileContents &&
196207
/^ruby(\s|\()("|')[\d.]+/.test(gemfileContents.toString())
197208
) {
198-
throw this.rubyVersionError();
209+
throw errorFn();
199210
}
200211

201212
const fallback = await vscode.window.withProgress(
202213
{
203-
title:
204-
"No .ruby-version found. Trying to fall back to latest installed Ruby in 10 seconds",
214+
title,
205215
location: vscode.ProgressLocation.Notification,
206216
cancellable: true,
207217
},
208218
async (progress, token) => {
209-
progress.report({
210-
message:
211-
"You can create a .ruby-version file in a parent directory to configure a fallback",
212-
});
219+
progress.report({ message });
213220

214221
// If they don't cancel, we wait 10 seconds before falling back so that they are aware of what's happening
215222
await new Promise<void>((resolve) => {
@@ -222,26 +229,20 @@ export class Chruby extends VersionManager {
222229
});
223230

224231
if (token.isCancellationRequested) {
225-
await this.handleCancelledFallback();
232+
await this.handleCancelledFallback(errorFn);
226233

227234
// We throw this error to be able to catch and re-run activation after the user has configured a fallback
228235
throw new RubyVersionCancellationError();
229236
}
230237

231-
const fallback = await this.findFallbackRuby();
232-
233-
if (!fallback) {
234-
throw new Error("Cannot find any Ruby installations");
235-
}
236-
237-
return fallback;
238+
return fallbackFn();
238239
},
239240
);
240241

241242
return fallback;
242243
}
243244

244-
private async handleCancelledFallback() {
245+
private async handleCancelledFallback(errorFn: () => Error) {
245246
const answer = await vscode.window.showInformationMessage(
246247
`The Ruby LSP requires a Ruby version to launch.
247248
You can define a fallback for the system or for the Ruby LSP only`,
@@ -250,15 +251,15 @@ export class Chruby extends VersionManager {
250251
);
251252

252253
if (answer === "System") {
253-
await this.createParentRubyVersionFile();
254+
await this.createParentRubyVersionFile(errorFn);
254255
} else if (answer === "Ruby LSP only") {
255256
await this.manuallySelectRuby();
256257
}
257258

258-
throw this.rubyVersionError();
259+
throw errorFn();
259260
}
260261

261-
private async createParentRubyVersionFile() {
262+
private async createParentRubyVersionFile(errorFn: () => Error) {
262263
const items: vscode.QuickPickItem[] = [];
263264

264265
for (const uri of this.rubyInstallationUris) {
@@ -285,7 +286,7 @@ export class Chruby extends VersionManager {
285286
});
286287

287288
if (!answer) {
288-
throw this.rubyVersionError();
289+
throw errorFn();
289290
}
290291

291292
const targetDirectory = await vscode.window.showOpenDialog({
@@ -298,7 +299,7 @@ export class Chruby extends VersionManager {
298299
});
299300

300301
if (!targetDirectory) {
301-
throw this.rubyVersionError();
302+
throw errorFn();
302303
}
303304

304305
await vscode.workspace.fs.writeFile(
@@ -307,9 +308,10 @@ export class Chruby extends VersionManager {
307308
);
308309
}
309310

310-
private async findFallbackRuby(): Promise<
311-
{ uri: vscode.Uri; rubyVersion: RubyVersion } | undefined
312-
> {
311+
private async findFallbackRuby(): Promise<{
312+
uri: vscode.Uri;
313+
rubyVersion: RubyVersion;
314+
}> {
313315
for (const uri of this.rubyInstallationUris) {
314316
let directories;
315317

@@ -352,7 +354,7 @@ export class Chruby extends VersionManager {
352354
}
353355
}
354356

355-
return undefined;
357+
throw new Error("Cannot find any Ruby installations");
356358
}
357359

358360
// Run the activation script using the Ruby installation we found so that we can discover gem paths

0 commit comments

Comments
 (0)