Skip to content

Commit da78301

Browse files
committed
Extract version managers from ruby-lsp
1 parent 8abfe55 commit da78301

File tree

13 files changed

+1099
-0
lines changed

13 files changed

+1099
-0
lines changed

eslint.config.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ export default tseslint.config(
3636
},
3737
{
3838
rules: {
39+
"@typescript-eslint/no-explicit-any": "off",
40+
"@typescript-eslint/no-unsafe-assignment": "off",
41+
"@typescript-eslint/no-unsafe-argument": "off",
42+
"@typescript-eslint/no-unsafe-member-access": "off",
43+
"@typescript-eslint/no-unsafe-return": "off",
3944
"@typescript-eslint/no-unused-vars": [
4045
"error",
4146
{

src/common.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { exec } from "child_process";
2+
import { promisify } from "util";
3+
import { window } from "vscode";
4+
5+
export interface RubyInterface {
6+
error: boolean;
7+
versionManager: { identifier: string };
8+
rubyVersion?: string;
9+
}
10+
11+
export const asyncExec = promisify(exec);
12+
export const EXTENSION_NAME = "Ruby Environments";
13+
export const LOG_CHANNEL = window.createOutputChannel(EXTENSION_NAME, {
14+
log: true,
15+
});

src/ruby/asdf.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import os from "os";
2+
import path from "path";
3+
4+
import * as vscode from "vscode";
5+
6+
import { VersionManager, ActivationResult } from "./versionManager";
7+
8+
// A tool to manage multiple runtime versions with a single CLI tool
9+
//
10+
// Learn more: https://github.com/asdf-vm/asdf
11+
export class Asdf extends VersionManager {
12+
async activate(): Promise<ActivationResult> {
13+
// These directories are where we can find the ASDF executable for v0.16 and above
14+
const possibleExecutablePaths = [
15+
vscode.Uri.joinPath(vscode.Uri.file("/"), "opt", "homebrew", "bin"),
16+
vscode.Uri.joinPath(vscode.Uri.file("/"), "usr", "local", "bin"),
17+
];
18+
19+
// Prefer the path configured by the user, then the ASDF scripts for versions below v0.16 and finally the
20+
// executables for v0.16 and above
21+
const asdfPath =
22+
(await this.getConfiguredAsdfPath()) ??
23+
(await this.findAsdfInstallation()) ??
24+
(await this.findExec(possibleExecutablePaths, "asdf"));
25+
26+
// If there's no extension name, then we are using the ASDF executable directly. If there is an extension, then it's
27+
// a shell script and we have to source it first
28+
const baseCommand = path.extname(asdfPath) === "" ? asdfPath : `. ${asdfPath} && asdf`;
29+
30+
const parsedResult = await this.runEnvActivationScript(`${baseCommand} exec ruby`);
31+
32+
return {
33+
env: { ...process.env, ...parsedResult.env },
34+
yjit: parsedResult.yjit,
35+
version: parsedResult.version,
36+
gemPath: parsedResult.gemPath,
37+
};
38+
}
39+
40+
// Only public for testing. Finds the ASDF installation URI based on what's advertised in the ASDF documentation
41+
async findAsdfInstallation(): Promise<string | undefined> {
42+
const scriptName = path.basename(vscode.env.shell) === "fish" ? "asdf.fish" : "asdf.sh";
43+
44+
// Possible ASDF installation paths as described in https://asdf-vm.com/guide/getting-started.html#_3-install-asdf.
45+
// In order, the methods of installation are:
46+
// 1. Git
47+
// 2. Pacman
48+
// 3. Homebrew M series
49+
// 4. Homebrew Intel series
50+
const possiblePaths = [
51+
vscode.Uri.joinPath(vscode.Uri.file(os.homedir()), ".asdf", scriptName),
52+
vscode.Uri.joinPath(vscode.Uri.file("/"), "opt", "asdf-vm", scriptName),
53+
vscode.Uri.joinPath(vscode.Uri.file("/"), "opt", "homebrew", "opt", "asdf", "libexec", scriptName),
54+
vscode.Uri.joinPath(vscode.Uri.file("/"), "usr", "local", "opt", "asdf", "libexec", scriptName),
55+
];
56+
57+
for (const possiblePath of possiblePaths) {
58+
try {
59+
await vscode.workspace.fs.stat(possiblePath);
60+
return possiblePath.fsPath;
61+
} catch (_error: any) {
62+
// Continue looking
63+
}
64+
}
65+
66+
this.outputChannel.info(`Could not find installation for ASDF < v0.16. Searched in ${possiblePaths.join(", ")}`);
67+
return undefined;
68+
}
69+
70+
private async getConfiguredAsdfPath(): Promise<string | undefined> {
71+
const config = vscode.workspace.getConfiguration("rubyLsp");
72+
const asdfPath = config.get<string | undefined>("rubyVersionManager.asdfExecutablePath");
73+
74+
if (!asdfPath) {
75+
return;
76+
}
77+
78+
const configuredPath = vscode.Uri.file(asdfPath);
79+
80+
try {
81+
await vscode.workspace.fs.stat(configuredPath);
82+
this.outputChannel.info(`Using configured ASDF executable path: ${asdfPath}`);
83+
return configuredPath.fsPath;
84+
} catch (_error: any) {
85+
throw new Error(`ASDF executable configured as ${configuredPath.fsPath}, but that file doesn't exist`);
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)