未验证 提交 23c4467e 编写于 作者: M Matt Bierner 提交者: GitHub

Enable web TS Server (#102990)

This enables running the TS Server on web. This currently requires a special version of the TypeScript server 
上级 ec8606cb
......@@ -6,6 +6,7 @@
//@ts-check
'use strict';
const CopyPlugin = require('copy-webpack-plugin');
const withBrowserDefaults = require('../shared.webpack.config').browser;
......@@ -13,6 +14,13 @@ module.exports = withBrowserDefaults({
context: __dirname,
entry: {
extension: './src/extension.browser.ts',
'tsserver.browser': './src/tsserver.browser.ts',
}
},
plugins: [
// @ts-ignore
new CopyPlugin({
patterns: [
{ from: 'node_modules/typescript-web-server', to: 'typescript-web' }
],
}),
],
});
......@@ -27,10 +27,14 @@
"@types/node": "^12.11.7",
"@types/rimraf": "2.0.2",
"@types/semver": "^5.5.0",
"copy-webpack-plugin": "^6.0.3",
"typescript-web-server": "git://github.com/mjbvz/ts-server-web-build",
"vscode": "^1.1.36"
},
"scripts": {
"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:typescript-language-features"
"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:typescript-language-features",
"compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none",
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
},
"activationEvents": [
"onLanguage:javascript",
......@@ -50,6 +54,7 @@
"onLanguage:jsonc"
],
"main": "./out/extension",
"browser": "./dist/browser/extension",
"contributes": {
"jsonValidation": [
{
......
......@@ -4,14 +4,36 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { noopLogDirectoryProvider } from './tsServer/logDirectoryProvider';
import { WorkerServerProcess } from './tsServer/workerServerProcess';
import { Api, getExtensionApi } from './api';
import { registerCommands } from './commands/index';
import { LanguageConfigurationManager } from './features/languageConfiguration';
import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost';
import { noopRequestCancellerFactory } from './tsServer/cancellation';
import { noopLogDirectoryProvider } from './tsServer/logDirectoryProvider';
import API from './utils/api';
import { CommandManager } from './utils/commandManager';
import { TypeScriptServiceConfiguration } from './utils/configuration';
import { PluginManager } from './utils/plugins';
import { noopRequestCancellerFactory } from './tsServer/cancellation';
import { ITypeScriptVersionProvider, TypeScriptVersion, TypeScriptVersionSource } from './utils/versionProvider';
class StaticVersionProvider implements ITypeScriptVersionProvider {
constructor(
private readonly _version: TypeScriptVersion
) { }
updateConfiguration(_configuration: TypeScriptServiceConfiguration): void {
// noop
}
get defaultVersion() { return this._version; }
get bundledVersion() { return this._version; }
readonly globalVersion = undefined;
readonly localVersion = undefined;
readonly localVersions = [];
}
export function activate(
context: vscode.ExtensionContext
......@@ -25,7 +47,13 @@ export function activate(
const onCompletionAccepted = new vscode.EventEmitter<vscode.CompletionItem>();
context.subscriptions.push(onCompletionAccepted);
const lazyClientHost = createLazyClientHost(context, pluginManager, commandManager, noopLogDirectoryProvider, noopRequestCancellerFactory, item => {
const versionProvider = new StaticVersionProvider(
new TypeScriptVersion(
TypeScriptVersionSource.Bundled,
'/builtin-extension/typescript-language-features/dist/browser/typescript-web/tsserver.js',
API.v400));
const lazyClientHost = createLazyClientHost(context, false, pluginManager, commandManager, noopLogDirectoryProvider, noopRequestCancellerFactory, versionProvider, WorkerServerProcess, item => {
onCompletionAccepted.fire(item);
});
......
......@@ -5,16 +5,19 @@
import * as rimraf from 'rimraf';
import * as vscode from 'vscode';
import { NodeLogDirectoryProvider } from './tsServer/logDirectoryProvider.electron';
import { Api, getExtensionApi } from './api';
import { registerCommands } from './commands/index';
import { LanguageConfigurationManager } from './features/languageConfiguration';
import * as task from './features/task';
import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost';
import { nodeRequestCancellerFactory } from './tsServer/cancellation.electron';
import { NodeLogDirectoryProvider } from './tsServer/logDirectoryProvider.electron';
import { CommandManager } from './utils/commandManager';
import * as electron from './utils/electron';
import { onCaseInsenitiveFileSystem } from './utils/fileSystem';
import { PluginManager } from './utils/plugins';
import { ChildServerProcess } from './utils/serverProcess';
import { DiskTypeScriptVersionProvider } from './utils/versionProvider.electron';
export function activate(
context: vscode.ExtensionContext
......@@ -30,7 +33,9 @@ export function activate(
const logDirectoryProvider = new NodeLogDirectoryProvider(context);
const lazyClientHost = createLazyClientHost(context, pluginManager, commandManager, logDirectoryProvider, nodeRequestCancellerFactory, item => {
const versionProvider = new DiskTypeScriptVersionProvider();
const lazyClientHost = createLazyClientHost(context, onCaseInsenitiveFileSystem(), pluginManager, commandManager, logDirectoryProvider, nodeRequestCancellerFactory, versionProvider, ChildServerProcess, item => {
onCompletionAccepted.fire(item);
});
......
......@@ -123,9 +123,9 @@ export function register(
) {
return conditionalRegistration([
requireMinVersion(client, TypeScriptCallHierarchySupport.minVersion),
requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic),
requireSomeCapability(client, ClientCapability.Semantic),
], () => {
return vscode.languages.registerCallHierarchyProvider(selector.syntax,
return vscode.languages.registerCallHierarchyProvider(selector.semantic,
new TypeScriptCallHierarchySupport(client));
});
}
......@@ -9,7 +9,6 @@ import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { Disposable } from '../utils/dispose';
import * as fileSchemes from '../utils/fileSchemes';
import { onCaseInsenitiveFileSystem } from '../utils/fileSystem';
import { isTypeScriptDocument } from '../utils/languageModeIds';
import { equals } from '../utils/objects';
import { ResourceMap } from '../utils/resourceMap';
......@@ -34,10 +33,11 @@ export default class FileConfigurationManager extends Disposable {
private readonly formatOptions: ResourceMap<Promise<FileConfiguration | undefined>>;
public constructor(
private readonly client: ITypeScriptServiceClient
private readonly client: ITypeScriptServiceClient,
onCaseInsenitiveFileSystem: boolean
) {
super();
this.formatOptions = new ResourceMap(undefined, { onCaseInsenitiveFileSystem: onCaseInsenitiveFileSystem() });
this.formatOptions = new ResourceMap(undefined, { onCaseInsenitiveFileSystem });
vscode.workspace.onDidCloseTextDocument(textDocument => {
// When a document gets closed delete the cached formatting options.
// This is necessary since the tsserver now closed a project when its
......
......@@ -6,11 +6,11 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import type * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { nulToken } from '../utils/cancellation';
import { Command, CommandManager } from '../utils/commandManager';
import { conditionalRegistration, requireMinVersion } from '../utils/dependentRegistration';
import { conditionalRegistration, requireMinVersion, requireSomeCapability } from '../utils/dependentRegistration';
import { DocumentSelector } from '../utils/documentSelector';
import { TelemetryReporter } from '../utils/telemetry';
import * as typeconverts from '../utils/typeConverters';
......@@ -108,10 +108,10 @@ export function register(
) {
return conditionalRegistration([
requireMinVersion(client, OrganizeImportsCodeActionProvider.minVersion),
requireSomeCapability(client, ClientCapability.Semantic),
], () => {
const organizeImportsProvider = new OrganizeImportsCodeActionProvider(client, commandManager, fileConfigurationManager, telemetryReporter);
return vscode.languages.registerCodeActionsProvider(selector.syntax,
return vscode.languages.registerCodeActionsProvider(selector.semantic,
organizeImportsProvider,
organizeImportsProvider.metadata);
});
......
......@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { TsServerProcessFactory } from './tsServer/server';
import { OngoingRequestCancellerFactory } from './tsServer/cancellation';
import { ILogDirectoryProvider } from './tsServer/logDirectoryProvider';
import TypeScriptServiceClientHost from './typeScriptServiceClientHost';
......@@ -14,23 +15,30 @@ import * as ProjectStatus from './utils/largeProjectStatus';
import { lazy, Lazy } from './utils/lazy';
import ManagedFileContextManager from './utils/managedFileContext';
import { PluginManager } from './utils/plugins';
import { ITypeScriptVersionProvider } from './utils/versionProvider';
export function createLazyClientHost(
context: vscode.ExtensionContext,
onCaseInsenitiveFileSystem: boolean,
pluginManager: PluginManager,
commandManager: CommandManager,
logDirectoryProvider: ILogDirectoryProvider,
cancellerFactory: OngoingRequestCancellerFactory,
versionProvider: ITypeScriptVersionProvider,
processFactory: TsServerProcessFactory,
onCompletionAccepted: (item: vscode.CompletionItem) => void,
): Lazy<TypeScriptServiceClientHost> {
return lazy(() => {
const clientHost = new TypeScriptServiceClientHost(
standardLanguageDescriptions,
context.workspaceState,
onCaseInsenitiveFileSystem,
pluginManager,
commandManager,
logDirectoryProvider,
cancellerFactory,
versionProvider,
processFactory,
onCompletionAccepted);
context.subscriptions.push(clientHost);
......@@ -46,7 +54,6 @@ export function createLazyClientHost(
});
}
export function lazilyActivateClient(
lazyClientHost: Lazy<TypeScriptServiceClientHost>,
pluginManager: PluginManager,
......
......@@ -7,14 +7,16 @@ import * as vscode from 'vscode';
import type * as Proto from '../protocol';
import { EventName } from '../protocol.const';
import { CallbackMap } from '../tsServer/callbackMap';
import { OngoingRequestCanceller } from './cancellation';
import { RequestItem, RequestQueue, RequestQueueingType } from '../tsServer/requestQueue';
import { TypeScriptServerError } from '../tsServer/serverError';
import { ServerResponse, TypeScriptRequests } from '../typescriptService';
import { TypeScriptServiceConfiguration } from '../utils/configuration';
import { Disposable } from '../utils/dispose';
import { TelemetryReporter } from '../utils/telemetry';
import Tracer from '../utils/tracer';
import { TypeScriptVersionManager } from '../utils/versionManager';
import { TypeScriptVersion } from '../utils/versionProvider';
import { OngoingRequestCanceller } from './cancellation';
export enum ExectuionTarget {
Semantic,
......@@ -41,6 +43,23 @@ export interface TsServerDelegate {
onFatalError(command: string, error: Error): void;
}
export const enum TsServerProcessKind {
Main = 'main',
Syntax = 'syntax',
Semantic = 'semantic',
Diagnostics = 'diagnostics'
}
export interface TsServerProcessFactory {
fork(
tsServerPath: string,
args: readonly string[],
kind: TsServerProcessKind,
configuration: TypeScriptServiceConfiguration,
versionManager: TypeScriptVersionManager,
): TsServerProcess;
}
export interface TsServerProcess {
write(serverRequest: Proto.Request): void;
......@@ -51,7 +70,6 @@ export interface TsServerProcess {
kill(): void;
}
export class ProcessBasedTsServer extends Disposable implements ITypeScriptServer {
private readonly _requestQueue = new RequestQueue();
private readonly _callbacks = new CallbackMap<Proto.Response>();
......
......@@ -9,23 +9,15 @@ import { OngoingRequestCancellerFactory } from '../tsServer/cancellation';
import { ClientCapabilities, ClientCapability } from '../typescriptService';
import API from '../utils/api';
import { SeparateSyntaxServerConfiguration, TsServerLogLevel, TypeScriptServiceConfiguration } from '../utils/configuration';
import * as electron from '../utils/electron';
import { ILogDirectoryProvider } from './logDirectoryProvider';
import Logger from '../utils/logger';
import { TypeScriptPluginPathsProvider } from '../utils/pluginPathsProvider';
import { PluginManager } from '../utils/plugins';
import { ChildServerProcess } from '../utils/serverProcess';
import { TelemetryReporter } from '../utils/telemetry';
import Tracer from '../utils/tracer';
import { TypeScriptVersion, TypeScriptVersionProvider } from '../utils/versionProvider';
import { GetErrRoutingTsServer, ITypeScriptServer, ProcessBasedTsServer, SyntaxRoutingTsServer, TsServerDelegate } from './server';
const enum ServerKind {
Main = 'main',
Syntax = 'syntax',
Semantic = 'semantic',
Diagnostics = 'diagnostics'
}
import { TypeScriptVersionManager } from '../utils/versionManager';
import { ITypeScriptVersionProvider, TypeScriptVersion } from '../utils/versionProvider';
import { ILogDirectoryProvider } from './logDirectoryProvider';
import { GetErrRoutingTsServer, ITypeScriptServer, ProcessBasedTsServer, SyntaxRoutingTsServer, TsServerDelegate, TsServerProcessFactory, TsServerProcessKind } from './server';
const enum CompositeServerType {
/** Run a single server that handles all commands */
......@@ -43,12 +35,14 @@ const enum CompositeServerType {
export class TypeScriptServerSpawner {
public constructor(
private readonly _versionProvider: TypeScriptVersionProvider,
private readonly _versionProvider: ITypeScriptVersionProvider,
private readonly _versionManager: TypeScriptVersionManager,
private readonly _logDirectoryProvider: ILogDirectoryProvider,
private readonly _pluginPathsProvider: TypeScriptPluginPathsProvider,
private readonly _logger: Logger,
private readonly _telemetryReporter: TelemetryReporter,
private readonly _tracer: Tracer,
private readonly _factory: TsServerProcessFactory,
) { }
public spawn(
......@@ -67,26 +61,26 @@ export class TypeScriptServerSpawner {
{
const enableDynamicRouting = serverType === CompositeServerType.DynamicSeparateSyntax;
primaryServer = new SyntaxRoutingTsServer({
syntax: this.spawnTsServer(ServerKind.Syntax, version, configuration, pluginManager, cancellerFactory),
semantic: this.spawnTsServer(ServerKind.Semantic, version, configuration, pluginManager, cancellerFactory),
syntax: this.spawnTsServer(TsServerProcessKind.Syntax, version, configuration, pluginManager, cancellerFactory),
semantic: this.spawnTsServer(TsServerProcessKind.Semantic, version, configuration, pluginManager, cancellerFactory),
}, delegate, enableDynamicRouting);
break;
}
case CompositeServerType.Single:
{
primaryServer = this.spawnTsServer(ServerKind.Main, version, configuration, pluginManager, cancellerFactory);
primaryServer = this.spawnTsServer(TsServerProcessKind.Main, version, configuration, pluginManager, cancellerFactory);
break;
}
case CompositeServerType.SyntaxOnly:
{
primaryServer = this.spawnTsServer(ServerKind.Syntax, version, configuration, pluginManager, cancellerFactory);
primaryServer = this.spawnTsServer(TsServerProcessKind.Syntax, version, configuration, pluginManager, cancellerFactory);
break;
}
}
if (this.shouldUseSeparateDiagnosticsServer(configuration)) {
return new GetErrRoutingTsServer({
getErr: this.spawnTsServer(ServerKind.Diagnostics, version, configuration, pluginManager, cancellerFactory),
getErr: this.spawnTsServer(TsServerProcessKind.Diagnostics, version, configuration, pluginManager, cancellerFactory),
primary: primaryServer,
}, delegate);
}
......@@ -124,7 +118,7 @@ export class TypeScriptServerSpawner {
}
private spawnTsServer(
kind: ServerKind,
kind: TsServerProcessKind,
version: TypeScriptVersion,
configuration: TypeScriptServiceConfiguration,
pluginManager: PluginManager,
......@@ -144,12 +138,12 @@ export class TypeScriptServerSpawner {
}
this._logger.info(`<${kind}> Forking...`);
const childProcess = electron.fork(version.tsServerPath, args, this.getForkOptions(kind, configuration));
const process = this._factory.fork(version.tsServerPath, args, kind, configuration, this._versionManager);
this._logger.info(`<${kind}> Starting...`);
return new ProcessBasedTsServer(
kind,
new ChildServerProcess(childProcess),
process!,
tsServerLogFile,
canceller,
version,
......@@ -157,20 +151,8 @@ export class TypeScriptServerSpawner {
this._tracer);
}
private getForkOptions(kind: ServerKind, configuration: TypeScriptServiceConfiguration) {
const debugPort = TypeScriptServerSpawner.getDebugPort(kind);
const inspectFlag = process.env['TSS_DEBUG_BRK'] ? '--inspect-brk' : '--inspect';
const tsServerForkOptions: electron.ForkOptions = {
execArgv: [
...(debugPort ? [`${inspectFlag}=${debugPort}`] : []),
...(configuration.maxTsServerMemory ? [`--max-old-space-size=${configuration.maxTsServerMemory}`] : [])
]
};
return tsServerForkOptions;
}
private getTsServerArgs(
kind: ServerKind,
kind: TsServerProcessKind,
configuration: TypeScriptServiceConfiguration,
currentVersion: TypeScriptVersion,
apiVersion: API,
......@@ -180,7 +162,7 @@ export class TypeScriptServerSpawner {
const args: string[] = [];
let tsServerLogFile: string | undefined;
if (kind === ServerKind.Syntax) {
if (kind === TsServerProcessKind.Syntax) {
args.push('--syntaxOnly');
}
......@@ -190,11 +172,11 @@ export class TypeScriptServerSpawner {
args.push('--useSingleInferredProject');
}
if (configuration.disableAutomaticTypeAcquisition || kind === ServerKind.Syntax || kind === ServerKind.Diagnostics) {
if (configuration.disableAutomaticTypeAcquisition || kind === TsServerProcessKind.Syntax || kind === TsServerProcessKind.Diagnostics) {
args.push('--disableAutomaticTypingAcquisition');
}
if (kind === ServerKind.Semantic || kind === ServerKind.Main) {
if (kind === TsServerProcessKind.Semantic || kind === TsServerProcessKind.Main) {
args.push('--enableTelemetry');
}
......@@ -247,21 +229,6 @@ export class TypeScriptServerSpawner {
return { args, tsServerLogFile };
}
private static getDebugPort(kind: ServerKind): number | undefined {
if (kind === 'syntax') {
// We typically only want to debug the main semantic server
return undefined;
}
const value = process.env['TSS_DEBUG_BRK'] || process.env['TSS_DEBUG'];
if (value) {
const port = parseInt(value);
if (!isNaN(port)) {
return port;
}
}
return undefined;
}
private static isLoggingEnabled(configuration: TypeScriptServiceConfiguration) {
return configuration.tsServerLogLevel !== TsServerLogLevel.Off;
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type * as Proto from '../protocol';
import { TypeScriptServiceConfiguration } from '../utils/configuration';
import { TsServerProcess, TsServerProcessKind } from './server';
declare const Worker: any;
declare type Worker = any;
export class WorkerServerProcess implements TsServerProcess {
public static fork(
tsServerPath: string,
args: readonly string[],
_kind: TsServerProcessKind,
_configuration: TypeScriptServiceConfiguration,
) {
const worker = new Worker(tsServerPath);
return new WorkerServerProcess(worker, args);
}
private _onDataHandlers = new Set<(data: Proto.Response) => void>();
private _onErrorHandlers = new Set<(err: Error) => void>();
private _onExitHandlers = new Set<(code: number | null) => void>();
public constructor(
private readonly worker: Worker,
args: readonly string[],
) {
worker.addEventListener('message', (msg: any) => {
for (const handler of this._onDataHandlers) {
handler(msg.data);
}
});
worker.postMessage(args);
}
write(serverRequest: Proto.Request): void {
this.worker.postMessage(serverRequest);
}
onData(handler: (response: Proto.Response) => void): void {
this._onDataHandlers.add(handler);
}
onError(handler: (err: Error) => void): void {
this._onErrorHandlers.add(handler);
// Todo: not implemented
}
onExit(handler: (code: number | null) => void): void {
this._onExitHandlers.add(handler);
// Todo: not implemented
}
kill(): void {
this.worker.terminate();
}
}
......@@ -15,16 +15,18 @@ import LanguageProvider from './languageProvider';
import * as Proto from './protocol';
import * as PConst from './protocol.const';
import { OngoingRequestCancellerFactory } from './tsServer/cancellation';
import { ILogDirectoryProvider } from './tsServer/logDirectoryProvider';
import { TsServerProcessFactory } from './tsServer/server';
import TypeScriptServiceClient from './typescriptServiceClient';
import { coalesce, flatten } from './utils/arrays';
import { CommandManager } from './utils/commandManager';
import { Disposable } from './utils/dispose';
import * as errorCodes from './utils/errorCodes';
import { DiagnosticLanguage, LanguageDescription } from './utils/languageDescription';
import { ILogDirectoryProvider } from './tsServer/logDirectoryProvider';
import { PluginManager } from './utils/plugins';
import * as typeConverters from './utils/typeConverters';
import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus';
import { ITypeScriptVersionProvider } from './utils/versionProvider';
import VersionStatus from './utils/versionStatus';
namespace Experimental {
......@@ -59,10 +61,13 @@ export default class TypeScriptServiceClientHost extends Disposable {
constructor(
descriptions: LanguageDescription[],
workspaceState: vscode.Memento,
onCaseInsenitiveFileSystem: boolean,
pluginManager: PluginManager,
private readonly commandManager: CommandManager,
logDirectoryProvider: ILogDirectoryProvider,
cancellerFactory: OngoingRequestCancellerFactory,
versionProvider: ITypeScriptVersionProvider,
processFactory: TsServerProcessFactory,
onCompletionAccepted: (item: vscode.CompletionItem) => void,
) {
super();
......@@ -70,9 +75,12 @@ export default class TypeScriptServiceClientHost extends Disposable {
const allModeIds = this.getAllModeIds(descriptions, pluginManager);
this.client = this._register(new TypeScriptServiceClient(
workspaceState,
onCaseInsenitiveFileSystem,
pluginManager,
logDirectoryProvider,
cancellerFactory,
versionProvider,
processFactory,
allModeIds));
this.client.onDiagnosticsReceived(({ kind, resource, diagnostics }) => {
......@@ -85,7 +93,7 @@ export default class TypeScriptServiceClientHost extends Disposable {
this._register(new VersionStatus(this.client, commandManager));
this._register(new AtaProgressReporter(this.client));
this.typingsStatus = this._register(new TypingsStatus(this.client));
this.fileConfigurationManager = this._register(new FileConfigurationManager(this.client));
this.fileConfigurationManager = this._register(new FileConfigurationManager(this.client, onCaseInsenitiveFileSystem));
for (const description of descriptions) {
const manager = new LanguageProvider(this.client, description, this.commandManager, this.client.telemetryReporter, this.typingsStatus, this.fileConfigurationManager, onCompletionAccepted);
......
......@@ -3,16 +3,16 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { OngoingRequestCancellerFactory } from './tsServer/cancellation';
import BufferSyncSupport from './features/bufferSyncSupport';
import { DiagnosticKind, DiagnosticsManager } from './features/diagnostics';
import * as Proto from './protocol';
import { EventName } from './protocol.const';
import { ITypeScriptServer } from './tsServer/server';
import { OngoingRequestCancellerFactory } from './tsServer/cancellation';
import { ILogDirectoryProvider } from './tsServer/logDirectoryProvider';
import { ITypeScriptServer, TsServerProcessFactory } from './tsServer/server';
import { TypeScriptServerError } from './tsServer/serverError';
import { TypeScriptServerSpawner } from './tsServer/spawner';
import { ClientCapabilities, ClientCapability, ExecConfig, ITypeScriptServiceClient, ServerResponse, TypeScriptRequests } from './typescriptService';
......@@ -20,16 +20,15 @@ import API from './utils/api';
import { TsServerLogLevel, TypeScriptServiceConfiguration } from './utils/configuration';
import { Disposable } from './utils/dispose';
import * as fileSchemes from './utils/fileSchemes';
import { onCaseInsenitiveFileSystem } from './utils/fileSystem';
import { ILogDirectoryProvider } from './tsServer/logDirectoryProvider';
import Logger from './utils/logger';
import { isWeb } from './utils/platform';
import { TypeScriptPluginPathsProvider } from './utils/pluginPathsProvider';
import { PluginManager } from './utils/plugins';
import { TelemetryProperties, TelemetryReporter, VSCodeTelemetryReporter } from './utils/telemetry';
import Tracer from './utils/tracer';
import { inferredProjectCompilerOptions, ProjectType } from './utils/tsconfig';
import { TypeScriptVersionManager } from './utils/versionManager';
import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider';
import { ITypeScriptVersionProvider, TypeScriptVersion } from './utils/versionProvider';
const localize = nls.loadMessageBundle();
......@@ -100,7 +99,6 @@ export default class TypeScriptServiceClient extends Disposable implements IType
private _onReady?: { promise: Promise<void>; resolve: () => void; reject: () => void; };
private _configuration: TypeScriptServiceConfiguration;
private versionProvider: TypeScriptVersionProvider;
private pluginPathsProvider: TypeScriptPluginPathsProvider;
private readonly _versionManager: TypeScriptVersionManager;
......@@ -123,9 +121,12 @@ export default class TypeScriptServiceClient extends Disposable implements IType
constructor(
private readonly workspaceState: vscode.Memento,
onCaseInsenitiveFileSystem: boolean,
public readonly pluginManager: PluginManager,
private readonly logDirectoryProvider: ILogDirectoryProvider,
private readonly cancellerFactory: OngoingRequestCancellerFactory,
private readonly versionProvider: ITypeScriptVersionProvider,
private readonly processFactory: TsServerProcessFactory,
allModeIds: readonly string[]
) {
super();
......@@ -143,17 +144,18 @@ export default class TypeScriptServiceClient extends Disposable implements IType
this.numberRestarts = 0;
this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace();
this.versionProvider = new TypeScriptVersionProvider(this._configuration);
this.versionProvider.updateConfiguration(this._configuration);
this.pluginPathsProvider = new TypeScriptPluginPathsProvider(this._configuration);
this._versionManager = this._register(new TypeScriptVersionManager(this._configuration, this.versionProvider, this.workspaceState));
this._register(this._versionManager.onDidPickNewVersion(() => {
this.restartTsServer();
}));
this.bufferSyncSupport = new BufferSyncSupport(this, allModeIds, onCaseInsenitiveFileSystem());
this.bufferSyncSupport = new BufferSyncSupport(this, allModeIds, onCaseInsenitiveFileSystem);
this.onReady(() => { this.bufferSyncSupport.listen(); });
this.diagnosticsManager = new DiagnosticsManager('typescript', onCaseInsenitiveFileSystem());
this.diagnosticsManager = new DiagnosticsManager('typescript', onCaseInsenitiveFileSystem);
this.bufferSyncSupport.onDelete(resource => {
this.cancelInflightRequestsForResource(resource);
this.diagnosticsManager.delete(resource);
......@@ -194,7 +196,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
return this.apiVersion.fullVersionString;
}));
this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer);
this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this._versionManager, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer, this.processFactory);
this._register(this.pluginManager.onDidUpdateConfig(update => {
this.configurePlugin(update.pluginId, update.config);
......@@ -206,12 +208,19 @@ export default class TypeScriptServiceClient extends Disposable implements IType
}
public get capabilities() {
if (isWeb()) {
return new ClientCapabilities(
ClientCapability.Syntax,
ClientCapability.EnhancedSyntax);
}
if (this.apiVersion.gte(API.v400)) {
return new ClientCapabilities(
ClientCapability.Syntax,
ClientCapability.EnhancedSyntax,
ClientCapability.Semantic);
}
return new ClientCapabilities(
ClientCapability.Syntax,
ClientCapability.Semantic);
......@@ -345,12 +354,6 @@ export default class TypeScriptServiceClient extends Disposable implements IType
let version = this._versionManager.currentVersion;
this.info(`Using tsserver from: ${version.path}`);
if (!fs.existsSync(version.tsServerPath)) {
vscode.window.showWarningMessage(localize('noServerFound', 'The path {0} doesn\'t point to a valid tsserver install. Falling back to bundled TypeScript version.', version.path));
this._versionManager.reset();
version = this._versionManager.currentVersion;
}
const apiVersion = version.apiVersion || API.defaultVersion;
let mytoken = ++this.token;
......@@ -433,7 +436,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
handle.onEvent(event => this.dispatchEvent(event));
if (apiVersion.gte(API.v300)) {
if (apiVersion.gte(API.v300) && this.capabilities.has(ClientCapability.Semantic)) {
this.loadingIndicator.startedLoadingProject(undefined /* projectName */);
}
......
......@@ -59,7 +59,7 @@ export interface ForkOptions {
export function fork(
modulePath: string,
args: string[],
args: readonly string[],
options: ForkOptions,
): cp.ChildProcess {
const newEnv = generatePatchedEnv(process.env, modulePath);
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function isWeb(): boolean {
return typeof process !== 'undefined';
}
......@@ -3,13 +3,20 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ChildProcess } from 'child_process';
import * as stream from 'stream';
import type { ChildProcess } from 'child_process';
import * as fs from 'fs';
import type { Readable } from 'stream';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import type * as Proto from '../protocol';
import { TsServerProcess } from '../tsServer/server';
import { TsServerProcess, TsServerProcessKind } from '../tsServer/server';
import { TypeScriptServiceConfiguration } from '../utils/configuration';
import { fork } from '../utils/electron';
import { TypeScriptVersionManager } from '../utils/versionManager';
import { Disposable } from './dispose';
const localize = nls.loadMessageBundle();
const defaultSize: number = 8192;
const contentLength: string = 'Content-Length: ';
const contentLengthSize: number = Buffer.byteLength(contentLength, 'utf8');
......@@ -88,7 +95,7 @@ class Reader<T> extends Disposable {
private readonly buffer: ProtocolBuffer = new ProtocolBuffer();
private nextMessageLength: number = -1;
public constructor(readable: stream.Readable) {
public constructor(readable: Readable) {
super();
readable.on('data', data => this.onLengthData(data));
}
......@@ -130,7 +137,50 @@ class Reader<T> extends Disposable {
export class ChildServerProcess extends Disposable implements TsServerProcess {
private readonly _reader: Reader<Proto.Response>;
public constructor(
public static fork(
tsServerPath: string,
args: readonly string[],
kind: TsServerProcessKind,
configuration: TypeScriptServiceConfiguration,
versionManager: TypeScriptVersionManager,
): ChildServerProcess {
if (!fs.existsSync(tsServerPath)) {
vscode.window.showWarningMessage(localize('noServerFound', 'The path {0} doesn\'t point to a valid tsserver install. Falling back to bundled TypeScript version.', tsServerPath));
versionManager.reset();
tsServerPath = versionManager.currentVersion.tsServerPath;
}
const childProcess = fork(tsServerPath, args, this.getForkOptions(kind, configuration));
return new ChildServerProcess(childProcess);
}
private static getForkOptions(kind: TsServerProcessKind, configuration: TypeScriptServiceConfiguration) {
const debugPort = this.getDebugPort(kind);
const inspectFlag = process.env['TSS_DEBUG_BRK'] ? '--inspect-brk' : '--inspect';
const tsServerForkOptions: any = {
execArgv: [
...(debugPort ? [`${inspectFlag}=${debugPort}`] : []),
...(configuration.maxTsServerMemory ? [`--max-old-space-size=${configuration.maxTsServerMemory}`] : [])
]
};
return tsServerForkOptions;
}
private static getDebugPort(kind: TsServerProcessKind): number | undefined {
if (kind === TsServerProcessKind.Syntax) {
// We typically only want to debug the main semantic server
return undefined;
}
const value = process.env['TSS_DEBUG_BRK'] || process.env['TSS_DEBUG'];
if (value) {
const port = parseInt(value);
if (!isNaN(port)) {
return port;
}
}
return undefined;
}
private constructor(
private readonly _process: ChildProcess,
) {
super();
......
......@@ -5,9 +5,9 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { TypeScriptVersion, TypeScriptVersionProvider } from './versionProvider';
import { Disposable } from './dispose';
import { TypeScriptServiceConfiguration } from '../utils/configuration';
import { Disposable } from './dispose';
import { ITypeScriptVersionProvider, TypeScriptVersion } from './versionProvider';
const localize = nls.loadMessageBundle();
......@@ -24,7 +24,7 @@ export class TypeScriptVersionManager extends Disposable {
public constructor(
private configuration: TypeScriptServiceConfiguration,
private readonly versionProvider: TypeScriptVersionProvider,
private readonly versionProvider: ITypeScriptVersionProvider,
private readonly workspaceState: vscode.Memento
) {
super();
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import API from '../utils/api';
import { TypeScriptServiceConfiguration } from './configuration';
import { RelativeWorkspacePathResolver } from './relativePathResolver';
import { ITypeScriptVersionProvider, localize, TypeScriptVersion, TypeScriptVersionSource } from './versionProvider';
export class DiskTypeScriptVersionProvider implements ITypeScriptVersionProvider {
public constructor(
private configuration?: TypeScriptServiceConfiguration
) { }
public updateConfiguration(configuration: TypeScriptServiceConfiguration): void {
this.configuration = configuration;
}
public get defaultVersion(): TypeScriptVersion {
return this.globalVersion || this.bundledVersion;
}
public get globalVersion(): TypeScriptVersion | undefined {
if (this.configuration?.globalTsdk) {
const globals = this.loadVersionsFromSetting(TypeScriptVersionSource.UserSetting, this.configuration.globalTsdk);
if (globals && globals.length) {
return globals[0];
}
}
return this.contributedTsNextVersion;
}
public get localVersion(): TypeScriptVersion | undefined {
const tsdkVersions = this.localTsdkVersions;
if (tsdkVersions && tsdkVersions.length) {
return tsdkVersions[0];
}
const nodeVersions = this.localNodeModulesVersions;
if (nodeVersions && nodeVersions.length === 1) {
return nodeVersions[0];
}
return undefined;
}
public get localVersions(): TypeScriptVersion[] {
const allVersions = this.localTsdkVersions.concat(this.localNodeModulesVersions);
const paths = new Set<string>();
return allVersions.filter(x => {
if (paths.has(x.path)) {
return false;
}
paths.add(x.path);
return true;
});
}
public get bundledVersion(): TypeScriptVersion {
const version = this.getContributedVersion(TypeScriptVersionSource.Bundled, 'vscode.typescript-language-features', ['..', 'node_modules']);
if (version) {
return version;
}
vscode.window.showErrorMessage(localize(
'noBundledServerFound',
'VS Code\'s tsserver was deleted by another application such as a misbehaving virus detection tool. Please reinstall VS Code.'));
throw new Error('Could not find bundled tsserver.js');
}
private get contributedTsNextVersion(): TypeScriptVersion | undefined {
return this.getContributedVersion(TypeScriptVersionSource.TsNightlyExtension, 'ms-vscode.vscode-typescript-next', ['node_modules']);
}
private getContributedVersion(source: TypeScriptVersionSource, extensionId: string, pathToTs: readonly string[]): TypeScriptVersion | undefined {
try {
const extension = vscode.extensions.getExtension(extensionId);
if (extension) {
const typescriptPath = path.join(extension.extensionPath, ...pathToTs, 'typescript', 'lib');
const bundledVersion = new TypeScriptVersion(source, path.join(typescriptPath, 'tsserver.js'), DiskTypeScriptVersionProvider.getApiVersion(typescriptPath), '');
if (bundledVersion.isValid) {
return bundledVersion;
}
}
} catch {
// noop
}
return undefined;
}
private get localTsdkVersions(): TypeScriptVersion[] {
const localTsdk = this.configuration?.localTsdk;
return localTsdk ? this.loadVersionsFromSetting(TypeScriptVersionSource.WorkspaceSetting, localTsdk) : [];
}
private loadVersionsFromSetting(source: TypeScriptVersionSource, tsdkPathSetting: string): TypeScriptVersion[] {
if (path.isAbsolute(tsdkPathSetting)) {
return [
new TypeScriptVersion(source,
path.join(tsdkPathSetting, 'tsserver.js'),
DiskTypeScriptVersionProvider.getApiVersion(tsdkPathSetting))
];
}
const workspacePath = RelativeWorkspacePathResolver.asAbsoluteWorkspacePath(tsdkPathSetting);
if (workspacePath !== undefined) {
return [
new TypeScriptVersion(source,
path.join(workspacePath, 'tsserver.js'),
DiskTypeScriptVersionProvider.getApiVersion(tsdkPathSetting),
tsdkPathSetting)
];
}
return this.loadTypeScriptVersionsFromPath(source, tsdkPathSetting);
}
private get localNodeModulesVersions(): TypeScriptVersion[] {
return this.loadTypeScriptVersionsFromPath(TypeScriptVersionSource.NodeModules, path.join('node_modules', 'typescript', 'lib'))
.filter(x => x.isValid);
}
private loadTypeScriptVersionsFromPath(source: TypeScriptVersionSource, relativePath: string): TypeScriptVersion[] {
if (!vscode.workspace.workspaceFolders) {
return [];
}
const versions: TypeScriptVersion[] = [];
for (const root of vscode.workspace.workspaceFolders) {
let label: string = relativePath;
if (vscode.workspace.workspaceFolders.length > 1) {
label = path.join(root.name, relativePath);
}
const serverPath = path.join(root.uri.fsPath, relativePath);
versions.push(new TypeScriptVersion(source, path.join(serverPath, 'tsserver.js'), DiskTypeScriptVersionProvider.getApiVersion(serverPath), label));
}
return versions;
}
private static getApiVersion(serverPath: string): API | undefined {
const version = DiskTypeScriptVersionProvider.getTypeScriptVersion(serverPath);
if (version) {
return version;
}
// Allow TS developers to provide custom version
const tsdkVersion = vscode.workspace.getConfiguration().get<string | undefined>('typescript.tsdk_version', undefined);
if (tsdkVersion) {
return API.fromVersionString(tsdkVersion);
}
return undefined;
}
private static getTypeScriptVersion(serverPath: string): API | undefined {
if (!fs.existsSync(serverPath)) {
return undefined;
}
const p = serverPath.split(path.sep);
if (p.length <= 2) {
return undefined;
}
const p2 = p.slice(0, -2);
const modulePath = p2.join(path.sep);
let fileName = path.join(modulePath, 'package.json');
if (!fs.existsSync(fileName)) {
// Special case for ts dev versions
if (path.basename(modulePath) === 'built') {
fileName = path.join(modulePath, '..', 'package.json');
}
}
if (!fs.existsSync(fileName)) {
return undefined;
}
const contents = fs.readFileSync(fileName).toString();
let desc: any = null;
try {
desc = JSON.parse(contents);
} catch (err) {
return undefined;
}
if (!desc || !desc.version) {
return undefined;
}
return desc.version ? API.fromVersionString(desc.version) : undefined;
}
}
......@@ -2,17 +2,14 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import API from './api';
import { TypeScriptServiceConfiguration } from './configuration';
import { RelativeWorkspacePathResolver } from './relativePathResolver';
const localize = nls.loadMessageBundle();
export const localize = nls.loadMessageBundle();
const enum TypeScriptVersionSource {
export const enum TypeScriptVersionSource {
Bundled = 'bundled',
TsNightlyExtension = 'ts-nightly-extension',
NodeModules = 'node-modules',
......@@ -22,18 +19,15 @@ const enum TypeScriptVersionSource {
export class TypeScriptVersion {
public readonly apiVersion: API | undefined;
constructor(
public readonly source: TypeScriptVersionSource,
public readonly path: string,
private readonly _pathLabel?: string
) {
this.apiVersion = TypeScriptVersion.getApiVersion(this.tsServerPath);
}
public readonly apiVersion: API | undefined,
private readonly _pathLabel?: string,
) { }
public get tsServerPath(): string {
return path.join(this.path, 'tsserver.js');
return this.path;
}
public get pathLabel(): string {
......@@ -63,176 +57,14 @@ export class TypeScriptVersion {
return version ? version.displayName : localize(
'couldNotLoadTsVersion', 'Could not load the TypeScript version at this path');
}
public static getApiVersion(serverPath: string): API | undefined {
const version = TypeScriptVersion.getTypeScriptVersion(serverPath);
if (version) {
return version;
}
// Allow TS developers to provide custom version
const tsdkVersion = vscode.workspace.getConfiguration().get<string | undefined>('typescript.tsdk_version', undefined);
if (tsdkVersion) {
return API.fromVersionString(tsdkVersion);
}
return undefined;
}
private static getTypeScriptVersion(serverPath: string): API | undefined {
if (!fs.existsSync(serverPath)) {
return undefined;
}
const p = serverPath.split(path.sep);
if (p.length <= 2) {
return undefined;
}
const p2 = p.slice(0, -2);
const modulePath = p2.join(path.sep);
let fileName = path.join(modulePath, 'package.json');
if (!fs.existsSync(fileName)) {
// Special case for ts dev versions
if (path.basename(modulePath) === 'built') {
fileName = path.join(modulePath, '..', 'package.json');
}
}
if (!fs.existsSync(fileName)) {
return undefined;
}
const contents = fs.readFileSync(fileName).toString();
let desc: any = null;
try {
desc = JSON.parse(contents);
} catch (err) {
return undefined;
}
if (!desc || !desc.version) {
return undefined;
}
return desc.version ? API.fromVersionString(desc.version) : undefined;
}
}
export class TypeScriptVersionProvider {
public constructor(
private configuration: TypeScriptServiceConfiguration
) { }
public updateConfiguration(configuration: TypeScriptServiceConfiguration): void {
this.configuration = configuration;
}
public get defaultVersion(): TypeScriptVersion {
return this.globalVersion || this.bundledVersion;
}
public get globalVersion(): TypeScriptVersion | undefined {
if (this.configuration.globalTsdk) {
const globals = this.loadVersionsFromSetting(TypeScriptVersionSource.UserSetting, this.configuration.globalTsdk);
if (globals && globals.length) {
return globals[0];
}
}
return this.contributedTsNextVersion;
}
public get localVersion(): TypeScriptVersion | undefined {
const tsdkVersions = this.localTsdkVersions;
if (tsdkVersions && tsdkVersions.length) {
return tsdkVersions[0];
}
export interface ITypeScriptVersionProvider {
updateConfiguration(configuration: TypeScriptServiceConfiguration): void;
const nodeVersions = this.localNodeModulesVersions;
if (nodeVersions && nodeVersions.length === 1) {
return nodeVersions[0];
}
return undefined;
}
public get localVersions(): TypeScriptVersion[] {
const allVersions = this.localTsdkVersions.concat(this.localNodeModulesVersions);
const paths = new Set<string>();
return allVersions.filter(x => {
if (paths.has(x.path)) {
return false;
}
paths.add(x.path);
return true;
});
}
public get bundledVersion(): TypeScriptVersion {
const version = this.getContributedVersion(TypeScriptVersionSource.Bundled, 'vscode.typescript-language-features', ['..', 'node_modules']);
if (version) {
return version;
}
vscode.window.showErrorMessage(localize(
'noBundledServerFound',
'VS Code\'s tsserver was deleted by another application such as a misbehaving virus detection tool. Please reinstall VS Code.'));
throw new Error('Could not find bundled tsserver.js');
}
private get contributedTsNextVersion(): TypeScriptVersion | undefined {
return this.getContributedVersion(TypeScriptVersionSource.TsNightlyExtension, 'ms-vscode.vscode-typescript-next', ['node_modules']);
}
private getContributedVersion(source: TypeScriptVersionSource, extensionId: string, pathToTs: readonly string[]): TypeScriptVersion | undefined {
try {
const extension = vscode.extensions.getExtension(extensionId);
if (extension) {
const typescriptPath = path.join(extension.extensionPath, ...pathToTs, 'typescript', 'lib');
const bundledVersion = new TypeScriptVersion(source, typescriptPath, '');
if (bundledVersion.isValid) {
return bundledVersion;
}
}
} catch {
// noop
}
return undefined;
}
private get localTsdkVersions(): TypeScriptVersion[] {
const localTsdk = this.configuration.localTsdk;
return localTsdk ? this.loadVersionsFromSetting(TypeScriptVersionSource.WorkspaceSetting, localTsdk) : [];
}
private loadVersionsFromSetting(source: TypeScriptVersionSource, tsdkPathSetting: string): TypeScriptVersion[] {
if (path.isAbsolute(tsdkPathSetting)) {
return [new TypeScriptVersion(source, tsdkPathSetting)];
}
const workspacePath = RelativeWorkspacePathResolver.asAbsoluteWorkspacePath(tsdkPathSetting);
if (workspacePath !== undefined) {
return [new TypeScriptVersion(source, workspacePath, tsdkPathSetting)];
}
return this.loadTypeScriptVersionsFromPath(source, tsdkPathSetting);
}
private get localNodeModulesVersions(): TypeScriptVersion[] {
return this.loadTypeScriptVersionsFromPath(TypeScriptVersionSource.NodeModules, path.join('node_modules', 'typescript', 'lib'))
.filter(x => x.isValid);
}
private loadTypeScriptVersionsFromPath(source: TypeScriptVersionSource, relativePath: string): TypeScriptVersion[] {
if (!vscode.workspace.workspaceFolders) {
return [];
}
const versions: TypeScriptVersion[] = [];
for (const root of vscode.workspace.workspaceFolders) {
let label: string = relativePath;
if (vscode.workspace.workspaceFolders.length > 1) {
label = path.join(root.name, relativePath);
}
versions.push(new TypeScriptVersion(source, path.join(root.uri.fsPath, relativePath), label));
}
return versions;
}
readonly defaultVersion: TypeScriptVersion;
readonly globalVersion: TypeScriptVersion | undefined;
readonly localVersion: TypeScriptVersion | undefined;
readonly localVersions: readonly TypeScriptVersion[];
readonly bundledVersion: TypeScriptVersion;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册