SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,97 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Event, Emitter } from './events';
export interface CancellationToken {
/**
* Is `true` when the token has been cancelled, `false` otherwise.
*/
isCancellationRequested: boolean;
/**
* An [event](#Event) which fires upon cancellation.
*/
onCancellationRequested: Event<any>;
}
export namespace CancellationToken {
export const None: CancellationToken = Object.freeze({
isCancellationRequested: false,
onCancellationRequested: Event.None
});
export const Cancelled: CancellationToken = Object.freeze({
isCancellationRequested: true,
onCancellationRequested: Event.None
});
}
const shortcutEvent: Event<any> = Object.freeze(function (callback, context?) {
let handle = setTimeout(callback.bind(context), 0);
return { dispose() { clearTimeout(handle); } };
});
class MutableToken implements CancellationToken {
private _isCancelled: boolean = false;
private _emitter: Emitter<any>;
public cancel() {
if (!this._isCancelled) {
this._isCancelled = true;
if (this._emitter) {
this._emitter.fire(undefined);
this._emitter = undefined;
}
}
}
get isCancellationRequested(): boolean {
return this._isCancelled;
}
get onCancellationRequested(): Event<any> {
if (this._isCancelled) {
return shortcutEvent;
}
if (!this._emitter) {
this._emitter = new Emitter<any>();
}
return this._emitter.event;
}
}
export class CancellationTokenSource {
private _token: CancellationToken;
get token(): CancellationToken {
if (!this._token) {
// be lazy and create the token only when
// actually needed
this._token = new MutableToken();
}
return this._token;
}
cancel(): void {
if (!this._token) {
// save an object by returning the default
// cancelled token when cancellation happens
// before someone asks for the token
this._token = CancellationToken.Cancelled;
} else {
(<MutableToken>this._token).cancel();
}
}
dispose(): void {
this.cancel();
}
}

View File

@@ -0,0 +1,213 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
export interface Disposable {
/**
* Dispose this object.
*/
dispose();
}
/**
* Represents a typed event.
*/
export interface Event<T> {
/**
*
* @param listener The listener function will be call when the event happens.
* @param thisArgs The 'this' which will be used when calling the event listener.
* @param disposables An array to which a {{IDisposable}} will be added. The
* @return
*/
(listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]): Disposable;
}
export namespace Event {
const _disposable = { dispose() { } };
export const None: Event<any> = function () { return _disposable; };
}
/**
* Represents a type which can release resources, such
* as event listening or a timer.
*/
class DisposableImpl implements Disposable {
/**
* Combine many disposable-likes into one. Use this method
* when having objects with a dispose function which are not
* instances of Disposable.
*
* @return Returns a new disposable which, upon dispose, will
* dispose all provides disposable-likes.
*/
static from(...disposables: { dispose(): any }[]): DisposableImpl {
return new DisposableImpl(function () {
if (disposables) {
for (let disposable of disposables) {
disposable.dispose();
}
disposables = undefined;
}
});
}
private _callOnDispose: Function;
constructor(callOnDispose: Function) {
this._callOnDispose = callOnDispose;
}
/**
* Dispose this object.
*/
dispose(): any {
if (typeof this._callOnDispose === 'function') {
this._callOnDispose();
this._callOnDispose = undefined;
}
}
}
class CallbackList {
private _callbacks: Function[];
private _contexts: any[];
public add(callback: Function, context: any = null, bucket?: Disposable[]): void {
if (!this._callbacks) {
this._callbacks = [];
this._contexts = [];
}
this._callbacks.push(callback);
this._contexts.push(context);
if (Array.isArray(bucket)) {
bucket.push({ dispose: () => this.remove(callback, context) });
}
}
public remove(callback: Function, context: any = null): void {
if (!this._callbacks) {
return;
}
var foundCallbackWithDifferentContext = false;
for (var i = 0, len = this._callbacks.length; i < len; i++) {
if (this._callbacks[i] === callback) {
if (this._contexts[i] === context) {
// callback & context match => remove it
this._callbacks.splice(i, 1);
this._contexts.splice(i, 1);
return;
} else {
foundCallbackWithDifferentContext = true;
}
}
}
if (foundCallbackWithDifferentContext) {
throw new Error('When adding a listener with a context, you should remove it with the same context');
}
}
public invoke(...args: any[]): any[] {
if (!this._callbacks) {
return;
}
var ret: any[] = [],
callbacks = this._callbacks.slice(0),
contexts = this._contexts.slice(0);
for (var i = 0, len = callbacks.length; i < len; i++) {
try {
ret.push(callbacks[i].apply(contexts[i], args));
} catch (e) {
console.error(e);
}
}
return ret;
}
public isEmpty(): boolean {
return !this._callbacks || this._callbacks.length === 0;
}
public dispose(): void {
this._callbacks = undefined;
this._contexts = undefined;
}
}
export interface EmitterOptions {
onFirstListenerAdd?: Function;
onLastListenerRemove?: Function;
}
export class Emitter<T> {
private static _noop = function () { };
private _event: Event<T>;
private _callbacks: CallbackList;
constructor(private _options?: EmitterOptions) {
}
/**
* For the public to allow to subscribe
* to events from this Emitter
*/
get event(): Event<T> {
if (!this._event) {
this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => {
if (!this._callbacks) {
this._callbacks = new CallbackList();
}
if (this._options && this._options.onFirstListenerAdd && this._callbacks.isEmpty()) {
this._options.onFirstListenerAdd(this);
}
this._callbacks.add(listener, thisArgs);
let result: Disposable;
result = {
dispose: () => {
this._callbacks.remove(listener, thisArgs);
result.dispose = Emitter._noop;
if (this._options && this._options.onLastListenerRemove && this._callbacks.isEmpty()) {
this._options.onLastListenerRemove(this);
}
}
};
if (Array.isArray(disposables)) {
disposables.push(result);
}
return result;
};
}
return this._event;
}
/**
* To be kept private to fire an event to
* subscribers
*/
fire(event: T): any {
if (this._callbacks) {
this._callbacks.invoke.call(this._callbacks, event);
}
}
dispose() {
if (this._callbacks) {
this._callbacks.dispose();
this._callbacks = undefined;
}
}
}

