Skip to content

Commit

Permalink
Add support for locating a bundled version of air
Browse files Browse the repository at this point in the history
  • Loading branch information
DavisVaughan committed Jan 15, 2025
1 parent d91c8bf commit 8828ccb
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 29 deletions.
67 changes: 50 additions & 17 deletions editors/code/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 24 additions & 6 deletions editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@
"default": null,
"markdownDescription": "Controls the log level of the language server.",
"enum": [
"error",
"warning",
"info",
"debug",
"trace"
"error",
"warning",
"info",
"debug",
"trace"
],
"scope": "application",
"type": "string"
Expand All @@ -55,6 +55,20 @@
"markdownDescription": "Controls the log level of the Rust crates that the language server depends on.",
"scope": "application",
"type": "string"
},
"air.executableLocation": {
"default": "environment",
"markdownDescription": "Location of the `air` executable to start the language server with.",
"enum": [
"environment",
"bundled"
],
"enumDescriptions": [
"Look for an `air` executable on the `PATH`, falling back to the bundled version.",
"Always use the bundled `air` executable."
],
"scope": "window",
"type": "string"
}
}
},
Expand Down Expand Up @@ -109,15 +123,19 @@
},
"dependencies": {
"@types/p-queue": "^3.1.0",
"fs-extra": "^11.1.1",
"p-queue": "npm:@esm2cjs/p-queue@^7.3.0",
"adm-zip": "^0.5.16",
"vscode-languageclient": "^9.0.1"
"vscode-languageclient": "^9.0.1",
"which": "^4.0.0"
},
"devDependencies": {
"@types/adm-zip": "^0.5.6",
"@types/fs-extra": "^11.0.4",
"@types/mocha": "^10.0.9",
"@types/node": "20.x",
"@types/vscode": "^1.90.0",
"@types/which": "^3.0.4",
"@typescript-eslint/eslint-plugin": "^8.10.0",
"@typescript-eslint/parser": "^8.7.0",
"@vscode/test-cli": "^0.0.10",
Expand Down
36 changes: 36 additions & 0 deletions editors/code/src/binary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as vscode from "vscode";
import which from "which";
import { AIR_BINARY_NAME, BUNDLED_AIR_EXECUTABLE } from "./constants";
import { outputLog } from "./logging";

export type ExecutableLocation = "environment" | "bundled";

export async function resolveAirBinaryPath(
executableLocation: ExecutableLocation
): Promise<string> {
if (!vscode.workspace.isTrusted) {
outputLog(
`Workspace is not trusted, using bundled executable: ${BUNDLED_AIR_EXECUTABLE}`
);
return BUNDLED_AIR_EXECUTABLE;
}

// User requested the bundled air binary
if (executableLocation === "bundled") {
outputLog(
`Using bundled executable as requested by \`air.executableLocation\`: ${BUNDLED_AIR_EXECUTABLE}`
);
return BUNDLED_AIR_EXECUTABLE;
}

// First choice: the executable in the global environment.
const environmentPath = await which(AIR_BINARY_NAME, { nothrow: true });
if (environmentPath) {
outputLog(`Using environment executable: ${environmentPath}`);
return environmentPath;
}

// Second choice: bundled executable.
outputLog(`Using bundled executable: ${BUNDLED_AIR_EXECUTABLE}`);
return BUNDLED_AIR_EXECUTABLE;
}
27 changes: 27 additions & 0 deletions editors/code/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as path from "path";

const folderName = path.basename(__dirname);

/**
* Path to the root directory of this extension.
*/
export const EXTENSION_ROOT_DIR =
folderName === "common"
? path.dirname(path.dirname(__dirname))
: path.dirname(__dirname);

/**
* Name of the `air` binary based on the current platform.
*/
export const AIR_BINARY_NAME = process.platform === "win32" ? "air.exe" : "air";

/**
* Path to the `air` executable that is bundled with the extension.
* The GitHub Action is in charge of placing the executable here.
*/
export const BUNDLED_AIR_EXECUTABLE = path.join(
EXTENSION_ROOT_DIR,
"bundled",
"bin",
AIR_BINARY_NAME
);
28 changes: 28 additions & 0 deletions editors/code/src/logging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as util from "util";
import { Disposable, OutputChannel } from "vscode";

type Arguments = unknown[];
class OutputChannelLogger {
constructor(private readonly channel: OutputChannel) {}

public outputLog(...data: Arguments): void {
this.channel.appendLine(util.format(...data));
}
}

let channel: OutputChannelLogger | undefined;
export function registerLogger(logChannel: OutputChannel): Disposable {
channel = new OutputChannelLogger(logChannel);
return {
dispose: () => {
channel = undefined;
},
};
}

export function outputLog(...args: Arguments): void {
if (process.env.CI === "true") {
console.log(...args);
}
channel?.outputLog(...args);
}
20 changes: 15 additions & 5 deletions editors/code/src/lsp.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import * as vscode from "vscode";
import * as lc from "vscode-languageclient/node";
import { default as PQueue } from "p-queue";
import { getInitializationOptions } from "./settings";
import { getInitializationOptions, getWorkspaceSettings } from "./settings";
import { Middleware, ResponseError } from "vscode-languageclient/node";
import { registerLogger } from "./logging";
import { resolveAirBinaryPath } from "./binary";
import { getRootWorkspaceFolder } from "./workspace";

// All session management operations are put on a queue. They can't run
// concurrently and either result in a started or stopped state. Starting when
Expand All @@ -25,7 +28,7 @@ export class Lsp {

constructor(context: vscode.ExtensionContext) {
this.channel = vscode.window.createOutputChannel("Air Language Server");
context.subscriptions.push(this.channel);
context.subscriptions.push(this.channel, registerLogger(this.channel));
this.stateQueue = new PQueue({ concurrency: 1 });
}

Expand Down Expand Up @@ -54,10 +57,17 @@ export class Lsp {
return;
}

const workspaceFolder = await getRootWorkspaceFolder();

const workspaceSettings = getWorkspaceSettings("air", workspaceFolder);
const initializationOptions = getInitializationOptions("air");

const command = await resolveAirBinaryPath(
workspaceSettings.executableLocation
);

let serverOptions: lc.ServerOptions = {
command: "air",
command: command,
args: ["language-server"],
};

Expand Down Expand Up @@ -89,7 +99,7 @@ export class Lsp {

const config = vscode.workspace.getConfiguration(
undefined,
{ uri, languageId },
{ uri, languageId }
);
items[i] = config.get(item.section);
}
Expand All @@ -116,7 +126,7 @@ export class Lsp {
"airLanguageServer",
"Air Language Server",
serverOptions,
clientOptions,
clientOptions
);
await client.start();

Expand Down
25 changes: 24 additions & 1 deletion editors/code/src/settings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { ConfigurationScope, workspace, WorkspaceConfiguration } from "vscode";
import {
ConfigurationScope,
workspace,
WorkspaceConfiguration,
WorkspaceFolder,
} from "vscode";
import { ExecutableLocation } from "./binary";

type LogLevel = "error" | "warn" | "info" | "debug" | "trace";

Expand All @@ -11,6 +17,10 @@ export type InitializationOptions = {
dependencyLogLevels?: string;
};

export type WorkspaceSettings = {
executableLocation: ExecutableLocation;
};

export function getInitializationOptions(
namespace: string
): InitializationOptions {
Expand All @@ -25,6 +35,19 @@ export function getInitializationOptions(
};
}

export function getWorkspaceSettings(
namespace: string,
workspace: WorkspaceFolder
): WorkspaceSettings {
const config = getConfiguration(namespace, workspace);

return {
executableLocation:
config.get<ExecutableLocation>("executableLocation") ??
"environment",
};
}

function getOptionalUserValue<T>(
config: WorkspaceConfiguration,
key: string
Expand Down
Loading

0 comments on commit 8828ccb

Please sign in to comment.