Skip to content

Commit

Permalink
feat: custom global typescript plugins
Browse files Browse the repository at this point in the history
docs: instruction for volar hybrid mode

rework
  • Loading branch information
yioneko committed Apr 15, 2024
1 parent 41ad8c9 commit 2c5bf7b
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 7 deletions.
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,29 @@ Instead of switching client, some server configuration options could also make p

### TypeScript plugin not activated

- Plugin should be specified in `tsconfig.json`.
- Check the place of running tsserver. By default the bundled version is used as in VSCode. Switch to workspace version by command `typescript.selectTypeScriptVersion` or config option `vtsls.autoUseWorkspaceTsdk`.
- `typescript.tsserver.pluginPaths`: use this option without modifying `tsconfig.json`.
If the plugin is specified in project `package.json` and installed locally:

- Ensure the plugin is also specified in `compilerOptions.plugins` field of `tsconfig.json`.
- Switch to workspace version of tsserver by command `typescript.selectTypeScriptVersion` or config option `vtsls.autoUseWorkspaceTsdk`.
- Alternatively, set `typescript.tsserver.pluginPaths = ["./node_modules"]` to tell the bundled tsserver to search plugins in project local `node_modules` folder.

Or if the plugin resides elsewhere, typically when you want to test a plugin without locally installing it to your package or modifying `tsconfig.json`: use config option `vtsls.tsserver.globalPlugins`. Refer to the following section for an example.

### Vue Support via Hybrid Mode of [`volar >= 2.0`](https://github.com/vuejs/language-tools/tree/master/packages/typescript-plugin)

Suppose `@vue/language-server` has been installed through package manager, then set configuration option `vtsls.tsserver.globalPlugins` to:

```json
[
{
"name": "@vue/typescript-plugin",
"location": "/usr/local/lib/node_modules/@vue/language-server",
"languages": ["vue"],
"configNamespace": "typescript",
"enableForWorkspaceTypeScriptVersions": true,
}
]
```

### Log

Expand Down
6 changes: 3 additions & 3 deletions packages/server/build.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const esbuild = require("esbuild");
const fs = require("fs");
const path = require("path");
const fs = require("node:fs/promises");
const path = require("node:path");

const outDir = path.resolve(__dirname, "./dist");
const srcDir = path.resolve(__dirname, "./src");
Expand All @@ -9,7 +9,7 @@ const srcDir = path.resolve(__dirname, "./src");
* @param args {{ watch: boolean }}
*/
async function build({ watch }) {
const pkgJson = await fs.promises.readFile(path.resolve(__dirname, "./package.json"), "utf8");
const pkgJson = await fs.readFile(path.resolve(__dirname, "./package.json"), "utf8");
const { version, dependencies = [] } = JSON.parse(pkgJson);
const opts = {
entryPoints: [srcDir],
Expand Down
31 changes: 31 additions & 0 deletions packages/service/configuration.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,37 @@
"default": false,
"type": "boolean",
"description": "Automatically use workspace version of TypeScript lib on startup. By default, the bundled version is used for intelliSense."
},
"vtsls.tsserver.globalPlugins": {
"default": [],
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"location": {
"type": "string",
"description": "Location where to resolve the path of plugin. If not provided, the plugin will be resolved from the place of running `tsserver.js` and `typescript.tsserver.pluginPaths`."
},
"enableForWorkspaceTypeScriptVersions": {
"type": "boolean",
"description": "By default, global plugins won't be enabled when workspace version of tsdk is used. Set to `true` to switch this behavior."
},
"languages": {
"type": "array",
"items": {
"type": "string"
},
"description": "Additional languages except for JS/TS suppported by the plugin."
},
"configNamespace": {
"type": "string"
}
}
},
"description": "TypeScript plugins that are not locally avaiable in the workspace. Usually the plugin configuration can be found in the `contributes.typescriptServerPlugins` field of `package.json` of the corresponding VSCode extension."
}
}
}
93 changes: 93 additions & 0 deletions packages/service/patches/003-read-global-plugins.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
diff --git a/src/tsServer/plugins.ts b/src/tsServer/plugins.ts
index 6036e4c..1d3efd6 100644
--- a/src/tsServer/plugins.ts
+++ b/src/tsServer/plugins.ts
@@ -9,7 +9,7 @@ import { Disposable } from '../utils/dispose';