View File

@@ -0,0 +1,47 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
const toString = Object.prototype.toString;
export function defined(value: any): boolean {
return typeof value !== 'undefined';
}
export function undefined(value: any): boolean {
return typeof value === 'undefined';
}
export function nil(value: any): boolean {
return value === null;
}
export function boolean(value: any): value is boolean {
return value === true || value === false;
}
export function string(value: any): value is string {
return toString.call(value) === '[object String]';
}
export function number(value: any): value is number {
return toString.call(value) === '[object Number]';
}
export function error(value: any): value is Error {
return toString.call(value) === '[object Error]';
}
export function func(value: any): value is Function {
return toString.call(value) === '[object Function]';
}
export function array<T>(value: any): value is T[] {
return Array.isArray(value);
}
export function stringArray(value: any): value is string[] {
return array(value) && (<any[]>value).every(elem => string(elem));
}

View File

@@ -0,0 +1,576 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import * as is from './is';
import {
Message,
RequestMessage, RequestType, isRequestMessage,
ResponseMessage, isReponseMessage, ResponseError, ErrorCodes,
NotificationMessage, NotificationType, isNotificationMessage
} from './messages';
import { MessageReader, DataCallback, StreamMessageReader, IPCMessageReader } from './messageReader';
import { MessageWriter, StreamMessageWriter, IPCMessageWriter } from './messageWriter';
import { Disposable, Event, Emitter } from './events';
import { CancellationTokenSource, CancellationToken } from './cancellation';
export {
Message, ErrorCodes, ResponseError,
RequestMessage, RequestType,
NotificationMessage, NotificationType,
MessageReader, DataCallback, StreamMessageReader, IPCMessageReader,
MessageWriter, StreamMessageWriter, IPCMessageWriter,
CancellationTokenSource, CancellationToken,
Disposable, Event, Emitter
}
interface CancelParams {
/**
* The request id to cancel.
*/
id: number | string;
}
namespace CancelNotification {
export const type: NotificationType<CancelParams> = { get method() { return '$/cancelRequest'; } };
}
export interface RequestHandler<P, R, E> {
(params: P, token: CancellationToken): R | ResponseError<E> | Thenable<R | ResponseError<E>>;
}
export interface NotificationHandler<P> {
(params: P): void;
}
export interface Logger {
error(message: string): void;
warn(message: string): void;
info(message: string): void;
log(message: string): void;
}
export enum Trace {
Off, Messages, Verbose
}
export type TraceValues = 'off' | 'messages' | 'verbose';
export namespace Trace {
export function fromString(value: string): Trace {
value = value.toLowerCase();
switch (value) {
case 'off':
return Trace.Off;
case 'messages':
return Trace.Messages;
case 'verbose':
return Trace.Verbose;
default:
return Trace.Off;
}
}
export function toString(value: Trace): TraceValues {
switch (value) {
case Trace.Off:
return 'off';
case Trace.Messages:
return 'messages';
case Trace.Verbose:
return 'verbose';
default:
return 'off';
}
}
}
export interface SetTraceParams {
value: TraceValues;
}
export namespace SetTraceNotification {
export const type: NotificationType<SetTraceParams> = { get method() { return '$/setTraceNotification'; } };
}
export interface LogTraceParams {
message: string;
verbose?: string;
}
export namespace LogTraceNotification {
export const type: NotificationType<LogTraceParams> = { get method() { return '$/logTraceNotification'; } };
}
export interface Tracer {
log(message: string, data?: string): void;
}
export interface MessageConnection {
sendRequest<P, R, E>(type: RequestType<P, R, E>, params: P, token?: CancellationToken): Thenable<R>;
onRequest<P, R, E>(type: RequestType<P, R, E>, handler: RequestHandler<P, R, E>): void;
sendNotification<P>(type: NotificationType<P>, params?: P): void;
onNotification<P>(type: NotificationType<P>, handler: NotificationHandler<P>): void;
trace(value: Trace, tracer: Tracer, sendNotification?: boolean): void;
onError: Event<[Error, Message, number]>;
onClose: Event<void>;
onUnhandledNotification: Event<NotificationMessage>;
listen();
onDispose: Event<void>;
dispose(): void;
}
export interface ServerMessageConnection extends MessageConnection {
}
export interface ClientMessageConnection extends MessageConnection {
}
interface ResponsePromise {
method: string;
timerStart: number;
resolve: (response) => void;
reject: (error: any) => void
}
enum ConnectionState {
New = 1,
Listening = 2,
Closed = 3,
Disposed = 4
}
function createMessageConnection<T extends MessageConnection>(messageReader: MessageReader, messageWriter: MessageWriter, logger: Logger, client: boolean = false): T {
let sequenceNumber = 0;
const version: string = '2.0';
let requestHandlers: { [name: string]: RequestHandler<any, any, any> } = Object.create(null);
let eventHandlers: { [name: string]: NotificationHandler<any> } = Object.create(null);
let responsePromises: { [name: string]: ResponsePromise } = Object.create(null);
let requestTokens: { [id: string]: CancellationTokenSource } = Object.create(null);
let trace: Trace = Trace.Off;
let tracer: Tracer;
let state: ConnectionState = ConnectionState.New;
let errorEmitter: Emitter<[Error, Message, number]> = new Emitter<[Error, Message, number]>();
let closeEmitter: Emitter<void> = new Emitter<void>();
let unhandledNotificationEmitter: Emitter<NotificationMessage> = new Emitter<NotificationMessage>();
let disposeEmitter: Emitter<void> = new Emitter<void>();
function isListening(): boolean {
return state === ConnectionState.Listening;
}
function isClosed(): boolean {
return state === ConnectionState.Closed;
}
function isDisposed(): boolean {
return state === ConnectionState.Disposed;
}
function closeHandler(): void {
if (state === ConnectionState.New || state === ConnectionState.Listening) {
state = ConnectionState.Closed;
closeEmitter.fire(undefined);
}
// If the connection is disposed don't sent close events.
};
function readErrorHandler(error: Error): void {
errorEmitter.fire([error, undefined, undefined]);
}
function writeErrorHandler(data: [Error, Message, number]): void {
errorEmitter.fire(data);
}
messageReader.onClose(closeHandler);
messageReader.onError(readErrorHandler);
messageWriter.onClose(closeHandler);
messageWriter.onError(writeErrorHandler);
function handleRequest(requestMessage: RequestMessage) {
if (isDisposed()) {
// we return here silently since we fired an event when the
// connection got disposed.
return;
}
function reply(resultOrError: any | ResponseError<any>): void {
let message: ResponseMessage = {
jsonrpc: version,
id: requestMessage.id
};
if (resultOrError instanceof ResponseError) {
message.error = (<ResponseError<any>>resultOrError).toJson();
} else {
message.result = is.undefined(resultOrError) ? null : resultOrError;
}
messageWriter.write(message);
}
function replyError(error: ResponseError<any>) {
let message: ResponseMessage = {
jsonrpc: version,
id: requestMessage.id,
error: error.toJson()
};
messageWriter.write(message);
}
function replySuccess(result: any) {
// The JSON RPC defines that a response must either have a result or an error
// So we can't treat undefined as a valid response result.
if (is.undefined(result)) {
result = null;
}
let message: ResponseMessage = {
jsonrpc: version,
id: requestMessage.id,
result: result
};
messageWriter.write(message);
}
let requestHandler = requestHandlers[requestMessage.method];
if (requestHandler) {
let cancellationSource = new CancellationTokenSource();
let tokenKey = String(requestMessage.id);
requestTokens[tokenKey] = cancellationSource;
try {
let handlerResult = requestHandler(requestMessage.params, cancellationSource.token);
let promise = <Thenable<any | ResponseError<any>>>handlerResult;
if (!handlerResult) {
delete requestTokens[tokenKey];
replySuccess(handlerResult);
} else if (promise.then) {
promise.then((resultOrError): any | ResponseError<any> => {
delete requestTokens[tokenKey];
reply(resultOrError);
}, error => {
delete requestTokens[tokenKey];
if (error instanceof ResponseError) {
replyError(<ResponseError<any>>error);
} else if (error && is.string(error.message)) {
replyError(new ResponseError<void>(ErrorCodes.InternalError, `Request ${requestMessage.method} failed with message: ${error.message}`));
} else {
replyError(new ResponseError<void>(ErrorCodes.InternalError, `Request ${requestMessage.method} failed unexpectedly without providing any details.`));
}
});
} else {
delete requestTokens[tokenKey];
reply(handlerResult);
}
} catch (error) {
delete requestTokens[tokenKey];
if (error instanceof ResponseError) {
reply(<ResponseError<any>>error);
} else if (error && is.string(error.message)) {
replyError(new ResponseError<void>(ErrorCodes.InternalError, `Request ${requestMessage.method} failed with message: ${error.message}`));
} else {
replyError(new ResponseError<void>(ErrorCodes.InternalError, `Request ${requestMessage.method} failed unexpectedly without providing any details.`));
}
}
} else {
replyError(new ResponseError<void>(ErrorCodes.MethodNotFound, `Unhandled method ${requestMessage.method}`));
}
}
function handleResponse(responseMessage: ResponseMessage) {
if (isDisposed()) {
// See handle request.
return;
}
let key = String(responseMessage.id);
let responsePromise = responsePromises[key];
if (trace != Trace.Off && tracer) {
traceResponse(responseMessage, responsePromise);
}
if (responsePromise) {
delete responsePromises[key];
try {
if (is.defined(responseMessage.error)) {
let error = responseMessage.error;
responsePromise.reject(new ResponseError(error.code, error.message, error.data));
} else if (is.defined(responseMessage.result)) {
responsePromise.resolve(responseMessage.result);
} else {
throw new Error('Should never happen.');
}
} catch (error) {
if (error.message) {
logger.error(`Response handler '${responsePromise.method}' failed with message: ${error.message}`);
} else {
logger.error(`Response handler '${responsePromise.method}' failed unexpectedly.`);
}
}
}
}
function handleNotification(message: NotificationMessage) {
if (isDisposed()) {
// See handle request.
return;
}
let eventHandler: NotificationHandler<any>;
if (message.method === CancelNotification.type.method) {
eventHandler = (params: CancelParams) => {
let id = params.id;
let source = requestTokens[String(id)];
if (source) {
source.cancel();
}
}
} else {
eventHandler = eventHandlers[message.method];
}
if (eventHandler) {
try {
if (trace != Trace.Off && tracer) {
traceReceivedNotification(message);
}
eventHandler(message.params);
} catch (error) {
if (error.message) {
logger.error(`Notification handler '${message.method}' failed with message: ${error.message}`);
} else {
logger.error(`Notification handler '${message.method}' failed unexpectedly.`);
}
}
} else {
unhandledNotificationEmitter.fire(message);
}
}
function handleInvalidMessage(message: Message) {
if (!message) {
logger.error('Received empty message.');
return;
}
logger.error(`Received message which is neither a response nor a notification message:\n${JSON.stringify(message, null, 4)}`);
// Test whether we find an id to reject the promise
let responseMessage: ResponseMessage = message as ResponseMessage;
if (is.string(responseMessage.id) || is.number(responseMessage.id)) {
let key = String(responseMessage.id);
let responseHandler = responsePromises[key];
if (responseHandler) {
responseHandler.reject(new Error('The received response has neither a result nor an error property.'));
}
}
}
function traceRequest(message: RequestMessage): void {
let data: string = undefined;
if (trace === Trace.Verbose && message.params) {
data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
}
tracer.log(`Sending request '${message.method} - (${message.id})'.`, data);
}
function traceSendNotification(message: NotificationMessage): void {
let data: string = undefined;
if (trace === Trace.Verbose) {
if (message.params) {
data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
} else {
data = 'No parameters provided.\n\n';
}
}
tracer.log(`Sending notification '${message.method}'.`, data);
}
function traceReceivedNotification(message: NotificationMessage): void {
if (message.method === LogTraceNotification.type.method) {
return;
}
let data: string = undefined;
if (trace === Trace.Verbose) {
if (message.params) {
data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
} else {
data = 'No parameters provided.\n\n';
}
}
tracer.log(`Received notification '${message.method}'.`, data);
}
function traceResponse(message: ResponseMessage, responsePromise: ResponsePromise): void {
let data: string = undefined;
if (trace === Trace.Verbose) {
if (message.error && message.error.data) {
data = `Error data: ${JSON.stringify(message.error.data, null, 4)}\n\n`;
} else {
if (message.result) {
data = `Result: ${JSON.stringify(message.result, null, 4)}\n\n`;
} else if (is.undefined(message.error)) {
data = 'No result returned.\n\n';
}
}
}
if (responsePromise) {
let error = message.error ? ` Request failed: ${message.error.message} (${message.error.code}).` : '';
tracer.log(`Received response '${responsePromise.method} - (${message.id})' in ${Date.now() - responsePromise.timerStart}ms.${error}`, data);
} else {
tracer.log(`Received response ${message.id} without active response promise.`, data);
}
}
let callback: DataCallback = (message) => {
if (isRequestMessage(message)) {
handleRequest(message);
} else if (isReponseMessage(message)) {
handleResponse(message)
} else if (isNotificationMessage(message)) {
handleNotification(message);
} else {
handleInvalidMessage(message);
}
};
function throwIfClosedOrDisposed() {
if (isClosed()) {
throw new Error('Connection is closed.');
}
if (isDisposed()) {
throw new Error('Connection is disposed.');
}
}
function throwIfListening() {
if (isListening()) {
throw new Error('Connection is already listening');
}
}
let connection: MessageConnection = {
sendNotification: <P>(type: NotificationType<P>, params): void => {
throwIfClosedOrDisposed();
let notificatioMessage: NotificationMessage = {
jsonrpc: version,
method: type.method,
params: params
}
if (trace != Trace.Off && tracer) {
traceSendNotification(notificatioMessage);
}
messageWriter.write(notificatioMessage);
},
onNotification: <P>(type: NotificationType<P>, handler: NotificationHandler<P>) => {
throwIfClosedOrDisposed();
eventHandlers[type.method] = handler;
},
sendRequest: <P, R, E>(type: RequestType<P, R, E>, params: P, token?: CancellationToken) => {
throwIfClosedOrDisposed();
let id = sequenceNumber++;
let result = new Promise<R | ResponseError<E>>((resolve, reject) => {
let requestMessage: RequestMessage = {
jsonrpc: version,
id: id,
method: type.method,
params: params
}
let responsePromise: ResponsePromise = { method: type.method, timerStart: Date.now(), resolve, reject };
if (trace != Trace.Off && tracer) {
traceRequest(requestMessage);
}
try {
messageWriter.write(requestMessage);
} catch (e) {
// Writing the message failed. So we need to reject the promise.
responsePromise.reject(new ResponseError<void>(ErrorCodes.MessageWriteError, e.message ? e.message : 'Unknown reason'));
responsePromise = null;
}
if (responsePromise) {
responsePromises[String(id)] = responsePromise;
}
});
if (token) {
token.onCancellationRequested((event) => {
connection.sendNotification(CancelNotification.type, { id });
});
}
return result;
},
onRequest: <P, R, E>(type: RequestType<P, R, E>, handler: RequestHandler<P, R, E>) => {
throwIfClosedOrDisposed();
requestHandlers[type.method] = handler;
},
trace: (_value: Trace, _tracer: Tracer, sendNotification: boolean = false) => {
trace = _value;
if (trace === Trace.Off) {
tracer = null;
} else {
tracer = _tracer;
}
if (sendNotification && !isClosed() && !isDisposed()) {
connection.sendNotification(SetTraceNotification.type, { value: Trace.toString(_value) });
}
},
onError: errorEmitter.event,
onClose: closeEmitter.event,
onUnhandledNotification: unhandledNotificationEmitter.event,
onDispose: disposeEmitter.event,
dispose: () => {
if (isDisposed()) {
return;
}
state = ConnectionState.Disposed;
disposeEmitter.fire(undefined);
let error = new Error('Connection got disposed.');
Object.keys(responsePromises).forEach((key) => {
responsePromises[key].reject(error);
});
responsePromises = Object.create(null);
requestTokens = Object.create(null);
},
listen: () => {
throwIfClosedOrDisposed();
throwIfListening();
state = ConnectionState.Listening;
messageReader.listen(callback);
}
};
connection.onNotification(LogTraceNotification.type, (params) => {
if (trace === Trace.Off) {
return;
}
tracer.log(params.message, trace === Trace.Verbose ? params.verbose : undefined);
});
return connection as T;
}
function isMessageReader(value: any): value is MessageReader {
return is.defined(value.listen) && is.undefined(value.read);
}
function isMessageWriter(value: any): value is MessageWriter {
return is.defined(value.write) && is.undefined(value.end);
}
export function createServerMessageConnection(reader: MessageReader, writer: MessageWriter, logger: Logger): ServerMessageConnection;
export function createServerMessageConnection(inputStream: NodeJS.ReadableStream, outputStream: NodeJS.WritableStream, logger: Logger): ServerMessageConnection;
export function createServerMessageConnection(input: MessageReader | NodeJS.ReadableStream, output: MessageWriter | NodeJS.WritableStream, logger: Logger): ServerMessageConnection {
let reader = isMessageReader(input) ? input : new StreamMessageReader(input);
let writer = isMessageWriter(output) ? output : new StreamMessageWriter(output);
return createMessageConnection<ServerMessageConnection>(reader, writer, logger);
}
export function createClientMessageConnection(reader: MessageReader, writer: MessageWriter, logger: Logger): ClientMessageConnection;
export function createClientMessageConnection(inputStream: NodeJS.ReadableStream, outputStream: NodeJS.WritableStream, logger: Logger): ClientMessageConnection;
export function createClientMessageConnection(input: MessageReader | NodeJS.ReadableStream, output: MessageWriter | NodeJS.WritableStream, logger: Logger): ClientMessageConnection {
let reader = isMessageReader(input) ? input : new StreamMessageReader(input);
let writer = isMessageWriter(output) ? output : new StreamMessageWriter(output);
return createMessageConnection<ClientMessageConnection>(reader, writer, logger, true);
}

