diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 18231e0632e..093437e5565 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -320,7 +320,6 @@ namespace ts.server { pluginProbeLocations?: ReadonlyArray; allowLocalPluginLoads?: boolean; typesMapLocation?: string; - eventSender?: EventSender; } type WatchFile = (host: ServerHost, file: string, cb: FileWatcherCallback, watchType: WatchType, project?: Project) => FileWatcher; @@ -441,7 +440,7 @@ namespace ts.server { this.logger.info("No types map provided; using the default"); } - this.typingsInstaller.attach(this, opts.eventSender); + this.typingsInstaller.attach(this); this.typingsCache = new TypingsCache(this.typingsInstaller); diff --git a/src/server/server.ts b/src/server/server.ts index 68909227ac8..2f5061bc715 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -7,9 +7,9 @@ namespace ts.server { cancellationToken: ServerCancellationToken; canUseEvents: boolean; /** - * If defined, specifies the socket used to send events to the client. - * Otherwise, events are sent through the host. - */ + * If defined, specifies the socket used to send events to the client. + * Otherwise, events are sent through the host. + */ eventPort?: number; useSingleInferredProject: boolean; useInferredProjectPerProjectRoot: boolean; @@ -248,7 +248,6 @@ namespace ts.server { private installer: NodeChildProcess; private installerPidReported = false; private projectService: ProjectService; - private eventSender: EventSender | undefined; private activeRequestCount = 0; private requestQueue: QueuedOperation[] = []; private requestMap = createMap(); // Maps operation ID to newest requestQueue entry with that ID @@ -272,7 +271,11 @@ namespace ts.server { readonly globalTypingsCacheLocation: string, readonly typingSafeListLocation: string, readonly typesMapLocation: string, - private readonly npmLocation: string | undefined) { + private readonly npmLocation: string | undefined, + /** + * If undefined, event-related work will be suppressed. + */ + private eventSender: EventSender | undefined) { } isKnownTypesPackageName(name: string): boolean { @@ -311,16 +314,12 @@ namespace ts.server { } - attach(projectService: ProjectService, eventSender?: EventSender) { + attach(projectService: ProjectService) { this.projectService = projectService; if (this.logger.hasLevel(LogLevel.requestTime)) { this.logger.info("Binding..."); } - if (eventSender) { - this.eventSender = eventSender; - } - const args: string[] = [Arguments.GlobalCacheLocation, this.globalTypingsCacheLocation]; if (this.telemetryEnabled) { args.push(Arguments.EnableTelemetry); @@ -519,17 +518,15 @@ namespace ts.server { } } - class SocketEventSender implements EventSender { - private host: ServerHost; - private logger: Logger; - private eventPort: number; + class SocketEventSender extends DefaultMessageSender { private eventSocket: NodeSocket | undefined; private socketEventQueue: { body: any, eventName: string }[] | undefined; - constructor(host: ServerHost, logger: Logger, eventPort: number) { - this.host = host; - this.logger = logger; - this.eventPort = eventPort; + constructor(host: ServerHost, + byteLength: (buf: string, encoding?: string) => number, + logger: Logger, + private eventPort: number) { + super(host, byteLength, logger, /*canUseEvents*/ true); const s = net.connect({ port: this.eventPort }, () => { this.eventSocket = s; @@ -541,20 +538,20 @@ namespace ts.server { this.socketEventQueue = undefined; } }); - } - public event = (body: T, eventName: string) => { - if (!this.eventSocket) { - if (this.logger.hasLevel(LogLevel.verbose)) { - this.logger.info(`eventPort: event "${eventName}" queued, but socket not yet initialized`); + this.event = (body: T, eventName: string) => { + if (!this.eventSocket) { + if (this.logger.hasLevel(LogLevel.verbose)) { + this.logger.info(`eventPort: event "${eventName}" queued, but socket not yet initialized`); + } + (this.socketEventQueue || (this.socketEventQueue = [])).push({ body, eventName }); + return; } - (this.socketEventQueue || (this.socketEventQueue = [])).push({ body, eventName }); - return; - } - else { - Debug.assert(this.socketEventQueue === undefined); - this.writeToEventSocket(body, eventName); - } + else { + Debug.assert(this.socketEventQueue === undefined); + this.writeToEventSocket(body, eventName); + } + }; } private writeToEventSocket(body: any, eventName: string): void { @@ -566,15 +563,11 @@ namespace ts.server { constructor(options: IoSessionOptions) { const { host, eventPort, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, canUseEvents } = options; - let event: Event; - if (canUseEvents && eventPort) { - const eventSender = new SocketEventSender(host, logger, eventPort); - event = eventSender.event; - } + const messageSender = eventPort && canUseEvents ? new SocketEventSender(host, Buffer.byteLength, logger, eventPort) : new DefaultMessageSender(host, Buffer.byteLength, logger, canUseEvents); const typingsInstaller = disableAutomaticTypingAcquisition ? undefined - : new NodeTypingsInstaller(telemetryEnabled, logger, host, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation); + : new NodeTypingsInstaller(telemetryEnabled, logger, host, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, canUseEvents ? messageSender : undefined); super({ host, @@ -586,7 +579,7 @@ namespace ts.server { hrtime: process.hrtime, logger, canUseEvents, - event, + messageSender, globalPlugins: options.globalPlugins, pluginProbeLocations: options.pluginProbeLocations, allowLocalPluginLoads: options.allowLocalPluginLoads }); diff --git a/src/server/session.ts b/src/server/session.ts index 73575672bec..ff41138ae29 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -245,6 +245,58 @@ namespace ts.server { event: Event; } + export type Send = (msg: protocol.Message) => void; + + export interface MessageSender extends EventSender { + send: Send; + event: Event; + } + + function defaultSend( + host: ServerHost, + byteLength: (buf: string, encoding?: string) => number, + logger: Logger, + canUseEvents: boolean, + msg: protocol.Message) { + if (msg.type === "event" && !canUseEvents) { + if (logger.hasLevel(LogLevel.verbose)) { + logger.info(`Session does not support events: ignored event: ${JSON.stringify(msg)}`); + } + return; + } + host.write(formatMessage(msg, logger, byteLength, host.newLine)); + } + + function defaultEvent( + host: ServerHost, + byteLength: (buf: string, encoding?: string) => number, + logger: Logger, + canUseEvents: boolean, + body: T, eventName: string): void { + const ev: protocol.Event = { + seq: 0, + type: "event", + event: eventName, + body + }; + defaultSend(host, byteLength, logger, canUseEvents, ev); + } + + export class DefaultMessageSender implements MessageSender { + constructor(protected host: ServerHost, + protected byteLength: (buf: string, encoding?: string) => number, + protected logger: Logger, + protected canUseEvents: boolean) { } + + public send = (msg: protocol.Message) => { + defaultSend(this.host, this.byteLength, this.logger, this.canUseEvents, msg); + } + + public event = (body: T, eventName: string) => { + defaultEvent(this.host, this.byteLength, this.logger, this.canUseEvents, body, eventName); + } + } + export interface SessionOptions { host: ServerHost; cancellationToken: ServerCancellationToken; @@ -259,10 +311,9 @@ namespace ts.server { */ canUseEvents: boolean; /** - * An optional callback overriding the default behavior for sending events. - * if set, `canUseEvents` and `eventPort` are ignored. + * An optional callback overriding the default behavior for sending messages. */ - event?: Event; + messageSender?: MessageSender; eventHandler?: ProjectServiceEventHandler; throttleWaitMilliseconds?: number; @@ -271,9 +322,7 @@ namespace ts.server { allowLocalPluginLoads?: boolean; } - export class Session implements EventSender { - public readonly event: Event; - + export class Session implements MessageSender { private readonly gcTimer: GcTimer; protected projectService: ProjectService; private changeSeq = 0; @@ -298,23 +347,13 @@ namespace ts.server { this.byteLength = opts.byteLength; this.hrtime = opts.hrtime; this.logger = opts.logger; - this.canUseEvents = opts.canUseEvents || !!opts.event; + this.canUseEvents = opts.canUseEvents; const { throttleWaitMilliseconds } = opts; - if (opts.event) { - this.event = opts.event; - } - else { - this.event = function (body: T, eventName: string): void { - const ev: protocol.Event = { - seq: 0, - type: "event", - event: eventName, - body - }; - this.send(ev); - }; + if (opts.messageSender) { + this.send = opts.messageSender.send; + this.event = opts.messageSender.event; } this.eventHandler = this.canUseEvents @@ -340,8 +379,7 @@ namespace ts.server { eventHandler: this.eventHandler, globalPlugins: opts.globalPlugins, pluginProbeLocations: opts.pluginProbeLocations, - allowLocalPluginLoads: opts.allowLocalPluginLoads, - eventSender: this + allowLocalPluginLoads: opts.allowLocalPluginLoads }; this.projectService = new ProjectService(settings); this.gcTimer = new GcTimer(this.host, /*delay*/ 7000, this.logger); @@ -413,13 +451,11 @@ namespace ts.server { } public send(msg: protocol.Message) { - if (msg.type === "event" && !this.canUseEvents) { - if (this.logger.hasLevel(LogLevel.verbose)) { - this.logger.info(`Session does not support events: ignored event: ${JSON.stringify(msg)}`); - } - return; - } - this.host.write(formatMessage(msg, this.logger, this.byteLength, this.host.newLine)); + defaultSend(this.host, this.byteLength, this.logger, this.canUseEvents, msg); + } + + public event(body: T, eventName: string): void { + defaultEvent(this.host, this.byteLength, this.logger, this.canUseEvents, body, eventName); } // For backwards-compatibility only. diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts index 6fbf0939431..cde303bfd39 100644 --- a/src/server/typingsCache.ts +++ b/src/server/typingsCache.ts @@ -10,7 +10,7 @@ namespace ts.server { isKnownTypesPackageName(name: string): boolean; installPackage(options: InstallPackageOptionsWithProjectRootPath): Promise; enqueueInstallTypingsRequest(p: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray): void; - attach(projectService: ProjectService, eventSender?: EventSender): void; + attach(projectService: ProjectService): void; onProjectClosed(p: Project): void; readonly globalTypingsCacheLocation: string; }