/* --------------------------------------------------------------------------------------------
* 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 cp from 'child_process';
import * as stream from 'stream';
import ChildProcess = cp.ChildProcess;
import {
workspace as Workspace, window as Window, languages as Languages, extensions as Extensions, TextDocumentChangeEvent, TextDocument, Disposable, OutputChannel,
FileSystemWatcher, Uri, DiagnosticCollection, DocumentSelector,
CancellationToken, Hover as VHover, Position as VPosition, Location as VLocation, Range as VRange,
CompletionItem as VCompletionItem, CompletionList as VCompletionList, SignatureHelp as VSignatureHelp, Definition as VDefinition, DocumentHighlight as VDocumentHighlight,
SymbolInformation as VSymbolInformation, CodeActionContext as VCodeActionContext, Command as VCommand, CodeLens as VCodeLens,
FormattingOptions as VFormattingOptions, TextEdit as VTextEdit, WorkspaceEdit as VWorkspaceEdit, MessageItem,
DocumentLink as VDocumentLink
} from 'vscode';
import {
ConnectionInfo, ConnectionInfoSummary, dataprotocol, DataProtocolProvider, ConnectionProvider,
DataProtocolServerCapabilities as VDataProtocolServerCapabilities,
DataProtocolClientCapabilities, CapabilitiesProvider, MetadataProvider,
ScriptingProvider, ProviderMetadata, ScriptingResult, ScriptingCompleteResult,
QueryProvider, QueryCancelResult as VQueryCancelResult, ObjectMetadata,
ListDatabasesResult as VListDatabasesResult, ChangedConnectionInfo,
SaveResultRequestResult as VSaveResultRequestResult, ScriptingParamDetails,
SaveResultsRequestParams as VSaveResultsRequestParams, ObjectExplorerProvider,
ExpandNodeInfo, ObjectExplorerCloseSessionInfo, ObjectExplorerSession, ObjectExplorerExpandInfo,
TaskServicesProvider, ListTasksParams, ListTasksResponse, CancelTaskParams, TaskProgressInfo, TaskInfo,
AdminServicesProvider, DisasterRecoveryProvider, RestoreInfo, ExecutionPlanOptions,
SerializationProvider, FileBrowserProvider, FileBrowserOpenedParams, FileBrowserExpandedParams, FileBrowserValidatedParams,
RestoreConfigInfo, ProfilerProvider, ProfilerSessionEvents
} from 'data';
import {
Message,
RequestHandler, NotificationHandler, MessageConnection, ClientMessageConnection, Logger, createClientMessageConnection,
ErrorCodes, ResponseError, RequestType, NotificationType,
MessageReader, IPCMessageReader, MessageWriter, IPCMessageWriter, Trace, Tracer, Event, Emitter
} from 'dataprotocol-jsonrpc';
import {
Range, Position, Location, Diagnostic, DiagnosticSeverity, Command,
TextEdit, WorkspaceEdit, WorkspaceChange, TextEditChange,
TextDocumentIdentifier, CompletionItemKind, CompletionItem, CompletionList,
Hover, MarkedString,
SignatureHelp, SignatureInformation, ParameterInformation,
Definition, CodeActionContext,
DocumentHighlight, DocumentHighlightKind,
SymbolInformation, SymbolKind,
CodeLens,
FormattingOptions, DocumentLink,
ConnectionCompleteParams, IntelliSenseReadyParams,
ConnectionProviderOptions, DataProtocolServerCapabilities,
ISelectionData, QueryExecuteBatchNotificationParams,
MetadataQueryParams, MetadataQueryResult,
ScriptOperation, ScriptingCompleteParams,
DatabaseInfo, BackupConfigInfo, CreateDatabaseResponse, CreateDatabaseParams,
LoginInfo, CreateLoginResponse, CreateLoginParams,
BackupInfo, BackupResponse, BackupParams, TaskExecutionMode,
RestoreParams, RestoreResponse, RestorePlanResponse,
DefaultDatabaseInfoResponse, DefaultDatabaseInfoParams,
GetDatabaseInfoResponse, GetDatabaseInfoParams,
BackupConfigInfoResponse, FileBrowserOpenParams, FileBrowserCloseResponse,
FileBrowserCloseParams, FileBrowserExpandParams, FileBrowserValidateParams,
StartProfilingParams, StartProfilingResponse, StopProfilingParams, StopProfilingResponse,
ProfilerEventsAvailableParams
} from 'dataprotocol-languageserver-types';
import {
InitializeRequest, InitializeParams, InitializeResult, InitializeError, ClientCapabilities, ServerCapabilities, TextDocumentSyncKind,
ShutdownRequest,
ExitNotification,
LogMessageNotification, LogMessageParams, MessageType,
ShowMessageNotification, ShowMessageParams, ShowMessageRequest, ShowMessageRequestParams,
TelemetryEventNotification,
DidChangeConfigurationNotification, DidChangeConfigurationParams,
TextDocumentPositionParams,
DidOpenTextDocumentNotification, DidOpenTextDocumentParams, DidChangeTextDocumentNotification, DidChangeTextDocumentParams,
DidCloseTextDocumentNotification, DidCloseTextDocumentParams, DidSaveTextDocumentNotification, DidSaveTextDocumentParams,
DidChangeWatchedFilesNotification, DidChangeWatchedFilesParams, FileEvent, FileChangeType,
PublishDiagnosticsNotification, PublishDiagnosticsParams,
CompletionRequest, CompletionResolveRequest,
HoverRequest,
SignatureHelpRequest, DefinitionRequest, ReferencesRequest, DocumentHighlightRequest,
DocumentSymbolRequest, WorkspaceSymbolRequest, WorkspaceSymbolParams,
CodeActionRequest, CodeActionParams,
CodeLensRequest, CodeLensResolveRequest,
DocumentFormattingRequest, DocumentFormattingParams, DocumentRangeFormattingRequest, DocumentRangeFormattingParams,
DocumentOnTypeFormattingRequest, DocumentOnTypeFormattingParams,
RenameRequest, RenameParams,
DocumentLinkRequest, DocumentLinkResolveRequest, DocumentLinkParams,
RebuildIntelliSenseNotification, RebuildIntelliSenseParams,
CapabiltiesDiscoveryRequest,
ConnectionRequest, ConnectParams,
DisconnectRequest, DisconnectParams,
CancelConnectRequest, CancelConnectParams,
ChangeDatabaseParams, ChangeDatabaseRequest,
ListDatabasesRequest, ListDatabasesParams, ListDatabasesResult,
ConnectionChangedNotification, ConnectionChangedParams,
ConnectionCompleteNotification, IntelliSenseReadyNotification,
TableMetadataRequest, ViewMetadataRequest, MetadataQueryRequest,
ScriptingRequest, ScriptingCompleteNotification,
QueryCancelRequest, QueryCancelResult, QueryCancelParams,
QueryExecuteRequest, QueryExecuteSubsetResult, QueryExecuteSubsetParams,
SimpleExecuteRequest, SimpleExecuteResult, SimpleExecuteParams,
QueryExecuteBatchStartNotification, QueryExecuteBatchCompleteNotification, QueryExecuteCompleteNotification,
QueryExecuteMessageNotification, QueryDisposeParams, QueryDisposeRequest, QueryExecuteCompleteNotificationResult,
QueryExecuteMessageParams, QueryExecuteParams, QueryExecuteResultSetCompleteNotification, QueryExecuteResultSetCompleteNotificationParams,
QueryExecuteStringParams, QueryExecuteStringRequest,
QueryExecuteStatementParams, QueryExecuteStatementRequest,
QueryExecuteSubsetRequest, SaveResultRequestResult, SaveResultsRequestParams, SaveResultsAsCsvRequest, SaveResultsAsJsonRequest, SaveResultsAsExcelRequest,
EditCommitRequest, EditCommitParams,
EditCreateRowRequest, EditCreateRowParams, EditCreateRowResult,
EditDeleteRowRequest, EditDeleteRowParams,
EditDisposeRequest, EditDisposeParams,
EditInitializeRequest, EditInitializeParams, EditInitializeFiltering,
EditRevertCellRequest, EditRevertCellParams, EditRevertCellResult,
EditRevertRowRequest, EditRevertRowParams,
EditSessionReadyNotification, EditSessionReadyParams,
EditUpdateCellRequest, EditUpdateCellParams, EditUpdateCellResult,
EditSubsetRequest, EditSubsetParams, EditSubsetResult,
ObjectExplorerCreateSessionRequest, ObjectExplorerExpandRequest, ObjectExplorerRefreshRequest, ObjectExplorerCloseSessionRequest,
ObjectExplorerCreateSessionCompleteNotification, ObjectExplorerExpandCompleteNotification,
CreateDatabaseRequest, CreateLoginRequest, BackupRequest, DefaultDatabaseInfoRequest, GetDatabaseInfoRequest, BackupConfigInfoRequest,
RestoreRequest, RestorePlanRequest, CancelRestorePlanRequest, RestoreConfigInfoRequest,
ListTasksRequest, CancelTaskRequest, TaskStatusChangedNotification, TaskCreatedNotification,
LanguageFlavorChangedNotification, DidChangeLanguageFlavorParams, FileBrowserOpenRequest, FileBrowserOpenedNotification,
FileBrowserValidateRequest, FileBrowserValidatedNotification, FileBrowserExpandRequest, FileBrowserExpandedNotification, FileBrowserCloseRequest,
StartProfilingRequest, StopProfilingRequest, ProfilerEventsAvailableNotification
} from './protocol';
import * as c2p from './codeConverter';
import * as p2c from './protocolConverter';
import * as is from './utils/is';
import * as electron from './utils/electron';
import { terminate } from './utils/processes';
import { Delayer } from './utils/async';
export {
RequestType, NotificationType, NotificationHandler, RequestHandler,
ResponseError, InitializeError, ErrorCodes,
Position, Range, Location, TextDocumentIdentifier, TextDocumentPositionParams,
TextEdit, TextEditChange, WorkspaceChange,
c2p as Code2Protocol, p2c as Protocol2Code
}
declare var v8debug;
interface IConnection {
listen(): void;
sendRequest
(type: RequestType
, params: P, token?: CancellationToken): Thenable;
sendNotification(type: NotificationType
, params: P): void;
onNotification
(type: NotificationType
, handler: NotificationHandler
): void;
onRequest
(type: RequestType
, handler: RequestHandler
): void;
trace(value: Trace, tracer: Tracer, sendNotification?: boolean): void;
initialize(params: InitializeParams): Thenable;
shutdown(): Thenable;
exit(): void;
onLogMessage(handle: NotificationHandler): void;
onShowMessage(handler: NotificationHandler): void;
onTelemetry(handler: NotificationHandler): void;
didChangeConfiguration(params: DidChangeConfigurationParams): void;
didChangeWatchedFiles(params: DidChangeWatchedFilesParams): void;
didOpenTextDocument(params: DidOpenTextDocumentParams): void;
didChangeTextDocument(params: DidChangeTextDocumentParams): void;
didCloseTextDocument(params: DidCloseTextDocumentParams): void;
didSaveTextDocument(params: DidSaveTextDocumentParams): void;
onDiagnostics(handler: NotificationHandler): void;
dispose(): void;
}
class ConsoleLogger implements Logger {
public error(message: string): void {
console.error(message);
}
public warn(message: string): void {
console.warn(message);
}
public info(message: string): void {
console.info(message);
}
public log(message: string): void {
console.log(message);
}
}
interface ConnectionErrorHandler {
(error: Error, message: Message, count: number): void;
}
interface ConnectionCloseHandler {
(): void;
}
function createConnection(inputStream: stream.Readable, outputStream: NodeJS.WritableStream, errorHandler: ConnectionErrorHandler, closeHandler: ConnectionCloseHandler): IConnection;
function createConnection(inputStream: NodeJS.ReadableStream, outputStream: NodeJS.WritableStream, errorHandler: ConnectionErrorHandler, closeHandler: ConnectionCloseHandler): IConnection;
function createConnection(reader: MessageReader, writer: MessageWriter, errorHandler: ConnectionErrorHandler, closeHandler: ConnectionCloseHandler): IConnection;
function createConnection(input: any, output: any, errorHandler: ConnectionErrorHandler, closeHandler: ConnectionCloseHandler): IConnection {
let logger = new ConsoleLogger();
let connection = createClientMessageConnection(input, output, logger);
connection.onError((data) => { errorHandler(data[0], data[1], data[2]) });
connection.onClose(closeHandler);
let result: IConnection = {
listen: (): void => connection.listen(),
sendRequest: (type: RequestType
, params: P, token?: CancellationToken): Thenable => connection.sendRequest(type, params, token),
sendNotification: (type: NotificationType
, params: P): void => connection.sendNotification(type, params),
onNotification:
(type: NotificationType
, handler: NotificationHandler
): void => connection.onNotification(type, handler),
onRequest:
(type: RequestType
, handler: RequestHandler
): void => connection.onRequest(type, handler),
trace: (value: Trace, tracer: Tracer, sendNotification: boolean = false): void => connection.trace(value, tracer, sendNotification),
initialize: (params: InitializeParams) => connection.sendRequest(InitializeRequest.type, params),
shutdown: () => connection.sendRequest(ShutdownRequest.type, undefined),
exit: () => connection.sendNotification(ExitNotification.type),
onLogMessage: (handler: NotificationHandler) => connection.onNotification(LogMessageNotification.type, handler),
onShowMessage: (handler: NotificationHandler) => connection.onNotification(ShowMessageNotification.type, handler),
onTelemetry: (handler: NotificationHandler) => connection.onNotification(TelemetryEventNotification.type, handler),
didChangeConfiguration: (params: DidChangeConfigurationParams) => connection.sendNotification(DidChangeConfigurationNotification.type, params),
didChangeWatchedFiles: (params: DidChangeWatchedFilesParams) => connection.sendNotification(DidChangeWatchedFilesNotification.type, params),
didOpenTextDocument: (params: DidOpenTextDocumentParams) => connection.sendNotification(DidOpenTextDocumentNotification.type, params),
didChangeTextDocument: (params: DidChangeTextDocumentParams | DidChangeTextDocumentParams[]) => connection.sendNotification(DidChangeTextDocumentNotification.type, params),
didCloseTextDocument: (params: DidCloseTextDocumentParams) => connection.sendNotification(DidCloseTextDocumentNotification.type, params),
didSaveTextDocument: (params: DidSaveTextDocumentParams) => connection.sendNotification(DidSaveTextDocumentNotification.type, params),
onDiagnostics: (handler: NotificationHandler) => connection.onNotification(PublishDiagnosticsNotification.type, handler),
dispose: () => connection.dispose()
};
return result;
}
export interface StreamInfo {
writer: NodeJS.WritableStream;
reader: NodeJS.ReadableStream;
}
export interface ExecutableOptions {
cwd?: string;
stdio?: string | string[];
env?: any;
detached?: boolean;
}
export interface Executable {
command: string;
args?: string[];
options?: ExecutableOptions;
}
export interface ForkOptions {
cwd?: string;
env?: any;
encoding?: string;
execArgv?: string[];
}
export enum TransportKind {
stdio,
ipc
}
export interface NodeModule {
module: string;
transport?: TransportKind;
args?: string[];
runtime?: string;
options?: ForkOptions;
}
export type ServerOptions = Executable | { run: Executable; debug: Executable; } | { run: NodeModule; debug: NodeModule } | NodeModule | (() => Thenable);
/**
* An action to be performed when the connection is producing errors.
*/
export enum ErrorAction {
/**
* Continue running the server.
*/
Continue = 1,
/**
* Shutdown the server.
*/
Shutdown = 2
}
/**
* An action to be performed when the connection to a server got closed.
*/
export enum CloseAction {
/**
* Don't restart the server. The connection stays closed.
*/
DoNotRestart = 1,
/**
* Restart the server.
*/
Restart = 2,
}
/**
* A pluggable error handler that is invoked when the connection is either
* producing errors or got closed.
*/
export interface ErrorHandler {
/**
* An error has occurred while writing or reading from the connection.
*
* @param error - the error received
* @param message - the message to be delivered to the server if know.
* @param count - a count indicating how often an error is received. Will
* be reset if a message got successfully send or received.
*/
error(error: Error, message: Message, count: number): ErrorAction;
/**
* The connection to the server got closed.
*/
closed(): CloseAction
}
class DefaultErrorHandler implements ErrorHandler {
private restarts: number[];
constructor(private name: string) {
this.restarts = [];
}
public error(error: Error, message: Message, count): ErrorAction {
if (count && count <= 3) {
return ErrorAction.Continue;
}
return ErrorAction.Shutdown;
}
public closed(): CloseAction {
this.restarts.push(Date.now());
if (this.restarts.length < 5) {
return CloseAction.Restart;
} else {
let diff = this.restarts[this.restarts.length - 1] - this.restarts[0];
if (diff <= 3 * 60 * 1000) {
Window.showErrorMessage(`The ${this.name} server crashed 5 times in the last 3 minutes. The server will not be restarted.`);
return CloseAction.DoNotRestart;
} else {
this.restarts.shift();
return CloseAction.Restart;
}
}
}
}
export interface InitializationFailedHandler {
(error: ResponseError | Error | any): boolean;
}
export interface SynchronizeOptions {
configurationSection?: string | string[];
fileEvents?: FileSystemWatcher | FileSystemWatcher[];
textDocumentFilter?: (textDocument: TextDocument) => boolean;
}
export enum RevealOutputChannelOn {
Info = 1,
Warn = 2,
Error = 3,
Never = 4
}
export interface LanguageClientOptions {
documentSelector?: string | string[];
providerId: string;
synchronize?: SynchronizeOptions;
diagnosticCollectionName?: string;
outputChannelName?: string;
revealOutputChannelOn?: RevealOutputChannelOn;
/**
* The encoding use to read stdout and stderr. Defaults
* to 'utf8' if ommitted.
*/
stdioEncoding?: string;
initializationOptions?: any | (() => any);
initializationFailedHandler?: InitializationFailedHandler;
errorHandler?: ErrorHandler;
uriConverters?: {
code2Protocol: c2p.URIConverter,
protocol2Code: p2c.URIConverter
};
serverConnectionMetadata?: string;
}
export enum State {
Stopped = 1,
Running = 2
}
export interface StateChangeEvent {
oldState: State;
newState: State;
}
enum ClientState {
Initial,
Starting,
StartFailed,
Running,
Stopping,
Stopped
}
interface SyncExpression {
evaluate(textDocument: TextDocument): boolean;
}
class FalseSyncExpression implements SyncExpression {
public evaluate(textDocument: TextDocument): boolean {
return false;
}
}
class LanguageIdExpression implements SyncExpression {
constructor(private _id: string) {
}
public evaluate(textDocument: TextDocument): boolean {
return this._id === textDocument.languageId;
}
}
class FunctionSyncExpression implements SyncExpression {
constructor(private _func: (textDocument: TextDocument) => boolean) {
}
public evaluate(textDocument: TextDocument): boolean {
return this._func(textDocument);
}
}
class CompositeSyncExpression implements SyncExpression {
private _expression: SyncExpression[];
constructor(values: string[], func?: (textDocument: TextDocument) => boolean) {
this._expression = values.map(value => new LanguageIdExpression(value));
if (func) {
this._expression.push(new FunctionSyncExpression(func));
}
}
public evaluate(textDocument: TextDocument): boolean {
return this._expression.some(exp => exp.evaluate(textDocument));
}
}
export class LanguageClient {
private _id: string;
private _name: string;
private _serverOptions: ServerOptions;
private _clientOptions: LanguageClientOptions;
private _forceDebug: boolean;
private _state: ClientState;
private _onReady: Promise;
private _onReadyCallbacks: { resolve: () => void; reject: (error) => void; };
private _connection: Thenable;
private _childProcess: ChildProcess;
private _outputChannel: OutputChannel;
private _capabilites: ServerCapabilities;
private _listeners: Disposable[];
private _providers: Disposable[];
private _diagnostics: DiagnosticCollection;
private _syncExpression: SyncExpression;
private _documentSyncDelayer: Delayer;
private _fileEvents: FileEvent[];
private _fileEventDelayer: Delayer;
private _telemetryEmitter: Emitter;
private _stateChangeEmitter: Emitter;
private _trace: Trace;
private _tracer: Tracer;
private _c2p: c2p.Converter;
private _p2c: p2c.Converter;
public constructor(name: string, serverOptions: ServerOptions, clientOptions: LanguageClientOptions, forceDebug?: boolean);
public constructor(id: string, name: string, serverOptions: ServerOptions, clientOptions: LanguageClientOptions, forceDebug?: boolean);
public constructor(arg1: string, arg2: ServerOptions | string, arg3: LanguageClientOptions | ServerOptions, arg4: boolean | LanguageClientOptions, arg5?: boolean) {
let clientOptions: LanguageClientOptions;
let forceDebug: boolean;
if (is.string(arg2)) {
this._id = arg1;
this._name = arg2;
this._serverOptions = arg3 as ServerOptions;
clientOptions = arg4 as LanguageClientOptions;
forceDebug = arg5;
} else {
this._id = arg1.toLowerCase();
this._name = arg1;
this._serverOptions = arg2 as ServerOptions;
clientOptions = arg3 as LanguageClientOptions;
forceDebug = arg4 as boolean;
}
if (forceDebug === void 0) { forceDebug = false; }
this._clientOptions = clientOptions || { providerId: '' };
this._clientOptions.synchronize = this._clientOptions.synchronize || {};
this._clientOptions.errorHandler = this._clientOptions.errorHandler || new DefaultErrorHandler(this._name);
this._clientOptions.revealOutputChannelOn == this._clientOptions.revealOutputChannelOn || RevealOutputChannelOn.Error;
this._syncExpression = this.computeSyncExpression();
this._forceDebug = forceDebug;
this.state = ClientState.Initial;
this._connection = null;
this._childProcess = null;
this._outputChannel = null;
this._listeners = null;
this._providers = null;
this._diagnostics = null;
this._fileEvents = [];
this._fileEventDelayer = new Delayer(250);
this._onReady = new Promise((resolve, reject) => {
this._onReadyCallbacks = { resolve, reject };
});
this._telemetryEmitter = new Emitter();
this._stateChangeEmitter = new Emitter();
this._tracer = {
log: (message: string, data?: string) => {
this.logTrace(message, data);
}
};
this._c2p = c2p.createConverter(clientOptions.uriConverters ? clientOptions.uriConverters.code2Protocol : undefined);
this._p2c = p2c.createConverter(clientOptions.uriConverters ? clientOptions.uriConverters.protocol2Code : undefined);
}
private get state(): ClientState {
return this._state;
}
private set state(value: ClientState) {
let oldState = this.getPublicState();
this._state = value;
let newState = this.getPublicState();
if (newState !== oldState) {
this._stateChangeEmitter.fire({ oldState, newState });
}
}
private getPublicState(): State {
if (this.state === ClientState.Running) {
return State.Running;
} else {
return State.Stopped;
}
}
private computeSyncExpression(): SyncExpression {
let documentSelector = this._clientOptions.documentSelector;
let textDocumentFilter = this._clientOptions.synchronize.textDocumentFilter;
if (!documentSelector && !textDocumentFilter) {
return new FalseSyncExpression();
}
if (textDocumentFilter && !documentSelector) {
return new FunctionSyncExpression(textDocumentFilter);
}
if (!textDocumentFilter && documentSelector) {
if (is.string(documentSelector)) {
return new LanguageIdExpression(documentSelector)
} else {
return new CompositeSyncExpression(documentSelector)
}
}
if (textDocumentFilter && documentSelector) {
return new CompositeSyncExpression(
is.string(documentSelector) ? [documentSelector] : documentSelector,
textDocumentFilter);
}
}
public sendRequest(type: RequestType
, params: P, token?: CancellationToken): Thenable {
return this.onReady().then(() => {
return this.resolveConnection().then((connection) => {
return this.doSendRequest(connection, type, params, token);
});
});
}
private doSendRequest(connection: IConnection, type: RequestType
, params: P, token?: CancellationToken): Thenable {
if (this.isConnectionActive()) {
this.forceDocumentSync();
try {
return connection.sendRequest(type, params, token);
} catch (error) {
this.error(`Sending request ${type.method} failed.`, error);
}
} else {
return Promise.reject(new ResponseError(ErrorCodes.InternalError, 'Connection is closed.'));
}
}
public sendNotification(type: NotificationType
, params?: P): void {
this.onReady().then(() => {
this.resolveConnection().then((connection) => {
if (this.isConnectionActive()) {
this.forceDocumentSync();
try {
connection.sendNotification(type, params);
} catch (error) {
this.error(`Sending notification ${type.method} failed.`, error);
}
}
});
}, (error) => {
this.error(`Sending notification ${type.method} failed.`, error)
});
}
public onNotification
(type: NotificationType
, handler: NotificationHandler
): void {
this.onReady().then(() => {
this.resolveConnection().then((connection) => {
try {
connection.onNotification(type, handler);
} catch (error) {
this.error(`Registering notification handler ${type.method} failed.`, error);
}
})
}, (error) => {
});
}
public onRequest
(type: RequestType
, handler: RequestHandler
): void {
this.onReady().then(() => {
this.resolveConnection().then((connection) => {
try {
connection.onRequest(type, handler);
} catch (error) {
this.error(`Registering request handler ${type.method} failed.`, error);
}
})
}, (error) => {
});
}
public get onTelemetry(): Event {
return this._telemetryEmitter.event;
}
public get onDidChangeState(): Event {
return this._stateChangeEmitter.event;
}
public get outputChannel(): OutputChannel {
if (!this._outputChannel) {
this._outputChannel = Window.createOutputChannel(this._clientOptions.outputChannelName ? this._clientOptions.outputChannelName : this._name);
}
return this._outputChannel;
}
public get diagnostics(): DiagnosticCollection {
return this._diagnostics;
}
public createDefaultErrorHandler(): ErrorHandler {
return new DefaultErrorHandler(this._name);
}
public set trace(value: Trace) {
this._trace = value;
this.onReady().then(() => {
this.resolveConnection().then((connection) => {
connection.trace(value, this._tracer);
})
}, (error) => {
});
}
private data2String(data: any): string {
if (data instanceof ResponseError) {
const responseError = data as ResponseError;
return ` Message: ${responseError.message}\n Code: ${responseError.code} ${responseError.data ? '\n' + responseError.data.toString() : ''}`
}
if (data instanceof Error) {
if (is.string(data.stack)) {
return data.stack;
}
return (data as Error).message;
}
if (is.string(data)) {
return data;
}
return data.toString();
}
public info(message: string, data?: any): void {
this.outputChannel.appendLine(`[Info - ${(new Date().toLocaleTimeString())}] ${message}`);
if (data) {
this.outputChannel.appendLine(this.data2String(data));
}
if (this._clientOptions.revealOutputChannelOn <= RevealOutputChannelOn.Info) {
this.outputChannel.show(true);
}
}
public warn(message: string, data?: any): void {
this.outputChannel.appendLine(`[Warn - ${(new Date().toLocaleTimeString())}] ${message}`);
if (data) {
this.outputChannel.appendLine(this.data2String(data));
}
if (this._clientOptions.revealOutputChannelOn <= RevealOutputChannelOn.Warn) {
this.outputChannel.show(true);
}
}
public error(message: string, data?: any): void {
this.outputChannel.appendLine(`[Error - ${(new Date().toLocaleTimeString())}] ${message}`);
if (data) {
this.outputChannel.appendLine(this.data2String(data));
}
if (this._clientOptions.revealOutputChannelOn <= RevealOutputChannelOn.Error) {
this.outputChannel.show(true);
}
}
private logTrace(message: string, data?: any): void {
this.outputChannel.appendLine(`[Trace - ${(new Date().toLocaleTimeString())}] ${message}`);
if (data) {
this.outputChannel.appendLine(this.data2String(data));
}
this.outputChannel.show(true);
}
public needsStart(): boolean {
return this.state === ClientState.Initial || this.state === ClientState.Stopping || this.state === ClientState.Stopped;
}
public needsStop(): boolean {
return this.state === ClientState.Starting || this.state === ClientState.Running;
}
public onReady(): Promise {
return this._onReady;
}
private isConnectionActive(): boolean {
return this.state === ClientState.Running;
}
public start(): Disposable {
this._listeners = [];
this._providers = [];
// If we restart then the diagnostics collection is reused.
if (!this._diagnostics) {
this._diagnostics = this._clientOptions.diagnosticCollectionName
? Languages.createDiagnosticCollection(this._clientOptions.diagnosticCollectionName)
: Languages.createDiagnosticCollection();
}
this.state = ClientState.Starting;
if (this._clientOptions.providerId && this._clientOptions.providerId !== '') {
// hook-up SQL data protocol provider
this.hookDataProtocolProvider(this._clientOptions.providerId);
}
this.resolveConnection().then((connection) => {
connection.onLogMessage((message) => {
switch (message.type) {
case MessageType.Error:
this.error(message.message);
break;
case MessageType.Warning:
this.warn(message.message);
break;
case MessageType.Info:
this.info(message.message);
break;
default:
this.outputChannel.appendLine(message.message);
}
});
connection.onShowMessage((message) => {
switch (message.type) {
case MessageType.Error:
Window.showErrorMessage(message.message);
break;
case MessageType.Warning:
Window.showWarningMessage(message.message);
break;
case MessageType.Info:
Window.showInformationMessage(message.message);
break;
default:
Window.showInformationMessage(message.message);
}
});
connection.onRequest(ShowMessageRequest.type, (params) => {
let messageFunc: (message: string, ...items: T[]) => Thenable = null;
switch (params.type) {
case MessageType.Error:
messageFunc = Window.showErrorMessage;
break;
case MessageType.Warning:
messageFunc = Window.showWarningMessage;
break;
case MessageType.Info:
messageFunc = Window.showInformationMessage;
break;
default:
messageFunc = Window.showInformationMessage;
}
return messageFunc(params.message, ...params.actions);
});
connection.onTelemetry((data) => {
this._telemetryEmitter.fire(data);
});
connection.listen();
// Error is handled in the intialize call.
this.initialize(connection).then(null, (error) => { });
}, (error) => {
this.state = ClientState.StartFailed;
this._onReadyCallbacks.reject(error);
this.error('Starting client failed', error);
Window.showErrorMessage(`Couldn't start client ${this._name}`);
});
return new Disposable(() => {
if (this.needsStop()) {
this.stop();
}
});
}
private resolveConnection(): Thenable {
if (!this._connection) {
this._connection = this.createConnection();
}
return this._connection;
}
private initialize(connection: IConnection): Thenable {
this.refreshTrace(connection, false);
let initOption = this._clientOptions.initializationOptions;
let initParams: InitializeParams = {
processId: process.pid,
rootPath: Workspace.rootPath,
capabilities: {},
initializationOptions: is.func(initOption) ? initOption() : initOption,
trace: Trace.toString(this._trace)
};
return connection.initialize(initParams).then((result) => {
this.state = ClientState.Running;
this._capabilites = result.capabilities;
connection.onDiagnostics(params => this.handleDiagnostics(params));
if (this._capabilites.textDocumentSync !== TextDocumentSyncKind.None) {
Workspace.onDidOpenTextDocument(t => this.onDidOpenTextDoument(connection, t), null, this._listeners);
Workspace.onDidChangeTextDocument(t => this.onDidChangeTextDocument(connection, t), null, this._listeners);
Workspace.onDidCloseTextDocument(t => this.onDidCloseTextDoument(connection, t), null, this._listeners);
Workspace.onDidSaveTextDocument(t => this.onDidSaveTextDocument(connection, t), null, this._listeners);
if (this._capabilites.textDocumentSync === TextDocumentSyncKind.Full) {
this._documentSyncDelayer = new Delayer(100);
}
}
this.hookFileEvents(connection);
this.hookConfigurationChanged(connection);
this.hookCapabilities(connection);
this._onReadyCallbacks.resolve();
Workspace.textDocuments.forEach(t => this.onDidOpenTextDoument(connection, t));
return result;
}, (error: any) => {
if (this._clientOptions.initializationFailedHandler) {
if (this._clientOptions.initializationFailedHandler(error)) {
this.initialize(connection);
} else {
this.stop();
this._onReadyCallbacks.reject(error);
}
} else if (error instanceof ResponseError && error.data && error.data.retry) {
Window.showErrorMessage(error.message, { title: 'Retry', id: "retry" }).then(item => {
if (is.defined(item) && item.id === 'retry') {
this.initialize(connection);
} else {
this.stop();
this._onReadyCallbacks.reject(error);
}
});
} else {
if (error && error.message) {
Window.showErrorMessage(error.message);
}
this.error('Server initialization failed.', error);
this.stop();
this._onReadyCallbacks.reject(error);
}
});
}
public stop(): Thenable {
if (!this._connection) {
this.state = ClientState.Stopped;
return;
}
this.state = ClientState.Stopping;
this.cleanUp();
// unkook listeners
return this.resolveConnection().then(connection => {
connection.shutdown().then(() => {
connection.exit();
connection.dispose();
this.state = ClientState.Stopped;
this._connection = null;
let toCheck = this._childProcess;
this._childProcess = null;
// Remove all markers
this.checkProcessDied(toCheck);
});
});
}
private cleanUp(diagnostics: boolean = true): void {
if (this._listeners) {
this._listeners.forEach(listener => listener.dispose());
this._listeners = null;
}
if (this._providers) {
this._providers.forEach(provider => provider.dispose());
this._providers = null;
}
if (diagnostics) {
this._diagnostics.dispose();
this._diagnostics = null;
}
}
private notifyConfigurationChanged(settings: any): void {
this.onReady().then(() => {
this.resolveConnection().then(connection => {
if (this.isConnectionActive()) {
connection.didChangeConfiguration({ settings });
}
}, (error) => {
this.error(`Syncing settings failed.`, JSON.stringify(error, null, 4));
});
}, (error) => {
this.error(`Syncing settings failed.`, JSON.stringify(error, null, 4));
});
}
private notifyFileEvent(event: FileEvent): void {
this._fileEvents.push(event);
this._fileEventDelayer.trigger(() => {
this.onReady().then(() => {
this.resolveConnection().then(connection => {
if (this.isConnectionActive()) {
connection.didChangeWatchedFiles({ changes: this._fileEvents });
}
this._fileEvents = [];
});
}, (error) => {
this.error(`Notify file events failed.`, error);
});
});
}
private onDidOpenTextDoument(connection: IConnection, textDocument: TextDocument): void {
if (!this._syncExpression.evaluate(textDocument)) {
return;
}
connection.didOpenTextDocument(this._c2p.asOpenTextDocumentParams(textDocument));
}
private onDidChangeTextDocument(connection: IConnection, event: TextDocumentChangeEvent): void {
if (!this._syncExpression.evaluate(event.document)) {
return;
}
let uri: string = event.document.uri.toString();
if (this._capabilites.textDocumentSync === TextDocumentSyncKind.Incremental) {
connection.didChangeTextDocument(this._c2p.asChangeTextDocumentParams(event));
} else {
if (this._documentSyncDelayer) {
this._documentSyncDelayer.trigger(() => {
connection.didChangeTextDocument(this._c2p.asChangeTextDocumentParams(event.document));
}, -1);
}
}
}
private onDidCloseTextDoument(connection: IConnection, textDocument: TextDocument): void {
if (!this._syncExpression.evaluate(textDocument)) {
return;
}
connection.didCloseTextDocument(this._c2p.asCloseTextDocumentParams(textDocument));
}
private onDidSaveTextDocument(conneciton: IConnection, textDocument: TextDocument): void {
if (!this._syncExpression.evaluate(textDocument)) {
return;
}
conneciton.didSaveTextDocument(this._c2p.asSaveTextDocumentParams(textDocument));
}
private forceDocumentSync(): void {
if (this._documentSyncDelayer) {
this._documentSyncDelayer.forceDelivery();
}
}
private handleDiagnostics(params: PublishDiagnosticsParams) {
let uri = Uri.parse(params.uri);
let diagnostics = this._p2c.asDiagnostics(params.diagnostics);
this._diagnostics.set(uri, diagnostics);
}
private createConnection(): Thenable {
function getEnvironment(env: any): any {
if (!env) {
return process.env;
}
let result: any = Object.create(null);
Object.keys(process.env).forEach(key => result[key] = process.env[key]);
Object.keys(env).forEach(key => result[key] = env[key]);
}
function startedInDebugMode(): boolean {
let args = (process as any).execArgv;
if (args) {
return args.some((arg) => /^--debug=?/.test(arg) || /^--debug-brk=?/.test(arg));
};
return false;
}
let encoding = this._clientOptions.stdioEncoding || 'utf8';
let errorHandler = (error: Error, message: Message, count: number) => {
this.handleConnectionError(error, message, count);
}
let closeHandler = () => {
this.handleConnectionClosed();
}
let server = this._serverOptions;
// We got a function.
if (is.func(server)) {
return server().then((result) => {
let info = result as StreamInfo;
if (info.writer && info.reader) {
return createConnection(info.reader, info.writer, errorHandler, closeHandler);
} else {
let cp = result as ChildProcess;
return createConnection(cp.stdout, cp.stdin, errorHandler, closeHandler);
}
});
}
let json: { command?: string; module?: string } = null;
let runDebug = <{ run: any; debug: any; }>server;
if (is.defined(runDebug.run) || is.defined(runDebug.debug)) {
// We are under debugging. So use debug as well.
if (typeof v8debug === 'object' || this._forceDebug || startedInDebugMode()) {
json = runDebug.debug;
} else {
json = runDebug.run;
}
} else {
json = server;
}
if (is.defined(json.module)) {
let node: NodeModule = json;
if (node.runtime) {
let args: string[] = [];
let options: ForkOptions = node.options || Object.create(null);
if (options.execArgv) {
options.execArgv.forEach(element => args.push(element));
}
args.push(node.module);
if (node.args) {
node.args.forEach(element => args.push(element));
}
let execOptions: ExecutableOptions = Object.create(null);
execOptions.cwd = options.cwd || Workspace.rootPath;
execOptions.env = getEnvironment(options.env);
if (node.transport === TransportKind.ipc) {
execOptions.stdio = [null, null, null, 'ipc'];
args.push('--node-ipc');
} else if (node.transport === TransportKind.stdio) {
args.push('--stdio');
}
let process = cp.spawn(node.runtime, args, execOptions);
if (!process || !process.pid) {
return Promise.reject(`Launching server using runtime ${node.runtime} failed.`);
}
this._childProcess = process;
process.stderr.on('data', data => this.outputChannel.append(data.toString()));
if (node.transport === TransportKind.ipc) {
process.stdout.on('data', data => this.outputChannel.append(data.toString()));
return Promise.resolve(createConnection(new IPCMessageReader(process), new IPCMessageWriter(process), errorHandler, closeHandler));
} else {
return Promise.resolve(createConnection(process.stdout, process.stdin, errorHandler, closeHandler));
}
} else {
return new Promise((resolve, reject) => {
let args = node.args && node.args.slice() || [];
if (node.transport === TransportKind.ipc) {
args.push('--node-ipc');
} else if (node.transport === TransportKind.stdio) {
args.push('--stdio');
}
let options: ForkOptions = node.options || Object.create(null);
options.execArgv = options.execArgv || [];
options.cwd = options.cwd || Workspace.rootPath;
electron.fork(node.module, args || [], options, (error, cp) => {
if (error) {
reject(error);
} else {
this._childProcess = cp;
cp.stderr.on('data', data => this.outputChannel.append(data.toString()));
if (node.transport === TransportKind.ipc) {
cp.stdout.on('data', data => this.outputChannel.append(data.toString()));
resolve(createConnection(new IPCMessageReader(this._childProcess), new IPCMessageWriter(this._childProcess), errorHandler, closeHandler));
} else {
resolve(createConnection(cp.stdout, cp.stdin, errorHandler, closeHandler));
}
}
});
});
}
} else if (is.defined(json.command)) {
let command: Executable = json;
let options = command.options || {};
options.cwd = options.cwd || Workspace.rootPath;
let process = cp.spawn(command.command, command.args, command.options);
if (!process || !process.pid) {
return Promise.reject(`Launching server using command ${command.command} failed.`);
}
process.stderr.on('data', data => this.outputChannel.append(data.toString()));
this._childProcess = process;
return Promise.resolve(createConnection(process.stdout, process.stdin, errorHandler, closeHandler));
}
return Promise.reject(new Error(`Unsupported server configuartion ` + JSON.stringify(server, null, 4)));
}
private handleConnectionClosed() {
let self = this;
// Check whether this is a normal shutdown in progress or the client stopped normally.
if (this.state === ClientState.Stopping || this.state === ClientState.Stopped) {
return;
}
self._connection = null;
self._childProcess = null;
let action = this._clientOptions.errorHandler.closed();
if (action === CloseAction.DoNotRestart) {
self.error('Connection to server got closed. Server will not be restarted.');
self.state = ClientState.Stopped;
self.cleanUp();
} else if (action === CloseAction.Restart && self.state !== ClientState.Stopping) {
self.info('Connection to server got closed. Server will restart.');
self.cleanUp(false);
self.state = ClientState.Initial;
self.start();
}
}
private handleConnectionError(error: Error, message: Message, count: number) {
let action = this._clientOptions.errorHandler.error(error, message, count);
if (action === ErrorAction.Shutdown) {
this.error('Connection to server is erroring. Shutting down server.')
this.stop();
}
}
private checkProcessDied(childProcess: ChildProcess): void {
if (!childProcess) {
return;
}
setTimeout(() => {
// Test if the process is still alive. Throws an exception if not
try {
process.kill(childProcess.pid, 0);
terminate(childProcess);
} catch (error) {
// All is fine.
}
}, 2000);
}
private hookConfigurationChanged(connection: IConnection): void {
if (!this._clientOptions.synchronize.configurationSection) {
return;
}
Workspace.onDidChangeConfiguration(e => this.onDidChangeConfiguration(connection), this, this._listeners);
this.onDidChangeConfiguration(connection);
}
private refreshTrace(connection: IConnection, sendNotification: boolean = false): void {
let config = Workspace.getConfiguration(this._id);
let trace: Trace = Trace.Off;
if (config) {
trace = Trace.fromString(config.get('trace.server', 'off'));
}
this._trace = trace;
connection.trace(this._trace, this._tracer, sendNotification);
}
private onDidChangeConfiguration(connection: IConnection): void {
this.refreshTrace(connection, true);
let keys: string[] = null;
let configurationSection = this._clientOptions.synchronize.configurationSection;
if (is.string(configurationSection)) {
keys = [configurationSection];
} else if (is.stringArray(configurationSection)) {
keys = configurationSection;
}
if (keys) {
if (this.isConnectionActive()) {
connection.didChangeConfiguration({ settings: this.extractSettingsInformation(keys) });
}
}
}
private extractSettingsInformation(keys: string[]): any {
function ensurePath(config: any, path: string[]): any {
let current = config;
for (let i = 0; i < path.length - 1; i++) {
let obj = current[path[i]];
if (!obj) {
obj = Object.create(null);
current[path[i]] = obj;
}
current = obj;
}
return current;
}
let result = Object.create(null);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
let index: number = key.indexOf('.');
let config: any = null;
if (index >= 0) {
config = Workspace.getConfiguration(key.substr(0, index)).get(key.substr(index + 1));
} else {
config = Workspace.getConfiguration(key);
}
if (config) {
let path = keys[i].split('.');
ensurePath(result, path)[path[path.length - 1]] = config;
}
}
return result;
}
private hookFileEvents(connection: IConnection): void {
let fileEvents = this._clientOptions.synchronize.fileEvents;
if (!fileEvents) {
return;
}
let watchers: FileSystemWatcher[] = null;
if (is.array(fileEvents)) {
watchers = fileEvents;
} else {
watchers = [fileEvents];
}
if (!watchers) {
return;
}
watchers.forEach(watcher => {
watcher.onDidCreate((resource) => this.notifyFileEvent(
{
uri: resource.toString(),
type: FileChangeType.Created
}
), null, this._listeners);
watcher.onDidChange((resource) => this.notifyFileEvent(
{
uri: resource.toString(),
type: FileChangeType.Changed
}
), null, this._listeners);
watcher.onDidDelete((resource) => this.notifyFileEvent(
{
uri: resource.toString(),
type: FileChangeType.Deleted
}
), null, this._listeners);
});
}
private hookCapabilities(connection: IConnection): void {
let documentSelector = this._clientOptions.documentSelector;
if (!documentSelector) {
return;
}
this.hookCompletionProvider(documentSelector, connection);
this.hookHoverProvider(documentSelector, connection);
this.hookSignatureHelpProvider(documentSelector, connection);
this.hookDefinitionProvider(documentSelector, connection);
this.hookReferencesProvider(documentSelector, connection);
this.hookDocumentHighlightProvider(documentSelector, connection);
this.hookDocumentSymbolProvider(documentSelector, connection);
this.hookWorkspaceSymbolProvider(connection);
this.hookCodeActionsProvider(documentSelector, connection);
this.hookCodeLensProvider(documentSelector, connection);
this.hookDocumentFormattingProvider(documentSelector, connection);
this.hookDocumentRangeFormattingProvider(documentSelector, connection);
this.hookDocumentOnTypeFormattingProvider(documentSelector, connection);
this.hookRenameProvider(documentSelector, connection);
this.hookDocumentLinkProvider(documentSelector, connection);
}
private logFailedRequest(type: RequestType, error: any): void {
this.error(`Request ${type.method} failed.`, error);
}
/**
* SQL-Carbon Edit
* The helper method to add connection notifications after waiting for connections to be ready.
* This is needed for early DMP registration, which can be done without waiting for connection setup to finish.
*
* @param type
* @param handler
*/
private onConnectionReadyNotification(type: NotificationType
, handler: NotificationHandler
): void {
this.onReady().then(() => {
this.resolveConnection().then((connection) => {
connection.onNotification(type, handler);
});
}
);
}
private hookDataProtocolProvider(providerId: string): void {
let self = this;
let capabilitiesProvider: CapabilitiesProvider = {
getServerCapabilities(client: DataProtocolClientCapabilities): Thenable {
let capabilitiesPromise = self._clientOptions.serverConnectionMetadata === undefined ?
self.sendRequest(CapabiltiesDiscoveryRequest.type, self._c2p.asCapabilitiesParams(client), undefined) :
new Promise((resolve, reject) => resolve(self._clientOptions.serverConnectionMetadata));
return capabilitiesPromise.then(self._p2c.asServerCapabilities, (error) => {
self.logFailedRequest(ConnectionRequest.type, error);
return Promise.resolve([]);
}
);
}
};
let connectionProvider: ConnectionProvider = {
handle: -1,
connect(connUri: string, connInfo: ConnectionInfo): Thenable {
return self.sendRequest(ConnectionRequest.type, self._c2p.asConnectionParams(connUri, connInfo), undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(ConnectionRequest.type, error);
return Promise.resolve(false);
}
);
},
disconnect(connUri: string): Thenable {
let params: DisconnectParams = {
ownerUri: connUri
};
return self.sendRequest(DisconnectRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(DisconnectRequest.type, error);
return Promise.resolve(false);
}
);
},
cancelConnect(connUri: string): Thenable {
let params: CancelConnectParams = {
ownerUri: connUri
};
return self.sendRequest(CancelConnectRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(CancelConnectRequest.type, error);
return Promise.resolve(false);
}
);
},
changeDatabase(connUri: string, newDatabase: string): Thenable {
let params: ChangeDatabaseParams = {
ownerUri: connUri,
newDatabase: newDatabase
};
return self.sendRequest(ChangeDatabaseRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(ChangeDatabaseRequest.type, error);
return Promise.resolve(false);
}
);
},
listDatabases(connectionUri: string): Thenable {
let params: ListDatabasesParams = {
ownerUri: connectionUri
};
return self.sendRequest(ListDatabasesRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(ListDatabasesRequest.type, error);
return Promise.resolve(undefined);
}
);
},
rebuildIntelliSenseCache(connectionUri: string): Thenable {
let params: RebuildIntelliSenseParams = {
ownerUri: connectionUri
};
self.sendNotification(RebuildIntelliSenseNotification.type, params);
return Promise.resolve(undefined);
},
registerOnConnectionComplete(handler: (connSummary: ConnectionInfoSummary) => any) {
self.onConnectionReadyNotification(ConnectionCompleteNotification.type, (params: ConnectionCompleteParams) => {
handler({
ownerUri: params.ownerUri,
connectionId: params.connectionId,
messages: params.messages,
errorMessage: params.errorMessage,
errorNumber: params.errorNumber,
serverInfo: params.serverInfo,
connectionSummary: params.connectionSummary
});
});
},
registerOnIntelliSenseCacheComplete(handler: (connectionUri: string) => any) {
self.onConnectionReadyNotification(IntelliSenseReadyNotification.type, (params: IntelliSenseReadyParams) => {
handler(params.ownerUri);
});
},
registerOnConnectionChanged(handler: (changedConnInfo: ChangedConnectionInfo) => any) {
self.onConnectionReadyNotification(ConnectionChangedNotification.type, (params: ConnectionChangedParams) => {
handler({
connectionUri: params.ownerUri,
connection: params.connection
});
});
}
};
let queryProvider: QueryProvider = {
handle: -1,
queryType: 'MSSQL',
cancelQuery(ownerUri: string): Thenable {
let params: QueryCancelParams = { ownerUri: ownerUri };
return self.sendRequest(QueryCancelRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(QueryCancelRequest.type, error);
return Promise.reject(error);
}
);
},
runQuery(ownerUri: string, selection: ISelectionData, executionPlanOptions?: ExecutionPlanOptions): Thenable {
let params: QueryExecuteParams = {
ownerUri: ownerUri,
querySelection: selection,
executionPlanOptions: self._c2p.asExecutionPlanOptions(executionPlanOptions)
};
return self.sendRequest(QueryExecuteRequest.type, params, undefined).then(
(result) => {
return undefined;
},
(error) => {
self.logFailedRequest(QueryExecuteRequest.type, error);
return Promise.reject(error);
}
);
},
runQueryStatement(ownerUri: string, line: number, column: number): Thenable {
let params: QueryExecuteStatementParams = {
ownerUri: ownerUri,
line: line,
column: column
};
return self.sendRequest(QueryExecuteStatementRequest.type, params, undefined).then(
(result) => {
return undefined;
},
(error) => {
self.logFailedRequest(QueryExecuteStatementRequest.type, error);
return Promise.reject(error);
}
);
},
runQueryString(ownerUri: string, queryString: string): Thenable {
let params: QueryExecuteStringParams = { ownerUri: ownerUri, query: queryString };
return self.sendRequest(QueryExecuteStringRequest.type, params, undefined).then(
(result) => {
return undefined;
},
(error) => {
self.logFailedRequest(QueryExecuteStringRequest.type, error);
return Promise.reject(error);
}
);
},
runQueryAndReturn(ownerUri: string, queryString: string): Thenable {
let params: SimpleExecuteParams = { ownerUri: ownerUri, queryString: queryString };
return self.sendRequest(SimpleExecuteRequest.type, params, undefined).then(
result => {
return result;
},
error => {
self.logFailedRequest(SimpleExecuteRequest.type, error);
return Promise.reject(error);
}
);
},
getQueryRows(rowData: QueryExecuteSubsetParams): Thenable {
return self.sendRequest(QueryExecuteSubsetRequest.type, rowData, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(QueryExecuteSubsetRequest.type, error);
return Promise.reject(error);
}
);
},
disposeQuery(ownerUri: string): Thenable {
let params: QueryDisposeParams = { ownerUri: ownerUri };
return self.sendRequest(QueryDisposeRequest.type, params, undefined).then(
(result) => {
return undefined;
},
(error) => {
self.logFailedRequest(QueryDisposeRequest.type, error);
return Promise.reject(error);
}
);
},
registerOnQueryComplete(handler: (result: QueryExecuteCompleteNotificationResult) => any) {
self.onConnectionReadyNotification(QueryExecuteCompleteNotification.type, (params: QueryExecuteCompleteNotificationResult) => {
handler({
ownerUri: params.ownerUri,
batchSummaries: params.batchSummaries
});
});
},
registerOnBatchStart(handler: (batchInfo: QueryExecuteBatchNotificationParams) => any) {
self.onConnectionReadyNotification(QueryExecuteBatchStartNotification.type, (params: QueryExecuteBatchNotificationParams) => {
handler({
batchSummary: params.batchSummary,
ownerUri: params.ownerUri
});
});
},
registerOnBatchComplete(handler: (batchInfo: QueryExecuteBatchNotificationParams) => any) {
self.onConnectionReadyNotification(QueryExecuteBatchCompleteNotification.type, (params: QueryExecuteBatchNotificationParams) => {
handler({
batchSummary: params.batchSummary,
ownerUri: params.ownerUri
});
});
},
registerOnResultSetComplete(handler: (resultSetInfo: QueryExecuteResultSetCompleteNotificationParams) => any) {
self.onConnectionReadyNotification(QueryExecuteResultSetCompleteNotification.type, (params: QueryExecuteResultSetCompleteNotificationParams) => {
handler({
ownerUri: params.ownerUri,
resultSetSummary: params.resultSetSummary
});
});
},
registerOnMessage(handler: (message: QueryExecuteMessageParams) => any) {
self.onConnectionReadyNotification(QueryExecuteMessageNotification.type, (params: QueryExecuteMessageParams) => {
handler({
message: params.message,
ownerUri: params.ownerUri
});
});
},
saveResults(requestParams: VSaveResultsRequestParams): Thenable {
switch (requestParams.resultFormat) {
case 'csv':
return self.sendRequest(SaveResultsAsCsvRequest.type, requestParams, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(EditCommitRequest.type, error);
return Promise.reject(error);
}
);
case 'json':
return self.sendRequest(SaveResultsAsJsonRequest.type, requestParams, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(EditCommitRequest.type, error);
return Promise.reject(error);
}
);
case 'excel':
return self.sendRequest(SaveResultsAsExcelRequest.type, requestParams, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(EditCommitRequest.type, error);
return Promise.reject(error);
}
);
default:
return Promise.reject('unsupported format');
}
},
// Edit Data Requests
commitEdit(ownerUri: string): Thenable {
let params: EditCommitParams = { ownerUri: ownerUri };
return self.sendRequest(EditCommitRequest.type, params, undefined).then(
(result) => {
return undefined;
},
(error) => {
self.logFailedRequest(EditCommitRequest.type, error);
return Promise.reject(error);
}
);
},
createRow(ownerUri: string): Thenable {
let params: EditCreateRowParams = { ownerUri: ownerUri };
return self.sendRequest(EditCreateRowRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(EditCreateRowRequest.type, error);
return Promise.reject(error);
}
);
},
deleteRow(ownerUri: string, rowId: number): Thenable {
let params: EditDeleteRowParams = { ownerUri: ownerUri, rowId: rowId };
return self.sendRequest(EditDeleteRowRequest.type, params, undefined).then(
(result) => {
return undefined;
},
(error) => {
self.logFailedRequest(EditDeleteRowRequest.type, error);
return Promise.reject(error);
}
);
},
disposeEdit(ownerUri: string): Thenable {
let params: EditDisposeParams = { ownerUri: ownerUri };
return self.sendRequest(EditDisposeRequest.type, params, undefined).then(
(result) => {
return undefined;
},
(error) => {
self.logFailedRequest(EditDisposeRequest.type, error);
return Promise.reject(error);
}
);
},
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable {
let filters: EditInitializeFiltering = { LimitResults: rowLimit };
let params: EditInitializeParams = { ownerUri: ownerUri, schemaName: schemaName, objectName: objectName, objectType: objectType, filters: filters };
return self.sendRequest(EditInitializeRequest.type, params, undefined).then(
(result) => {
return undefined;
},
(error) => {
self.logFailedRequest(EditInitializeRequest.type, error);
return Promise.reject(error);
}
);
},
revertCell(ownerUri: string, rowId: number, columnId: number): Thenable {
let params: EditRevertCellParams = { ownerUri: ownerUri, rowId: rowId, columnId: columnId };
return self.sendRequest(EditRevertCellRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(EditRevertCellRequest.type, error);
return Promise.reject(error);
}
);
},
revertRow(ownerUri: string, rowId: number): Thenable {
let params: EditRevertRowParams = { ownerUri: ownerUri, rowId: rowId };
return self.sendRequest(EditRevertRowRequest.type, params, undefined).then(
(result) => {
return undefined;
},
(error) => {
self.logFailedRequest(EditRevertRowRequest.type, error);
return Promise.reject(error);
}
);
},
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable {
let params: EditUpdateCellParams = { ownerUri: ownerUri, rowId: rowId, columnId: columnId, newValue: newValue };
return self.sendRequest(EditUpdateCellRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(EditUpdateCellRequest.type, error);
return Promise.reject(error);
}
);
},
getEditRows(rowData: EditSubsetParams): Thenable {
return self.sendRequest(EditSubsetRequest.type, rowData, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(EditSubsetRequest.type, error);
return Promise.reject(error);
}
);
},
// Edit Data Event Handlers
registerOnEditSessionReady(handler: (ownerUri: string, success: boolean, message: string) => any): void {
self.onConnectionReadyNotification(EditSessionReadyNotification.type, (params: EditSessionReadyParams) => {
handler(params.ownerUri, params.success, params.message);
});
},
};
let metadataProvider: MetadataProvider = {
getMetadata(connectionUri: string): Thenable {
return self.sendRequest(MetadataQueryRequest.type,
self._c2p.asMetadataQueryParams(connectionUri), undefined).then(
self._p2c.asProviderMetadata,
(error) => {
self.logFailedRequest(MetadataQueryRequest.type, error);
return Promise.resolve(undefined);
}
);
},
getDatabases(connectionUri: string): Thenable {
return self.sendRequest(ListDatabasesRequest.type,
self._c2p.asListDatabasesParams(connectionUri), undefined).then(
(result) => {
return result.databaseNames;
},
(error) => {
self.logFailedRequest(ListDatabasesRequest.type, error);
return Promise.resolve(undefined);
}
);
},
getTableInfo(connectionUri: string, metadata: ObjectMetadata) {
return self.sendRequest(TableMetadataRequest.type,
self._c2p.asTableMetadataParams(connectionUri, metadata), undefined).then(
(result) => {
return result.columns;
},
(error) => {
self.logFailedRequest(TableMetadataRequest.type, error);
return Promise.resolve(undefined);
}
);
},
getViewInfo(connectionUri: string, metadata: ObjectMetadata) {
return self.sendRequest(ViewMetadataRequest.type,
self._c2p.asTableMetadataParams(connectionUri, metadata), undefined).then(
(result) => {
return result.columns;
},
(error) => {
self.logFailedRequest(ViewMetadataRequest.type, error);
return Promise.resolve(undefined);
}
);
}
};
let adminServicesProvider: AdminServicesProvider = {
createDatabase(connectionUri: string, database: DatabaseInfo): Thenable {
let params: CreateDatabaseParams = { ownerUri: connectionUri, databaseInfo: database };
return self.sendRequest(CreateDatabaseRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(CreateDatabaseRequest.type, error);
return Promise.resolve(undefined);
}
);
},
getDefaultDatabaseInfo(connectionUri: string): Thenable {
let params: DefaultDatabaseInfoParams = { ownerUri: connectionUri };
return self.sendRequest(DefaultDatabaseInfoRequest.type, params, undefined).then(
(result) => {
return result.defaultDatabaseInfo;
},
(error) => {
self.logFailedRequest(DefaultDatabaseInfoRequest.type, error);
return Promise.resolve(undefined);
}
);
},
getDatabaseInfo(connectionUri: string): Thenable {
let params: GetDatabaseInfoParams = { ownerUri: connectionUri };
return self.sendRequest(GetDatabaseInfoRequest.type, params, undefined).then(
(result) => {
return result.databaseInfo;
},
(error) => {
self.logFailedRequest(GetDatabaseInfoRequest.type, error);
return Promise.reject(error);
}
);
},
createLogin(connectionUri: string, login: LoginInfo): Thenable {
let params: CreateLoginParams = { ownerUri: connectionUri, loginInfo: login };
return self.sendRequest(CreateLoginRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(CreateLoginRequest.type, error);
return Promise.resolve(undefined);
}
);
}
};
let disasterRecoveryProvider: DisasterRecoveryProvider = {
backup(connectionUri: string, backupInfo: BackupInfo, taskExecutionMode: TaskExecutionMode): Thenable {
let params: BackupParams = { ownerUri: connectionUri, backupInfo: backupInfo, taskExecutionMode: taskExecutionMode };
return self.sendRequest(BackupRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(BackupRequest.type, error);
return Promise.resolve([]);
}
);
},
getBackupConfigInfo(connectionUri: string): Thenable {
let params: DefaultDatabaseInfoParams = { ownerUri: connectionUri };
return self.sendRequest(BackupConfigInfoRequest.type, params, undefined).then(
(result) => {
return result.backupConfigInfo;
},
(error) => {
self.logFailedRequest(BackupConfigInfoRequest.type, error);
return Promise.resolve(undefined);
}
);
},
getRestorePlan(ownerUri: string, restoreInfo: RestoreInfo): Thenable {
return self.sendRequest(RestorePlanRequest.type, self._c2p.asRestoreParams(ownerUri, restoreInfo), undefined).then(
self._p2c.asRestorePlanResponse,
error => {
self.logFailedRequest(RestorePlanRequest.type, error);
return Promise.resolve(undefined);
}
);
},
restore(ownerUri: string, restoreInfo: RestoreInfo): Thenable {
return self.sendRequest(RestoreRequest.type, self._c2p.asRestoreParams(ownerUri, restoreInfo), undefined).then(
self._p2c.asRestoreResponse,
error => {
self.logFailedRequest(RestoreRequest.type, error);
return Promise.resolve(undefined);
}
);
},
getRestoreConfigInfo(ownerUri: string): Thenable {
return self.sendRequest(RestoreConfigInfoRequest.type, self._c2p.asRestoreConfigInfoParams(ownerUri), undefined).then(
self._p2c.asRestoreConfigInfo,
error => {
self.logFailedRequest(RestorePlanRequest.type, error);
return Promise.resolve(undefined);
}
);
},
cancelRestorePlan(ownerUri: string, restoreInfo: RestoreInfo): Thenable {
return self.sendRequest(CancelRestorePlanRequest.type, self._c2p.asRestoreParams(ownerUri, restoreInfo), undefined).then(
(result) => {
return result;
},
error => {
self.logFailedRequest(CancelRestorePlanRequest.type, error);
return Promise.resolve(undefined);
}
);
},
};
let objectExplorer: ObjectExplorerProvider = {
createNewSession(connInfo: ConnectionInfo) {
return self.sendRequest(ObjectExplorerCreateSessionRequest.type,
self._c2p.asConnectionDetail(connInfo), undefined).then(
self._p2c.asObjectExplorerCreateSessionResponse,
(error) => {
self.logFailedRequest(ObjectExplorerCreateSessionRequest.type, error);
return Promise.resolve(undefined);
}
);
},
expandNode(nodeInfo: ExpandNodeInfo) {
return self.sendRequest(ObjectExplorerExpandRequest.type,
self._c2p.asExpandInfo(nodeInfo), undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(ObjectExplorerExpandRequest.type, error);
return Promise.resolve(undefined);
}
);
},
refreshNode(nodeInfo: ExpandNodeInfo) {
return self.sendRequest(ObjectExplorerRefreshRequest.type,
self._c2p.asExpandInfo(nodeInfo), undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(ObjectExplorerRefreshRequest.type, error);
return Promise.resolve(undefined);
}
);
},
closeSession(closeSessionInfo: ObjectExplorerCloseSessionInfo) {
return self.sendRequest(ObjectExplorerCloseSessionRequest.type,
self._c2p.asCloseSessionInfo(closeSessionInfo), undefined).then(
self._p2c.asObjectExplorerCloseSessionResponse,
(error) => {
self.logFailedRequest(ObjectExplorerCloseSessionRequest.type, error);
return Promise.resolve(undefined);
}
);
},
registerOnSessionCreated(handler: (response: ObjectExplorerSession) => any) {
self.onConnectionReadyNotification(ObjectExplorerCreateSessionCompleteNotification.type, (params: ObjectExplorerSession) => {
handler({
sessionId: params.sessionId,
success: params.success,
rootNode: params.rootNode,
errorMessage: params.errorMessage
});
});
},
registerOnExpandCompleted(handler: (response: ObjectExplorerExpandInfo) => any) {
self.onConnectionReadyNotification(ObjectExplorerExpandCompleteNotification.type, (params: ObjectExplorerExpandInfo) => {
handler({
sessionId: params.sessionId,
nodes: params.nodes,
errorMessage: params.errorMessage,
nodePath: params.nodePath
});
});
},
};
let scriptingProvider: ScriptingProvider = {
scriptAsOperation(connectionUri: string, operation: ScriptOperation, metadata: ObjectMetadata, paramDetails: ScriptingParamDetails): Thenable {
return self.sendRequest(ScriptingRequest.type,
self._c2p.asScriptingParams(connectionUri, operation, metadata, paramDetails), undefined).then(
self._p2c.asScriptingResult,
(error) => {
self.logFailedRequest(ScriptingRequest.type, error);
return Promise.resolve(undefined);
}
);
},
registerOnScriptingComplete(handler: (scriptingCompleteResult: ScriptingCompleteResult) => any) {
self.onConnectionReadyNotification(ScriptingCompleteNotification.type, (params: ScriptingCompleteResult) => {
handler({
canceled: params.canceled,
errorDetails: params.errorDetails,
errorMessage: params.errorMessage,
hasError: params.hasError,
success: params.success,
operationId: params.operationId
});
});
},
};
let taskServicesProvider: TaskServicesProvider = {
getAllTasks(listTasksParams: ListTasksParams): Thenable {
return self.sendRequest(ListTasksRequest.type,
self._c2p.asListTasksParams(listTasksParams), undefined).then(
self._p2c.asListTasksResponse,
(error) => {
self.logFailedRequest(ListTasksRequest.type, error);
return Promise.resolve(undefined);
}
);
},
cancelTask(cancelTaskParams: CancelTaskParams): Thenable {
return self.sendRequest(CancelTaskRequest.type,
self._c2p.asCancelTaskParams(cancelTaskParams), undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(CancelTaskRequest.type, error);
return Promise.resolve(undefined);
}
);
},
registerOnTaskCreated(handler: (response: TaskInfo) => any) {
self.onConnectionReadyNotification(TaskCreatedNotification.type, (params: TaskInfo) => {
handler(self._p2c.asTaskInfo(params));
});
},
registerOnTaskStatusChanged(handler: (response: TaskProgressInfo) => any) {
self.onConnectionReadyNotification(TaskStatusChangedNotification.type, (params: TaskProgressInfo) => {
handler({
taskId: params.taskId,
status: params.status,
message: params.message,
script: params.script,
duration: params.duration
});
});
}
};
let fileBrowserProvider: FileBrowserProvider = {
openFileBrowser(ownerUri: string, expandPath: string, fileFilters: string[], changeFilter: boolean): Thenable {
let params: FileBrowserOpenParams = { ownerUri: ownerUri, expandPath: expandPath, fileFilters: fileFilters, changeFilter: changeFilter };
return self.sendRequest(FileBrowserOpenRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(FileBrowserOpenRequest.type, error);
return Promise.resolve(undefined);
}
);
},
registerOnFileBrowserOpened(handler: (response: FileBrowserOpenedParams) => any) {
self.onConnectionReadyNotification(FileBrowserOpenedNotification.type, (params: FileBrowserOpenedParams) => {
handler(params);
});
},
expandFolderNode(ownerUri: string, expandPath: string): Thenable {
let params: FileBrowserExpandParams = { ownerUri: ownerUri, expandPath: expandPath };
return self.sendRequest(FileBrowserExpandRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(FileBrowserExpandRequest.type, error);
return Promise.resolve(undefined);
}
);
},
registerOnFolderNodeExpanded(handler: (response: FileBrowserExpandedParams) => any) {
self.onConnectionReadyNotification(FileBrowserExpandedNotification.type, (params: FileBrowserExpandedParams) => {
handler(params);
});
},
validateFilePaths(ownerUri: string, serviceType: string, selectedFiles: string[]): Thenable {
let params: FileBrowserValidateParams = { ownerUri: ownerUri, serviceType: serviceType, selectedFiles: selectedFiles };
return self.sendRequest(FileBrowserValidateRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(FileBrowserValidateRequest.type, error);
return Promise.resolve(undefined);
}
);
},
registerOnFilePathsValidated(handler: (response: FileBrowserValidatedParams) => any) {
self.onConnectionReadyNotification(FileBrowserValidatedNotification.type, (params: FileBrowserValidatedParams) => {
handler(params);
});
},
closeFileBrowser(ownerUri: string): Thenable {
let params: FileBrowserCloseParams = { ownerUri: ownerUri };
return self.sendRequest(FileBrowserCloseRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(FileBrowserCloseRequest.type, error);
return Promise.resolve(undefined);
}
);
}
};
let profilerProvider: ProfilerProvider = {
startSession(sessionId: string): Thenable {
let params: StartProfilingParams = {
ownerUri: sessionId,
options: { }
};
return self.sendRequest(StartProfilingRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(StartProfilingRequest.type, error);
return Promise.reject(error);
}
);
},
stopSession(sessionId: string): Thenable {
let params: StopProfilingParams = {
ownerUri: sessionId
};
return self.sendRequest(StopProfilingRequest.type, params, undefined).then(
(result) => {
return result;
},
(error) => {
self.logFailedRequest(StopProfilingRequest.type, error);
return Promise.reject(error);
}
);
},
pauseSession(sessionId: string): Thenable {
return undefined;
},
connectSession(sessionId: string): Thenable {
return undefined;
},
disconnectSession(sessionId: string): Thenable {
return undefined;
},
registerOnSessionEventsAvailable(handler: (response: ProfilerSessionEvents) => any) {
self.onNotification(ProfilerEventsAvailableNotification.type, (params: ProfilerEventsAvailableParams) => {
handler({
sessionId: params.ownerUri,
events: params.events
});
});
}
};
this._providers.push(dataprotocol.registerProvider({
handle: -1,
providerId: providerId,
capabilitiesProvider: capabilitiesProvider,
connectionProvider: connectionProvider,
queryProvider: queryProvider,
metadataProvider: metadataProvider,
scriptingProvider: scriptingProvider,
objectExplorerProvider: objectExplorer,
adminServicesProvider: adminServicesProvider,
disasterRecoveryProvider: disasterRecoveryProvider,
taskServicesProvider: taskServicesProvider,
fileBrowserProvider: fileBrowserProvider,
profilerProvider: profilerProvider
}));
// Hook to the workspace-wide notifications that aren't routed to a specific provider
dataprotocol.onDidChangeLanguageFlavor(e => {
self.sendNotification(LanguageFlavorChangedNotification.type, e);
}, this, this._listeners);
}
private hookCompletionProvider(documentSelector: DocumentSelector, connection: IConnection): void {
if (!this._capabilites.completionProvider) {
return;
}
this._providers.push(Languages.registerCompletionItemProvider(documentSelector, {
provideCompletionItems: (document: TextDocument, position: VPosition, token: CancellationToken): Thenable => {
return this.doSendRequest(connection, CompletionRequest.type, this._c2p.asTextDocumentPositionParams(document, position), token).then(
this._p2c.asCompletionResult,
(error) => {
this.logFailedRequest(CompletionRequest.type, error);
return Promise.resolve([]);
}
);
},
resolveCompletionItem: this._capabilites.completionProvider.resolveProvider
? (item: VCompletionItem, token: CancellationToken): Thenable => {
return this.doSendRequest(connection, CompletionResolveRequest.type, this._c2p.asCompletionItem(item), token).then(
this._p2c.asCompletionItem,
(error) => {
this.logFailedRequest(CompletionResolveRequest.type, error);
return Promise.resolve(item);
}
);
}
: undefined
}, ...this._capabilites.completionProvider.triggerCharacters));
}
private hookHoverProvider(documentSelector: DocumentSelector, connection: IConnection): void {
if (!this._capabilites.hoverProvider) {
return;
}
this._providers.push(Languages.registerHoverProvider(documentSelector, {
provideHover: (document: TextDocument, position: VPosition, token: CancellationToken): Thenable => {
return this.doSendRequest(connection, HoverRequest.type, this._c2p.asTextDocumentPositionParams(document, position), token).then(
this._p2c.asHover,
(error) => {
this.logFailedRequest(HoverRequest.type, error);
return Promise.resolve(null);
}
);
}
}));
}
private hookSignatureHelpProvider(documentSelector: DocumentSelector, connection: IConnection): void {
if (!this._capabilites.signatureHelpProvider) {
return;
}
this._providers.push(Languages.registerSignatureHelpProvider(documentSelector, {
provideSignatureHelp: (document: TextDocument, position: VPosition, token: CancellationToken): Thenable => {
return this.doSendRequest(connection, SignatureHelpRequest.type, this._c2p.asTextDocumentPositionParams(document, position), token).then(
this._p2c.asSignatureHelp,
(error) => {
this.logFailedRequest(SignatureHelpRequest.type, error);
return Promise.resolve(null);
}
);
}
}, ...this._capabilites.signatureHelpProvider.triggerCharacters));
}
private hookDefinitionProvider(documentSelector: DocumentSelector, connection: IConnection): void {
if (!this._capabilites.definitionProvider) {
return;
}
this._providers.push(Languages.registerDefinitionProvider(documentSelector, {
provideDefinition: (document: TextDocument, position: VPosition, token: CancellationToken): Thenable => {
return this.doSendRequest(connection, DefinitionRequest.type, this._c2p.asTextDocumentPositionParams(document, position), token).then(
this._p2c.asDefinitionResult,
(error) => {
this.logFailedRequest(DefinitionRequest.type, error);
return Promise.resolve(null);
}
);
}
}));
}
private hookReferencesProvider(documentSelector: DocumentSelector, connection: IConnection): void {
if (!this._capabilites.referencesProvider) {
return;
}
this._providers.push(Languages.registerReferenceProvider(documentSelector, {
provideReferences: (document: TextDocument, position: VPosition, options: { includeDeclaration: boolean; }, token: CancellationToken): Thenable => {
return this.doSendRequest(connection, ReferencesRequest.type, this._c2p.asReferenceParams(document, position, options), token).then(
this._p2c.asReferences,
(error) => {
this.logFailedRequest(ReferencesRequest.type, error);
return Promise.resolve([]);
}
);
}
}));
}
private hookDocumentHighlightProvider(documentSelector: DocumentSelector, connection: IConnection): void {
if (!this._capabilites.documentHighlightProvider) {
return;
}
this._providers.push(Languages.registerDocumentHighlightProvider(documentSelector, {
provideDocumentHighlights: (document: TextDocument, position: VPosition, token: CancellationToken): Thenable => {
return this.doSendRequest(connection, DocumentHighlightRequest.type, this._c2p.asTextDocumentPositionParams(document, position), token).then(
this._p2c.asDocumentHighlights,
(error) => {
this.logFailedRequest(DocumentHighlightRequest.type, error);
return Promise.resolve([]);
}
);
}
}));
}
private hookDocumentSymbolProvider(documentSelector: DocumentSelector, connection: IConnection): void {
if (!this._capabilites.documentSymbolProvider) {
return;
}
this._providers.push(Languages.registerDocumentSymbolProvider(documentSelector, {
provideDocumentSymbols: (document: TextDocument, token: CancellationToken): Thenable => {
return this.doSendRequest(connection, DocumentSymbolRequest.type, this._c2p.asDocumentSymbolParams(document), token).then(
this._p2c.asSymbolInformations,
(error) => {
this.logFailedRequest(DocumentSymbolRequest.type, error);
return Promise.resolve([]);
}
);
}
}));
}
private hookWorkspaceSymbolProvider(connection: IConnection): void {
if (!this._capabilites.workspaceSymbolProvider) {
return;
}
this._providers.push(Languages.registerWorkspaceSymbolProvider({
provideWorkspaceSymbols: (query: string, token: CancellationToken): Thenable => {
return this.doSendRequest(connection, WorkspaceSymbolRequest.type, { query }, token).then(
this._p2c.asSymbolInformations,
(error) => {
this.logFailedRequest(WorkspaceSymbolRequest.type, error);
return Promise.resolve([]);
}
);
}
}));
}
private hookCodeActionsProvider(documentSelector: DocumentSelector, connection: IConnection): void {
if (!this._capabilites.codeActionProvider) {
return;
}
this._providers.push(Languages.registerCodeActionsProvider(documentSelector, {
provideCodeActions: (document: TextDocument, range: VRange, context: VCodeActionContext, token: CancellationToken): Thenable => {
let params: CodeActionParams = {
textDocument: this._c2p.asTextDocumentIdentifier(document),
range: this._c2p.asRange(range),
context: this._c2p.asCodeActionContext(context)
};
return this.doSendRequest(connection, CodeActionRequest.type, params, token).then(
this._p2c.asCommands,
(error) => {
this.logFailedRequest(CodeActionRequest.type, error);
return Promise.resolve([]);
}
);
}
}));
}
private hookCodeLensProvider(documentSelector: DocumentSelector, connection: IConnection): void {
if (!this._capabilites.codeLensProvider) {
return;
}
this._providers.push(Languages.registerCodeLensProvider(documentSelector, {
provideCodeLenses: (document: TextDocument, token: CancellationToken): Thenable => {
return this.doSendRequest(connection, CodeLensRequest.type, this._c2p.asCodeLensParams(document), token).then(
this._p2c.asCodeLenses,
(error) => {
this.logFailedRequest(CodeLensRequest.type, error);
return Promise.resolve([]);
}
);
},
resolveCodeLens: (this._capabilites.codeLensProvider.resolveProvider)
? (codeLens: VCodeLens, token: CancellationToken): Thenable => {
return this.doSendRequest(connection, CodeLensResolveRequest.type, this._c2p.asCodeLens(codeLens), token).then(
this._p2c.asCodeLens,
(error) => {
this.logFailedRequest(CodeLensResolveRequest.type, error);
return codeLens;
}
);
}
: undefined
}));
}
private hookDocumentFormattingProvider(documentSelector: DocumentSelector, connection: IConnection): void {
if (!this._capabilites.documentFormattingProvider) {
return;
}
this._providers.push(Languages.registerDocumentFormattingEditProvider(documentSelector, {
provideDocumentFormattingEdits: (document: TextDocument, options: VFormattingOptions, token: CancellationToken): Thenable => {
let params: DocumentFormattingParams = {
textDocument: this._c2p.asTextDocumentIdentifier(document),
options: this._c2p.asFormattingOptions(options)
};
return this.doSendRequest(connection, DocumentFormattingRequest.type, params, token).then(
this._p2c.asTextEdits,
(error) => {
this.logFailedRequest(DocumentFormattingRequest.type, error);
return Promise.resolve([]);
}
);
}
}));
}
private hookDocumentRangeFormattingProvider(documentSelector: DocumentSelector, connection: IConnection): void {
if (!this._capabilites.documentRangeFormattingProvider) {
return;
}
this._providers.push(Languages.registerDocumentRangeFormattingEditProvider(documentSelector, {
provideDocumentRangeFormattingEdits: (document: TextDocument, range: VRange, options: VFormattingOptions, token: CancellationToken): Thenable => {
let params: DocumentRangeFormattingParams = {
textDocument: this._c2p.asTextDocumentIdentifier(document),
range: this._c2p.asRange(range),
options: this._c2p.asFormattingOptions(options)
};
return this.doSendRequest(connection, DocumentRangeFormattingRequest.type, params, token).then(
this._p2c.asTextEdits,
(error) => {
this.logFailedRequest(DocumentRangeFormattingRequest.type, error);
return Promise.resolve([]);
}
);
}
}));
}
private hookDocumentOnTypeFormattingProvider(documentSelector: DocumentSelector, connection: IConnection): void {
if (!this._capabilites.documentOnTypeFormattingProvider) {
return;
}
let formatCapabilities = this._capabilites.documentOnTypeFormattingProvider;
this._providers.push(Languages.registerOnTypeFormattingEditProvider(documentSelector, {
provideOnTypeFormattingEdits: (document: TextDocument, position: VPosition, ch: string, options: VFormattingOptions, token: CancellationToken): Thenable => {
let params: DocumentOnTypeFormattingParams = {
textDocument: this._c2p.asTextDocumentIdentifier(document),
position: this._c2p.asPosition(position),
ch: ch,
options: this._c2p.asFormattingOptions(options)
};
return this.doSendRequest(connection, DocumentOnTypeFormattingRequest.type, params, token).then(
this._p2c.asTextEdits,
(error) => {
this.logFailedRequest(DocumentOnTypeFormattingRequest.type, error);
return Promise.resolve([]);
}
);
}
}, formatCapabilities.firstTriggerCharacter, ...formatCapabilities.moreTriggerCharacter));
}
private hookRenameProvider(documentSelector: DocumentSelector, connection: IConnection): void {
if (!this._capabilites.renameProvider) {
return;
}
this._providers.push(Languages.registerRenameProvider(documentSelector, {
provideRenameEdits: (document: TextDocument, position: VPosition, newName: string, token: CancellationToken): Thenable => {
let params: RenameParams = {
textDocument: this._c2p.asTextDocumentIdentifier(document),
position: this._c2p.asPosition(position),
newName: newName
};
return this.doSendRequest(connection, RenameRequest.type, params, token).then(
this._p2c.asWorkspaceEdit,
(error: ResponseError) => {
this.logFailedRequest(RenameRequest.type, error);
Promise.resolve(new Error(error.message));
}
);
}
}));
}
private hookDocumentLinkProvider(documentSelector: DocumentSelector, connection: IConnection): void {
if (!this._capabilites.documentLinkProvider) {
return;
}
this._providers.push(Languages.registerDocumentLinkProvider(documentSelector, {
provideDocumentLinks: (document: TextDocument, token: CancellationToken): Thenable => {
return this.doSendRequest(connection, DocumentLinkRequest.type, this._c2p.asDocumentLinkParams(document), token).then(
this._p2c.asDocumentLinks,
(error: ResponseError) => {
this.logFailedRequest(DocumentLinkRequest.type, error);
Promise.resolve(new Error(error.message));
}
);
},
resolveDocumentLink: this._capabilites.documentLinkProvider.resolveProvider
? (link: VDocumentLink, token: CancellationToken): Thenable => {
return this.doSendRequest(connection, DocumentLinkResolveRequest.type, this._c2p.asDocumentLink(link), token).then(
this._p2c.asDocumentLink,
(error: ResponseError) => {
this.logFailedRequest(DocumentLinkResolveRequest.type, error);
Promise.resolve(new Error(error.message));
}
);
}
: undefined
}));
}
}
export class SettingMonitor {
private _listeners: Disposable[];
constructor(private _client: LanguageClient, private _setting: string) {
this._listeners = [];
}
public start(): Disposable {
Workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this._listeners);
this.onDidChangeConfiguration();
return new Disposable(() => {
if (this._client.needsStop()) {
this._client.stop();
}
});
}
private onDidChangeConfiguration(): void {
let index = this._setting.indexOf('.');
let primary = index >= 0 ? this._setting.substr(0, index) : this._setting;
let rest = index >= 0 ? this._setting.substr(index + 1) : undefined;
let enabled = rest ? Workspace.getConfiguration(primary).get(rest, false) : Workspace.getConfiguration(primary);
if (enabled && this._client.needsStart()) {
this._client.start();
} else if (!enabled && this._client.needsStop()) {
this._client.stop();
}
}
}