Prototype of Supporting Additional Language Types for TS Plugins

Fixes #25740

Allows ts server plugins to specify that they support additional languages, such as html. These supported languages are defined in the contributes section of the package.json:

```json
"contributes": {
    "typescriptServerPlugins": [
        {
            "name": "tslint-language-service",
            "languages": [ "html" ]
        }
    ]
}
```

This change allows the TS Plugin in VSCode to send the additional file types to the TypeScript server for processing by the plugin
This commit is contained in:
Matt Bierner 2017-05-15 16:57:22 -07:00
parent 62eaa644cb
commit 4f398bafda
3 changed files with 77 additions and 41 deletions

View file

@ -49,12 +49,13 @@ import * as BuildStatus from './utils/buildStatus';
import * as ProjectStatus from './utils/projectStatus';
import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus';
import * as VersionStatus from './utils/versionStatus';
import { getContributedTypeScriptServerPlugins, TypeScriptServerPlugin } from "./utils/plugins";
interface LanguageDescription {
id: string;
diagnosticSource: string;
modeIds: string[];
configFile: string;
configFile?: string;
}
enum ProjectConfigAction {
@ -67,12 +68,14 @@ interface ProjectConfigMessageItem extends MessageItem {
id: ProjectConfigAction;
}
export function activate(context: ExtensionContext): void {
const MODE_ID_TS = 'typescript';
const MODE_ID_TSX = 'typescriptreact';
const MODE_ID_JS = 'javascript';
const MODE_ID_JSX = 'javascriptreact';
const plugins = getContributedTypeScriptServerPlugins();
const clientHost = new TypeScriptServiceClientHost([
{
id: 'typescript',
@ -86,7 +89,7 @@ export function activate(context: ExtensionContext): void {
modeIds: [MODE_ID_JS, MODE_ID_JSX],
configFile: 'jsconfig.json'
}
], context.storagePath, context.globalState, context.workspaceState);
], context.storagePath, context.globalState, context.workspaceState, plugins);
context.subscriptions.push(clientHost);
const client = clientHost.serviceClient;
@ -130,6 +133,7 @@ export function activate(context: ExtensionContext): void {
BuildStatus.update({ queueLength: 0 });
}
const validateSetting = 'validate.enable';
class LanguageProvider {
@ -421,7 +425,8 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost {
descriptions: LanguageDescription[],
storagePath: string | undefined,
globalState: Memento,
workspaceState: Memento
workspaceState: Memento,
plugins: TypeScriptServerPlugin[]
) {
const handleProjectCreateOrDelete = () => {
this.client.execute('reloadProjects', null, false);
@ -438,7 +443,7 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost {
configFileWatcher.onDidDelete(handleProjectCreateOrDelete, this, this.disposables);
configFileWatcher.onDidChange(handleProjectChange, this, this.disposables);
this.client = new TypeScriptServiceClient(this, storagePath, globalState, workspaceState, this.disposables);
this.client = new TypeScriptServiceClient(this, storagePath, globalState, workspaceState, plugins, this.disposables);
this.languagePerId = Object.create(null);
for (const description of descriptions) {
const manager = new LanguageProvider(this.client, description);
@ -446,6 +451,30 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost {
this.disposables.push(manager);
this.languagePerId[description.id] = manager;
}
this.client.onReady().then(() => {
if (!this.client.apiVersion.has230Features()) {
return;
}
const langauges = new Set<string>();
for (const plugin of plugins) {
for (const language of plugin.languages) {
langauges.add(language);
}
}
if (langauges.size) {
const description: LanguageDescription = {
id: 'typescript-plugins',
modeIds: Array.from(langauges.values()),
diagnosticSource: 'ts-plugins'
};
const manager = new LanguageProvider(this.client, description);
this.languages.push(manager);
this.disposables.push(manager);
this.languagePerId[description.id] = manager;
}
});
}
public dispose(): void {
@ -461,15 +490,6 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost {
return this.client;
}
public restartTsServer(): void {
this.client.restartTsServer();
if (this.languages) {
for (const provider of this.languages) {
provider.reInitialize();
}
}
}
public reloadProjects(): void {
this.client.execute('reloadProjects', null, false);
this.triggerAllDiagnostics();

View file

@ -13,9 +13,10 @@ import * as os from 'os';
import * as electron from './utils/electron';
import { Reader } from './utils/wireProtocol';
import { workspace, window, extensions, Uri, CancellationToken, Disposable, OutputChannel, Memento, MessageItem, QuickPickItem, EventEmitter, Event, commands, WorkspaceConfiguration } from 'vscode';
import { workspace, window, Uri, CancellationToken, Disposable, OutputChannel, Memento, MessageItem, QuickPickItem, EventEmitter, Event, commands, WorkspaceConfiguration } from 'vscode';
import * as Proto from './protocol';
import { ITypescriptServiceClient, ITypescriptServiceClientHost, API } from './typescriptService';
import { TypeScriptServerPlugin } from './utils/plugins';
import * as VersionStatus from './utils/versionStatus';
import * as is from './utils/is';
@ -121,12 +122,6 @@ interface MyMessageItem extends MessageItem {
id: MessageAction;
}
interface TypeScriptServerPlugin {
path: string;
name: string;
}
export default class TypeScriptServiceClient implements ITypescriptServiceClient {
private static useWorkspaceTsdkStorageKey = 'typescript.useWorkspaceTsdk';
private static tsdkMigratedStorageKey = 'typescript.tsdkMigrated';
@ -171,7 +166,14 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
private telemetryReporter: TelemetryReporter;
private checkJs: boolean;
constructor(host: ITypescriptServiceClientHost, storagePath: string | undefined, globalState: Memento, private workspaceState: Memento, disposables: Disposable[]) {
constructor(
host: ITypescriptServiceClientHost,
storagePath: string | undefined,
globalState: Memento,
private workspaceState: Memento,
private plugins: TypeScriptServerPlugin[],
disposables: Disposable[]
) {
this.host = host;
this.storagePath = storagePath;
this.globalState = globalState;
@ -573,11 +575,10 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
}
if (this.apiVersion.has230Features()) {
const plugins = this.getContributedTypeScriptServerPlugins();
if (plugins.length) {
args.push('--globalPlugins', plugins.map(x => x.name).join(','));
if (this.plugins.length) {
args.push('--globalPlugins', this.plugins.map(x => x.name).join(','));
if (modulePath === this.globalTypescriptPath) {
args.push('--pluginProbeLocations', plugins.map(x => x.path).join(','));
args.push('--pluginProbeLocations', this.plugins.map(x => x.path).join(','));
}
}
}
@ -822,22 +823,6 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
return desc.version;
}
private getContributedTypeScriptServerPlugins(): TypeScriptServerPlugin[] {
const plugins: TypeScriptServerPlugin[] = [];
for (const extension of extensions.all) {
const pack = extension.packageJSON;
if (pack.contributes && pack.contributes.typescriptServerPlugins && Array.isArray(pack.contributes.typescriptServerPlugins)) {
for (const plugin of pack.contributes.typescriptServerPlugins) {
plugins.push({
name: plugin.name,
path: extension.extensionPath
});
}
}
}
return plugins;
}
private serviceExited(restart: boolean): void {
this.servicePromise = null;
this.tsServerLogFile = null;

View file

@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { extensions } from "vscode";
export interface TypeScriptServerPlugin {
path: string;
name: string;
languages: string[];
}
export function getContributedTypeScriptServerPlugins(): TypeScriptServerPlugin[] {
const plugins: TypeScriptServerPlugin[] = [];
for (const extension of extensions.all) {
const pack = extension.packageJSON;
if (pack.contributes && pack.contributes.typescriptServerPlugins && Array.isArray(pack.contributes.typescriptServerPlugins)) {
for (const plugin of pack.contributes.typescriptServerPlugins) {
plugins.push({
name: plugin.name,
path: extension.extensionPath,
languages: Array.isArray(plugin.languages) ? plugin.languages : []
});
}
}
}
return plugins;
}