diff --git a/vscode/package.json b/vscode/package.json index 073402434..0d114432b 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -551,6 +551,14 @@ "type": "boolean", "default": true }, + "rubyLsp.addonLanguageIds": { + "description": "Language IDs of other files to send to the server, for languages that an installed add-on supports (for example, `slim`). Without such an add-on, the server treats these files as Ruby", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, "rubyLsp.featureFlags": { "description": "Allows opting in or out of feature flags", "type": "object", diff --git a/vscode/src/client.ts b/vscode/src/client.ts index c71737937..2b8b1dde0 100644 --- a/vscode/src/client.ts +++ b/vscode/src/client.ts @@ -30,7 +30,7 @@ import { ErrorCodes, } from "vscode-languageclient/node"; -import { LSP_NAME, ClientInterface, Addon, SUPPORTED_LANGUAGE_IDS, FEATURE_FLAGS, featureEnabled } from "./common"; +import { LSP_NAME, ClientInterface, Addon, supportedLanguageIds, FEATURE_FLAGS, featureEnabled } from "./common"; import { Ruby } from "./ruby"; import { WorkspaceChannel } from "./workspaceChannel"; @@ -169,14 +169,15 @@ function collectClientOptions( // 1. Files inside of the workspace itself // 2. Bundled gems // 3. Default gems - let documentSelector: DocumentSelector = SUPPORTED_LANGUAGE_IDS.flatMap((language) => { + const languageIds = supportedLanguageIds(); + let documentSelector: DocumentSelector = languageIds.flatMap((language) => { return { scheme: "file", language, pattern: `${fsPath}/**/*` }; }); // Only the first language server we spawn should handle unsaved files, otherwise requests will be duplicated across // all workspaces if (isMainWorkspace) { - SUPPORTED_LANGUAGE_IDS.forEach((language) => { + languageIds.forEach((language) => { documentSelector.push({ language, scheme: "untitled", diff --git a/vscode/src/common.ts b/vscode/src/common.ts index e27ba06fc..39dfe67b2 100644 --- a/vscode/src/common.ts +++ b/vscode/src/common.ts @@ -75,6 +75,13 @@ export const LOG_CHANNEL = vscode.window.createOutputChannel(LSP_NAME, { }); export const SUPPORTED_LANGUAGE_IDS = ["ruby", "erb"]; +// Add-ons can make the server understand other languages, such as Slim templates. Users opt into sending those files to +// the server, since without a matching add-on it would treat them as Ruby +export function supportedLanguageIds(): string[] { + const addonLanguageIds = vscode.workspace.getConfiguration("rubyLsp").get("addonLanguageIds") ?? []; + return [...SUPPORTED_LANGUAGE_IDS, ...addonLanguageIds]; +} + // A list of feature flags where the key is the name and the value is the rollout percentage. // // Note: names added here should also be added to the `rubyLsp.optedOutFeatureFlags` enum in the `package.json` file diff --git a/vscode/src/rubyLsp.ts b/vscode/src/rubyLsp.ts index e6336fbba..09ff582e1 100644 --- a/vscode/src/rubyLsp.ts +++ b/vscode/src/rubyLsp.ts @@ -6,7 +6,7 @@ import { Range } from "vscode-languageclient/node"; import DocumentProvider from "./documentProvider"; import { Workspace } from "./workspace"; -import { Command, featureEnabled, LOG_CHANNEL, STATUS_EMITTER, SUPPORTED_LANGUAGE_IDS } from "./common"; +import { Command, featureEnabled, LOG_CHANNEL, STATUS_EMITTER, supportedLanguageIds } from "./common"; import { ManagerIdentifier, ManagerConfiguration } from "./ruby"; import { StatusItems } from "./status"; import { TestController } from "./testController"; @@ -72,7 +72,7 @@ export class RubyLsp { }), // Lazily activate workspaces that do not contain a lockfile vscode.workspace.onDidOpenTextDocument(async (document) => { - if (!SUPPORTED_LANGUAGE_IDS.includes(document.languageId)) { + if (!supportedLanguageIds().includes(document.languageId)) { return; } @@ -127,7 +127,7 @@ export class RubyLsp { // eagerly activate the workspace for that file too const activeDocument = vscode.window.activeTextEditor?.document; - if (activeDocument && SUPPORTED_LANGUAGE_IDS.includes(activeDocument.languageId)) { + if (activeDocument && supportedLanguageIds().includes(activeDocument.languageId)) { const workspaceFolder = vscode.workspace.getWorkspaceFolder(activeDocument.uri); if (workspaceFolder) { diff --git a/vscode/src/status.ts b/vscode/src/status.ts index 3492b9120..3fe6177ec 100644 --- a/vscode/src/status.ts +++ b/vscode/src/status.ts @@ -1,7 +1,7 @@ import * as vscode from "vscode"; import { State } from "vscode-languageclient"; -import { Command, STATUS_EMITTER, WorkspaceInterface, SUPPORTED_LANGUAGE_IDS } from "./common"; +import { Command, STATUS_EMITTER, WorkspaceInterface, supportedLanguageIds } from "./common"; const STOPPED_SERVER_OPTIONS = [ { label: "Ruby LSP: Start", description: Command.Start }, @@ -21,7 +21,7 @@ export abstract class StatusItem { public item: vscode.LanguageStatusItem; constructor(id: string) { - const documentSelector = SUPPORTED_LANGUAGE_IDS.map((language) => ({ + const documentSelector = supportedLanguageIds().map((language) => ({ language, })); diff --git a/vscode/src/test/suite/common.test.ts b/vscode/src/test/suite/common.test.ts index 954797fe4..b8b7c1ff6 100644 --- a/vscode/src/test/suite/common.test.ts +++ b/vscode/src/test/suite/common.test.ts @@ -3,7 +3,7 @@ import * as assert from "assert"; import * as vscode from "vscode"; import sinon from "sinon"; -import { featureEnabled, FEATURE_FLAGS } from "../../common"; +import { featureEnabled, FEATURE_FLAGS, supportedLanguageIds } from "../../common"; suite("featureEnabled", () => { let sandbox: sinon.SinonSandbox; @@ -131,3 +131,31 @@ suite("featureEnabled", () => { secondStub.restore(); }); }); + +suite("supportedLanguageIds", () => { + let sandbox: sinon.SinonSandbox; + + setup(() => { + sandbox = sinon.createSandbox(); + }); + + teardown(() => { + sandbox.restore(); + }); + + test("returns Ruby and ERB by default", () => { + sandbox.stub(vscode.workspace, "getConfiguration").returns({ + get: () => undefined, + } as any); + + assert.deepStrictEqual(supportedLanguageIds(), ["ruby", "erb"]); + }); + + test("includes the language IDs configured for add-ons", () => { + sandbox.stub(vscode.workspace, "getConfiguration").returns({ + get: (key: string) => (key === "addonLanguageIds" ? ["slim"] : undefined), + } as any); + + assert.deepStrictEqual(supportedLanguageIds(), ["ruby", "erb", "slim"]); + }); +});