Move event into IoSession

This commit is contained in:
Arthur Ozga 2017-11-15 18:51:25 -08:00
parent 005c86340f
commit d6c3a15ea6
4 changed files with 97 additions and 69 deletions

View file

@ -320,7 +320,6 @@ namespace ts.server {
pluginProbeLocations?: ReadonlyArray<string>;
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);

View file

@ -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<QueuedOperation>(); // 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 = <T>(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 = <T>(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 });

View file

@ -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<T>(
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 = <T>(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 <T>(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<T>(body: T, eventName: string): void {
defaultEvent(this.host, this.byteLength, this.logger, this.canUseEvents, body, eventName);
}
// For backwards-compatibility only.

View file

@ -10,7 +10,7 @@ namespace ts.server {
isKnownTypesPackageName(name: string): boolean;
installPackage(options: InstallPackageOptionsWithProjectRootPath): Promise<ApplyCodeActionCommandResult>;
enqueueInstallTypingsRequest(p: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>): void;
attach(projectService: ProjectService, eventSender?: EventSender): void;
attach(projectService: ProjectService): void;
onProjectClosed(p: Project): void;
readonly globalTypingsCacheLocation: string;
}