View File

@@ -0,0 +1,265 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import { ChildProcess } from 'child_process';
import { Message } from './messages';
import { Event, Emitter } from './events';
import * as is from './is';
let DefaultSize: number = 8192;
let CR: number = new Buffer('\r', 'ascii')[0];
let LF: number = new Buffer('\n', 'ascii')[0];
let CRLF: string = '\r\n';
class MessageBuffer {
private encoding: string;
private index: number;
private buffer: Buffer;
constructor(encoding: string = 'utf-8') {
this.encoding = encoding;
this.index = 0;
this.buffer = new Buffer(DefaultSize);
}
public append(chunk: Buffer | String): void {
var toAppend: Buffer = <Buffer>chunk;
if (typeof (chunk) == 'string') {
var str = <string>chunk;
toAppend = new Buffer(str.length);
toAppend.write(str, 0, str.length, this.encoding);
}
if (this.buffer.length - this.index >= toAppend.length) {
toAppend.copy(this.buffer, this.index, 0, toAppend.length);
} else {
var newSize = (Math.ceil((this.index + toAppend.length) / DefaultSize) + 1) * DefaultSize;
if (this.index === 0) {
this.buffer = new Buffer(newSize);
toAppend.copy(this.buffer, 0, 0, toAppend.length);
} else {
this.buffer = Buffer.concat([this.buffer.slice(0, this.index), toAppend], newSize);
}
}
this.index += toAppend.length;
}
public tryReadHeaders(): { [key: string]: string; } {
let result: { [key: string]: string; } = undefined;
let current = 0;
while (current + 3 < this.index && (this.buffer[current] !== CR || this.buffer[current + 1] !== LF || this.buffer[current + 2] !== CR || this.buffer[current + 3] !== LF)) {
current++;
}
// No header / body separator found (e.g CRLFCRLF)
if (current + 3 >= this.index) {
return result;
}
result = Object.create(null);
let headers = this.buffer.toString('ascii', 0, current).split(CRLF);
headers.forEach((header) => {
let index: number = header.indexOf(':');
if (index === -1) {
throw new Error('Message header must separate key and value using :');
}
let key = header.substr(0, index);
let value = header.substr(index + 1).trim();
result[key] = value;
})
let nextStart = current + 4;
this.buffer = this.buffer.slice(nextStart);
this.index = this.index - nextStart;
return result;
}
public tryReadContent(length: number): string {
if (this.index < length) {
return null;
}
let result = this.buffer.toString(this.encoding, 0, length);
let nextStart = length;
this.buffer.copy(this.buffer, 0, nextStart);
this.index = this.index - nextStart;
return result;
}
public get numberOfBytes(): number {
return this.index;
}
}
export interface DataCallback {
(data: Message): void;
}
export interface PartialMessageInfo {
messageToken: number;
waitingTime: number;
}
export interface MessageReader {
onError: Event<Error>;
onClose: Event<void>;
onPartialMessage: Event<PartialMessageInfo>;
listen(callback: DataCallback): void;
}
export abstract class AbstractMessageReader {
private errorEmitter: Emitter<Error>;
private closeEmitter: Emitter<void>;
private partialMessageEmitter: Emitter<PartialMessageInfo>;
constructor() {
this.errorEmitter = new Emitter<Error>();
this.closeEmitter = new Emitter<void>();
this.partialMessageEmitter = new Emitter<PartialMessageInfo>();
}
public get onError(): Event<Error> {
return this.errorEmitter.event;
}
protected fireError(error: any): void {
this.errorEmitter.fire(this.asError(error));
}
public get onClose(): Event<void> {
return this.closeEmitter.event;
}
protected fireClose(): void {
this.closeEmitter.fire(undefined);
}
public get onPartialMessage(): Event<PartialMessageInfo> {
return this.partialMessageEmitter.event;
}
protected firePartialMessage(info: PartialMessageInfo): void {
this.partialMessageEmitter.fire(info);
}
private asError(error: any): Error {
if (error instanceof Error) {
return error;
} else {
return new Error(`Reader recevied error. Reason: ${is.string(error.message) ? error.message : 'unknown'}`);
}
}
}
export class StreamMessageReader extends AbstractMessageReader implements MessageReader {
private readable: NodeJS.ReadableStream;
private callback: DataCallback;
private buffer: MessageBuffer;
private nextMessageLength: number;
private messageToken: number;
private partialMessageTimer: NodeJS.Timer;
private _partialMessageTimeout: number;
public constructor(readable: NodeJS.ReadableStream, encoding: string = 'utf-8') {
super();
this.readable = readable;
this.buffer = new MessageBuffer(encoding);
this._partialMessageTimeout = 10000;
}
public set partialMessageTimeout(timeout: number) {
this._partialMessageTimeout = timeout;
}
public get partialMessageTimeout(): number {
return this._partialMessageTimeout;
}
public listen(callback: DataCallback): void {
this.nextMessageLength = -1;
this.messageToken = 0;
this.partialMessageTimer = undefined;
this.callback = callback;
this.readable.on('data', (data: Buffer) => {
this.onData(data);
});
this.readable.on('error', (error: any) => this.fireError(error));
this.readable.on('close', () => this.fireClose());
}
private onData(data: Buffer | String): void {
this.buffer.append(data);
while (true) {
if (this.nextMessageLength === -1) {
let headers = this.buffer.tryReadHeaders();
if (!headers) {
return;
}
let contentLength = headers['Content-Length'];
if (!contentLength) {
throw new Error('Header must provide a Content-Length property.');
}
let length = parseInt(contentLength);
if (isNaN(length)) {
throw new Error('Content-Length value must be a number.');
}
this.nextMessageLength = length;
}
var msg = this.buffer.tryReadContent(this.nextMessageLength);
if (msg === null) {
/** We haven't recevied the full message yet. */
this.setPartialMessageTimer();
return;
}
this.clearPartialMessageTimer();
this.nextMessageLength = -1;
this.messageToken++;
var json = JSON.parse(msg);
this.callback(json);
}
}
private clearPartialMessageTimer(): void {
if (this.partialMessageTimer) {
clearTimeout(this.partialMessageTimer);
this.partialMessageTimer = undefined;
}
}
private setPartialMessageTimer(): void {
this.clearPartialMessageTimer();
if (this._partialMessageTimeout <= 0) {
return;
}
this.partialMessageTimer = setTimeout((token, timeout) => {
this.partialMessageTimer = undefined;
if (token === this.messageToken) {
this.firePartialMessage({ messageToken: token, waitingTime: timeout });
this.setPartialMessageTimer();
}
}, this._partialMessageTimeout, this.messageToken, this._partialMessageTimeout);
}
}
export class IPCMessageReader extends AbstractMessageReader implements MessageReader {
private process: NodeJS.Process | ChildProcess;
public constructor(process: NodeJS.Process | ChildProcess) {
super();
this.process = process;
let eventEmitter: NodeJS.EventEmitter = <NodeJS.EventEmitter>this.process;
eventEmitter.on('error', (error: any) => this.fireError(error));
eventEmitter.on('close', () => this.fireClose());
}
public listen(callback: DataCallback): void {
let eventEmitter: NodeJS.EventEmitter = <NodeJS.EventEmitter>this.process;
eventEmitter.on('message', callback);
}
}