export interface TypeScriptServerPlugin {
readonly extension: vscode.Extension<unknown>;
- readonly uri: vscode.Uri;
+ readonly uri?: vscode.Uri;
readonly name: string;
readonly enableForWorkspaceTypeScriptVersions: boolean;
readonly languages: ReadonlyArray<string>;
@@ -18,7 +18,7 @@ export interface TypeScriptServerPlugin {

namespace TypeScriptServerPlugin {
export function equals(a: TypeScriptServerPlugin, b: TypeScriptServerPlugin): boolean {
- return a.uri.toString() === b.uri.toString()
+ return a.uri?.toString() === b.uri?.toString()
&& a.name === b.name
&& a.enableForWorkspaceTypeScriptVersions === b.enableForWorkspaceTypeScriptVersions
&& arrays.equals(a.languages, b.languages);
@@ -33,7 +33,10 @@ export class PluginManager extends Disposable {
constructor() {
super();

- vscode.extensions.onDidChange(() => {
+ vscode.workspace.onDidChangeConfiguration((e) => {
+ if (!e.affectsConfiguration("vtsls.tsserver.globalPlugins")) {
+ return;
+ }
if (!this._plugins) {
return;
}
@@ -67,26 +70,25 @@ export class PluginManager extends Disposable {
}

private readPlugins() {
- const pluginMap = new Map<string, ReadonlyArray<TypeScriptServerPlugin>>();
- for (const extension of vscode.extensions.all) {
- const pack = extension.packageJSON;
- if (pack.contributes && Array.isArray(pack.contributes.typescriptServerPlugins)) {
- const plugins: TypeScriptServerPlugin[] = [];
- for (const plugin of pack.contributes.typescriptServerPlugins) {
- plugins.push({
- extension,
- name: plugin.name,
- enableForWorkspaceTypeScriptVersions: !!plugin.enableForWorkspaceTypeScriptVersions,
- uri: extension.extensionUri,
- languages: Array.isArray(plugin.languages) ? plugin.languages : [],
- configNamespace: plugin.configNamespace,
- });
- }
- if (plugins.length) {
- pluginMap.set(extension.id, plugins);
- }
- }
+ const configPlugins =
+ vscode.workspace.getConfiguration("vtsls").get<Array<any>>("tsserver.globalPlugins") || [];
+ const plugins: TypeScriptServerPlugin[] = [];
+ for (const plugin of configPlugins) {
+ const extension = {
+ id: plugin.name,
+ // extensionUri: uri,
+ // extensionPath: pluginPath,
+ // isActive: true,
+ } as any;
+ plugins.push({
+ extension,
+ name: plugin.name,
+ enableForWorkspaceTypeScriptVersions: !!plugin.enableForWorkspaceTypeScriptVersions,
+ uri: plugin.location ? vscode.Uri.file(plugin.location) : undefined,
+ languages: Array.isArray(plugin.languages) ? plugin.languages : [],
+ configNamespace: plugin.configNamespace,
+ });
}
- return pluginMap;
+ return new Map([["", plugins]]);
}
}
diff --git a/src/tsServer/spawner.ts b/src/tsServer/spawner.ts
index 52dcf5b..f280a06 100644
--- a/src/tsServer/spawner.ts
+++ b/src/tsServer/spawner.ts
@@ -253,7 +253,7 @@ export class TypeScriptServerSpawner {

const isUsingBundledTypeScriptVersion = currentVersion.path === this._versionProvider.defaultVersion.path;
for (const plugin of pluginManager.plugins) {
- if (isUsingBundledTypeScriptVersion || plugin.enableForWorkspaceTypeScriptVersions) {
+ if ((isUsingBundledTypeScriptVersion || plugin.enableForWorkspaceTypeScriptVersions) && plugin.uri) {
pluginPaths.push(isWeb() ? plugin.uri.toString() : plugin.uri.fsPath);
}
}
2 changes: 2 additions & 0 deletions packages/service/scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ async function build({ watch }) {
format: "cjs",
define: { "import.meta.url": "importMetaUrl" },
inject: [path.resolve(__dirname, "cjs_shims.js")],
// filter out esm-only packages
external: esmOpts.external.filter((d) => !d.startsWith("global-directory")),
};
if (!watch) {
await esbuild.build(esmOpts);
Expand Down
28 changes: 28 additions & 0 deletions packages/service/scripts/genConfigSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,34 @@ async function genSchema() {
description:
"Automatically use workspace version of TypeScript lib on startup. By default, the bundled version is used for intelliSense.",
},
"vtsls.tsserver.globalPlugins": {
default: [],
type: "array",
items: {
type: "object",
properties: {
name: { type: "string" },
location: {
type: "string",
description:
"Location where to resolve the path of plugin. If not provided, the plugin will be resolved from the place of running `tsserver.js` and `typescript.tsserver.pluginPaths`.",
},
enableForWorkspaceTypeScriptVersions: {
type: "boolean",
description:
"By default, global plugins won't be enabled when workspace version of tsdk is used. Set to `true` to switch this behavior.",
},
languages: {
type: "array",
items: { type: "string" },
description: "Additional languages except for JS/TS suppported by the plugin.",
},
configNamespace: { type: "string" },
},
},
description:
"TypeScript plugins that are not locally avaiable in the workspace. Usually the plugin configuration can be found in the `contributes.typescriptServerPlugins` field of `package.json` of the corresponding VSCode extension.",
},
};

return {
Expand Down
2 changes: 1 addition & 1 deletion packages/service/scripts/patch.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const cp = require("child_process");
const cp = require("node:child_process");
const fs = require("node:fs/promises");
const readline = require("node:readline");
const path = require("node:path");
Expand Down
1 change: 1 addition & 0 deletions packages/service/src/service/pkgJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function getDefaultConfig() {
"vtsls.experimental.completion.entriesLimit": null,
"vtsls.enableMoveToFileCodeAction": false,
"vtsls.autoUseWorkspaceTsdk": false,
"vtsls.tsserver.globalPlugins": [],
};

const res: TSLanguageServiceConfig = {};
Expand Down
5 changes: 5 additions & 0 deletions packages/service/src/shims/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ export class WorkspaceShimService extends Disposable {

async openTextDocument(nameOrUri: vscode.Uri | string): Promise<vscode.TextDocument> {
const uri = typeof nameOrUri === "string" ? URI.file(nameOrUri) : nameOrUri;
const maybeOpenedDoc = this._documents.get(uri);
if (maybeOpenedDoc) {
return this.delegate.converter.convertTextDocuemntFromLsp(maybeOpenedDoc);
}

const success = await this.delegate.openTextDocument(uri.toString());
if (!success) {
throw new Error(`Cannot open doc ${uri.toString()}`);
Expand Down

0 comments on commit 2c5bf7b

Please sign in to comment.