View File

@@ -0,0 +1,118 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import { ChildProcess } from 'child_process';
import { Message } from './messages';
import { Event, Emitter } from './events';
import * as is from './is';
let ContentLength: string = 'Content-Length: ';
let CRLF = '\r\n';
export interface MessageWriter {
onError: Event<[Error, Message, number]>;
onClose: Event<void>;
write(msg: Message): void;
}
export abstract class AbstractMessageWriter {
private errorEmitter: Emitter<[Error, Message, number]>;
private closeEmitter: Emitter<void>;
constructor() {
this.errorEmitter = new Emitter<[Error, Message, number]>();
this.closeEmitter = new Emitter<void>();
}
public get onError(): Event<[Error, Message, number]> {
return this.errorEmitter.event;
}
protected fireError(error: any, message?: Message, count?: number): void {
this.errorEmitter.fire([this.asError(error), message, count]);
}
public get onClose(): Event<void> {
return this.closeEmitter.event;
}
protected fireClose(): void {
this.closeEmitter.fire(undefined);
}
private asError(error: any): Error {
if (error instanceof Error) {
return error;
} else {
return new Error(`Writer recevied error. Reason: ${is.string(error.message) ? error.message : 'unknown'}`);
}
}
}
export class StreamMessageWriter extends AbstractMessageWriter implements MessageWriter {
private writable: NodeJS.WritableStream;
private encoding: string;
private errorCount: number;
public constructor(writable: NodeJS.WritableStream, encoding: string = 'utf8') {
super();
this.writable = writable;
this.encoding = encoding;
this.errorCount = 0;
this.writable.on('error', (error) => this.fireError(error));
this.writable.on('close', () => this.fireClose());
}
public write(msg: Message): void {
let json = JSON.stringify(msg);
let contentLength = Buffer.byteLength(json, this.encoding);
let headers: string[] = [
ContentLength, contentLength.toString(), CRLF,
CRLF
];
try {
// Header must be written in ASCII encoding
this.writable.write(headers.join(''), 'ascii');
// Now write the content. This can be written in any encoding
this.writable.write(json, this.encoding);
this.errorCount = 0;
} catch (error) {
this.errorCount++;
this.fireError(error, msg, this.errorCount);
}
}
}
export class IPCMessageWriter extends AbstractMessageWriter implements MessageWriter {
private process: NodeJS.Process | ChildProcess;
private errorCount: number;
public constructor(process: NodeJS.Process | ChildProcess) {
super();
this.process = process;
this.errorCount = 0;
let eventEmitter: NodeJS.EventEmitter = <NodeJS.EventEmitter>this.process;
eventEmitter.on('error', (error) => this.fireError(error));
eventEmitter.on('close', () => this.fireClose);
}
public write(msg: Message): void {
try {
this.process.send(msg);
this.errorCount = 0;
} catch (error) {
this.errorCount++;
this.fireError(error, msg, this.errorCount);
}
}
}

View File

@@ -0,0 +1,175 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import * as is from './is';
/**
* A language server message
*/
export interface Message {
jsonrpc: string;
}
/**
* Request message
*/
export interface RequestMessage extends Message {
/**
* The request id.
*/
id: number | string;
/**
* The method to be invoked.
*/
method: string;
/**
* The method's params.
*/
params?: any
}
/**
* Predefined error codes.
*/
export namespace ErrorCodes {
// Defined by JSON RPC
export const ParseError: number = -32700;
export const InvalidRequest: number = -32600;
export const MethodNotFound: number = -32601;
export const InvalidParams: number = -32602;
export const InternalError: number = -32603;
export const serverErrorStart: number = -32099
export const serverErrorEnd: number = -32000;
// Defined by VSCode.
export const MessageWriteError: number = 1;
export const MessageReadError: number = 2;
}
export interface ResponseErrorLiteral<D> {
/**
* A number indicating the error type that occured.
*/
code: number;
/**
* A string providing a short decription of the error.
*/
message: string;
/**
* A Primitive or Structured value that contains additional
* information about the error. Can be omitted.
*/
data?: D;
}
/**
* A error object return in a response in case a request
* has failed.
*/
export class ResponseError<D> extends Error {
public code: number;
public message: string;
public data: D;
constructor(code: number, message: string, data?: D) {
super(message);
this.code = code;
this.message = message;
if (is.defined(data)) {
this.data = data;
}
}
public toJson(): ResponseErrorLiteral<D> {
let result: ResponseErrorLiteral<D> = {
code: this.code,
message: this.message
};
if (is.defined(this.data)) {
result.data = this.data
};
return result;
}
}
/**
* A response message.
*/
export interface ResponseMessage extends Message {
/**
* The request id.
*/
id: number | string;
/**
* The result of a request. This can be omitted in
* the case of an error.
*/
result?: any;
/**
* The error object in case a request fails.
*/
error?: ResponseErrorLiteral<any>;
}
/**
* A interface to type the request parameter / response pair
*/
export interface RequestType<P, R, E> {
method: string;
}
/**
* Notification Message
*/
export interface NotificationMessage extends Message {
/**
* The method to be invoked.
*/
method: string;
/**
* The notification's params.
*/
params?: any
}
export interface NotificationType<P> {
method: string;
}
/**
* Tests if the given message is a request message
*/
export function isRequestMessage(message: Message): message is RequestMessage {
let candidate = <RequestMessage>message;
return candidate && is.string(candidate.method) && (is.string(candidate.id) || is.number(candidate.id));
}
/**
* Tests if the given message is a notification message
*/
export function isNotificationMessage(message: Message): message is NotificationMessage {
let candidate = <NotificationMessage>message;
return candidate && is.string(candidate.method) && is.undefined((<any>message).id);
}
/**
* Tests if the given message is a response message
*/
export function isReponseMessage(message: Message): message is ResponseMessage {
let candidate = <ResponseMessage>message;
return candidate && (is.defined(candidate.result) || is.defined(candidate.error)) && (is.string(candidate.id) || is.number(candidate.id));
}

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "ES5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"declaration": true,
"stripInternal": true,
"outDir": "../lib"
}
}

View File

@@ -0,0 +1,112 @@
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/**
* The Thenable (E.g. PromiseLike) and Promise declarions are taken from TypeScript's
* lib.core.es6.d.ts file. See above Copyright notice.
*/
/**
* Thenable is a common denominator between ES6 promises, Q, jquery.Deferred, WinJS.Promise,
* and others. This API makes no assumption about what promise libary is being used which
* enables reusing existing code without migrating to a specific promise implementation. Still,
* we recommand the use of native promises which are available in VS Code.
*/
interface Thenable<R> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult>(onfulfilled?: (value: R) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>;
then<TResult>(onfulfilled?: (value: R) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>;
}
/**
* Represents the completion of an asynchronous operation
*/
interface Promise<T> extends Thenable<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Promise<TResult>;
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Promise<TResult>;
/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
catch(onrejected?: (reason: any) => T | Thenable<T>): Promise<T>;
}
interface PromiseConstructor {
/**
* Creates a new Promise.
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
* a resolve callback used resolve the promise with a value or the result of another promise,
* and a reject callback used to reject the promise with a provided reason or error.
*/
new <T>(executor: (resolve: (value?: T | Thenable<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T>(values: Array<T | Thenable<T>>): Promise<T[]>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T>(values: Array<T | Thenable<T>>): Promise<T>;
/**
* Creates a new rejected promise for the provided reason.
* @param reason The reason the promise was rejected.
* @returns A new rejected Promise.
*/
reject(reason: any): Promise<void>;
/**
* Creates a new rejected promise for the provided reason.
* @param reason The reason the promise was rejected.
* @returns A new rejected Promise.
*/
reject<T>(reason: any): Promise<T>;
/**
* Creates a new resolved promise for the provided value.
* @param value A promise.
* @returns A promise whose internal state matches the provided promise.
*/
resolve<T>(value: T | Thenable<T>): Promise<T>;
/**
* Creates a new resolved promise .
* @returns A resolved promise.
*/
resolve(): Promise<void>;
}
declare var Promise: PromiseConstructor;