diff --git a/dataprotocol-node/.gitignore b/dataprotocol-node/.gitignore new file mode 100644 index 0000000000..829e00dc84 --- /dev/null +++ b/dataprotocol-node/.gitignore @@ -0,0 +1,29 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release +lib/ +out/ + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- +node_modules + +# Debug log from npm +npm-debug.log \ No newline at end of file diff --git a/dataprotocol-node/License.txt b/dataprotocol-node/License.txt new file mode 100644 index 0000000000..5ff4a3f7df --- /dev/null +++ b/dataprotocol-node/License.txt @@ -0,0 +1,18 @@ + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Source EULA + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT +OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/dataprotocol-node/README.md b/dataprotocol-node/README.md new file mode 100644 index 0000000000..e432a91724 --- /dev/null +++ b/dataprotocol-node/README.md @@ -0,0 +1,4 @@ +# Microsoft Data Management Protocol - Node + +## License +[MIT](https://github.com/Microsoft/carbon/blob/dev/license.txt) diff --git a/dataprotocol-node/client/.eslintrc b/dataprotocol-node/client/.eslintrc new file mode 100644 index 0000000000..306df0be07 --- /dev/null +++ b/dataprotocol-node/client/.eslintrc @@ -0,0 +1,24 @@ +{ + "rules": { + "indent": [ + 2, + "tab" + ], + "quotes": [ + 2, + "single" + ], + "linebreak-style": [ + 2, + "windows" + ], + "semi": [ + 2, + "always" + ] + }, + "env": { + "node": true + }, + "extends": "eslint:recommended" +} \ No newline at end of file diff --git a/dataprotocol-node/client/.npmignore b/dataprotocol-node/client/.npmignore new file mode 100644 index 0000000000..2b1e5b19e1 --- /dev/null +++ b/dataprotocol-node/client/.npmignore @@ -0,0 +1,9 @@ +.vscode/ +lib/test/ +lib/*.map +src/ +test/ +.eslintrc +.gitignore +gulpfile.js +tsd.json \ No newline at end of file diff --git a/dataprotocol-node/client/.vscode/launch.json b/dataprotocol-node/client/.vscode/launch.json new file mode 100644 index 0000000000..19f1d6dac6 --- /dev/null +++ b/dataprotocol-node/client/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + "version": "0.1.0", + // List of configurations. Add new configurations or edit existing ones. + // ONLY "node" and "mono" are supported, change "type" to switch. + "configurations": [ + { + "request": "launch", + // Name of configuration; appears in the launch configuration drop down menu. + "name": "Mocha", + // Type of configuration. Possible values: "node", "mono". + "type": "node", + // Workspace relative or absolute path to the program. + "program": "node_modules/mocha/bin/_mocha", + // Automatically stop program after launch. + "stopOnEntry": false, + // Command line arguments passed to the program. + "args": ["--timeout", "999999"], + // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. + "cwd": ".", + // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. + "runtimeExecutable": null, + // Optional arguments passed to the runtime executable. + "runtimeArgs": [], + // Environment variables passed to the program. + "env": { }, + // Use JavaScript source maps (if they exist). + "sourceMaps": true, + // If JavaScript source maps are enabled, the generated code is expected in this directory. + "outDir": "lib" + } + ] +} diff --git a/dataprotocol-node/client/.vscode/settings.json b/dataprotocol-node/client/.vscode/settings.json new file mode 100644 index 0000000000..de8ee93ce0 --- /dev/null +++ b/dataprotocol-node/client/.vscode/settings.json @@ -0,0 +1,11 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "javascript.validate.enable": false, + "files.trimTrailingWhitespace": true, + "eslint.enable": false, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "tslint.enable": false, + "typescript.tsdk": "./node_modules/typescript/lib", + "typescript.tsserver.trace": "off" +} \ No newline at end of file diff --git a/dataprotocol-node/client/.vscode/tasks.json b/dataprotocol-node/client/.vscode/tasks.json new file mode 100644 index 0000000000..0ed84877c1 --- /dev/null +++ b/dataprotocol-node/client/.vscode/tasks.json @@ -0,0 +1,9 @@ +{ + "version": "0.1.0", + "command": "npm", + "isShellCommand": true, + "args": ["run", "watch"], + "showOutput": "silent", + "isWatching": true, + "problemMatcher": "$tsc-watch" +} \ No newline at end of file diff --git a/dataprotocol-node/client/README.md b/dataprotocol-node/client/README.md new file mode 100644 index 0000000000..e432a91724 --- /dev/null +++ b/dataprotocol-node/client/README.md @@ -0,0 +1,4 @@ +# Microsoft Data Management Protocol - Node + +## License +[MIT](https://github.com/Microsoft/carbon/blob/dev/license.txt) diff --git a/dataprotocol-node/client/package.json b/dataprotocol-node/client/package.json new file mode 100644 index 0000000000..2d8865a3c4 --- /dev/null +++ b/dataprotocol-node/client/package.json @@ -0,0 +1,32 @@ +{ + "name": "dataprotocol-client", + "description": "VSCode Language client implementation", + "version": "2.6.3", + "author": "Microsoft Corporation", + "license": "MIT", + "engines": { + "vscode": "^1.5.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-languageserver-node.git" + }, + "bugs": { + "url": "https://github.com/Microsoft/vscode-languageserver-node/issues" + }, + "main": "./lib/main.js", + "typings": "./lib/main", + "devDependencies": { + "typescript": "2.0.3" + }, + "dependencies": { + "dataprotocol-jsonrpc": "file:../jsonrpc", + "dataprotocol-languageserver-types": "file:../types" + }, + "scripts": { + "prepublish": "tsc -p ./src", + "compile": "tsc -p ./src", + "watch": "tsc -w -p ./src", + "update-vscode": "node ./node_modules/vscode/bin/install" + } +} diff --git a/dataprotocol-node/client/src/codeConverter.ts b/dataprotocol-node/client/src/codeConverter.ts new file mode 100644 index 0000000000..1a641104c7 --- /dev/null +++ b/dataprotocol-node/client/src/codeConverter.ts @@ -0,0 +1,526 @@ +/* -------------------------------------------------------------------------------------------- + * 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 code from 'vscode'; +import * as data from 'data'; +import * as ls from 'dataprotocol-languageserver-types'; +import * as proto from './protocol'; +import * as is from './utils/is'; +import ProtocolCompletionItem from './protocolCompletionItem'; +import ProtocolCodeLens from './protocolCodeLens'; +import os = require('os'); +import path = require('path'); + +export interface Converter { + + asUri(uri: code.Uri): string; + + asTextDocumentIdentifier(textDocument: code.TextDocument): ls.TextDocumentIdentifier; + + asOpenTextDocumentParams(textDocument: code.TextDocument): proto.DidOpenTextDocumentParams; + + asChangeTextDocumentParams(textDocument: code.TextDocument): proto.DidChangeTextDocumentParams; + asChangeTextDocumentParams(event: code.TextDocumentChangeEvent): proto.DidChangeTextDocumentParams; + + asCloseTextDocumentParams(textDocument: code.TextDocument): proto.DidCloseTextDocumentParams; + + asSaveTextDocumentParams(textDocument: code.TextDocument): proto.DidSaveTextDocumentParams; + + asTextDocumentPositionParams(textDocument: code.TextDocument, position: code.Position): proto.TextDocumentPositionParams; + + asWorkerPosition(position: code.Position): ls.Position; + + asRange(value: code.Range): ls.Range; + + asPosition(value: code.Position): ls.Position; + + asDiagnosticSeverity(value: code.DiagnosticSeverity): ls.DiagnosticSeverity; + + asDiagnostic(item: code.Diagnostic): ls.Diagnostic; + asDiagnostics(items: code.Diagnostic[]): ls.Diagnostic[]; + + asCompletionItem(item: code.CompletionItem): ls.CompletionItem; + + asTextEdit(edit: code.TextEdit): ls.TextEdit; + + asReferenceParams(textDocument: code.TextDocument, position: code.Position, options: { includeDeclaration: boolean; }): proto.ReferenceParams; + + asCodeActionContext(context: code.CodeActionContext): ls.CodeActionContext; + + asCommand(item: code.Command): ls.Command; + + asCodeLens(item: code.CodeLens): ls.CodeLens; + + asFormattingOptions(item: code.FormattingOptions): ls.FormattingOptions; + + asDocumentSymbolParams(textDocument: code.TextDocument): proto.DocumentSymbolParams; + + asCodeLensParams(textDocument: code.TextDocument): proto.CodeLensParams; + + asDocumentLink(item: code.DocumentLink): ls.DocumentLink; + + asDocumentLinkParams(textDocument: code.TextDocument): proto.DocumentLinkParams; + + asConnectionParams(connectionUri: string, connectionInfo: data.ConnectionInfo): proto.ConnectParams; + + asCapabilitiesParams(client: data.DataProtocolClientCapabilities): proto.CapabiltiesDiscoveryParams; + + asMetadataQueryParams(connectionUri: string): ls.MetadataQueryParams; + + asListDatabasesParams(connectionUri: string): proto.ListDatabasesParams; + + asTableMetadataParams(connectionUri: string, metadata: data.ObjectMetadata): proto.TableMetadataParams; + + asScriptingParams(connectionUri: string, operation: ls.ScriptOperation, metadata: data.ObjectMetadata, paramDetails: data.ScriptingParamDetails): ls.ScriptingParams; + + asConnectionDetail(connInfo: data.ConnectionInfo): ls.ConnectionDetails; + + asExpandInfo(nodeInfo: data.ExpandNodeInfo): ls.ExpandParams; + + asCloseSessionInfo(nodeInfo: data.ObjectExplorerCloseSessionInfo): ls.CloseSessionParams; + + asExecutionPlanOptions(planOptions: data.ExecutionPlanOptions): proto.ExecutionPlanOptions; + + asListTasksParams(params: data.ListTasksParams): ls.ListTasksParams; + + asCancelTaskParams(params: data.CancelTaskParams): ls.CancelTaskParams; + + asRestoreParams(ownerUri: string, params: data.RestoreInfo): ls.RestoreParams; + + asRestoreConfigInfoParams(ownerUri: string): ls.RestoreConfigInfoRequestParams; +} + +export interface URIConverter { + (value: code.Uri): string; +} + +export function createConverter(uriConverter?: URIConverter): Converter { + + const nullConverter = (value: code.Uri) => value.toString(); + + const _uriConverter: URIConverter = uriConverter || nullConverter; + + function asUri(value: code.Uri): string { + return _uriConverter(value); + } + + function asTextDocumentIdentifier(textDocument: code.TextDocument): ls.TextDocumentIdentifier { + return { + uri: _uriConverter(textDocument.uri) + }; + } + + function asOpenTextDocumentParams(textDocument: code.TextDocument): proto.DidOpenTextDocumentParams { + return { + textDocument: { + uri: _uriConverter(textDocument.uri), + languageId: textDocument.languageId, + version: textDocument.version, + text: textDocument.getText() + } + }; + } + + function isTextDocumentChangeEvent(value: any): value is code.TextDocumentChangeEvent { + let candidate = value; + return is.defined(candidate.document) && is.defined(candidate.contentChanges); + } + + function isTextDocument(value: any): value is code.TextDocument { + let candidate = value; + return is.defined(candidate.uri) && is.defined(candidate.version); + } + + function asChangeTextDocumentParams(textDocument: code.TextDocument): proto.DidChangeTextDocumentParams; + function asChangeTextDocumentParams(event: code.TextDocumentChangeEvent): proto.DidChangeTextDocumentParams; + function asChangeTextDocumentParams(arg: code.TextDocumentChangeEvent | code.TextDocument): proto.DidChangeTextDocumentParams { + if (isTextDocument(arg)) { + let result: proto.DidChangeTextDocumentParams = { + textDocument: { + uri: _uriConverter(arg.uri), + version: arg.version + }, + contentChanges: [{ text: arg.getText() }] + } + return result; + } else if (isTextDocumentChangeEvent(arg)) { + let document = arg.document; + let result: proto.DidChangeTextDocumentParams = { + textDocument: { + uri: _uriConverter(document.uri), + version: document.version + }, + contentChanges: arg.contentChanges.map((change): proto.TextDocumentContentChangeEvent => { + let range = change.range; + return { + range: { + start: { line: range.start.line, character: range.start.character }, + end: { line: range.end.line, character: range.end.character } + }, + rangeLength: change.rangeLength, + text: change.text + } + }) + } + return result; + } else { + throw Error('Unsupported text document change parameter'); + } + } + + function asCloseTextDocumentParams(textDocument: code.TextDocument): proto.DidCloseTextDocumentParams { + return { + textDocument: asTextDocumentIdentifier(textDocument) + }; + } + + function asSaveTextDocumentParams(textDocument: code.TextDocument): proto.DidSaveTextDocumentParams { + return { + textDocument: asTextDocumentIdentifier(textDocument) + } + } + + function asTextDocumentPositionParams(textDocument: code.TextDocument, position: code.Position): proto.TextDocumentPositionParams { + return { + textDocument: asTextDocumentIdentifier(textDocument), + position: asWorkerPosition(position) + }; + } + + function asWorkerPosition(position: code.Position): ls.Position { + return { line: position.line, character: position.character }; + } + + function asRange(value: code.Range): ls.Range { + if (is.undefined(value)) { + return undefined; + } else if (is.nil(value)) { + return null; + } + return { start: asPosition(value.start), end: asPosition(value.end) }; + } + + function asPosition(value: code.Position): ls.Position { + if (is.undefined(value)) { + return undefined; + } else if (is.nil(value)) { + return null; + } + return { line: value.line, character: value.character }; + } + + function set(value, func: () => void): void { + if (is.defined(value)) { + func(); + } + } + + function asDiagnosticSeverity(value: code.DiagnosticSeverity): ls.DiagnosticSeverity { + switch (value) { + case code.DiagnosticSeverity.Error: + return ls.DiagnosticSeverity.Error; + case code.DiagnosticSeverity.Warning: + return ls.DiagnosticSeverity.Warning; + case code.DiagnosticSeverity.Information: + return ls.DiagnosticSeverity.Information; + case code.DiagnosticSeverity.Hint: + return ls.DiagnosticSeverity.Hint; + } + } + + function asDiagnostic(item: code.Diagnostic): ls.Diagnostic { + let result: ls.Diagnostic = ls.Diagnostic.create(asRange(item.range), item.message); + set(item.severity, () => result.severity = asDiagnosticSeverity(item.severity)); + set(item.code, () => result.code = item.code); + set(item.source, () => result.source = item.source); + return result; + } + + function asDiagnostics(items: code.Diagnostic[]): ls.Diagnostic[] { + if (is.undefined(items) || is.nil(items)) { + return items; + } + return items.map(asDiagnostic); + } + + function asCompletionItem(item: code.CompletionItem): ls.CompletionItem { + let result: ls.CompletionItem = { label: item.label }; + set(item.detail, () => result.detail = item.detail); + set(item.documentation, () => result.documentation = item.documentation); + set(item.filterText, () => result.filterText = item.filterText); + set(item.insertText, () => result.insertText = String(item.insertText)); + // Protocol item kind is 1 based, codes item kind is zero based. + set(item.kind, () => result.kind = item.kind + 1); + set(item.sortText, () => result.sortText = item.sortText); + set(item.textEdit, () => result.textEdit = asTextEdit(item.textEdit)); + set(item.additionalTextEdits, () => result.additionalTextEdits = asTextEdits(item.additionalTextEdits)); + set(item.command, () => result.command = asCommand(item.command)); + if (item instanceof ProtocolCompletionItem) { + set(item.data, () => result.data = item.data); + } + return result; + } + + function asTextEdit(edit: code.TextEdit): ls.TextEdit { + return { range: asRange(edit.range), newText: edit.newText }; + } + + function asTextEdits(edits: code.TextEdit[]): ls.TextEdit[] { + if (is.undefined(edits) || is.nil(edits)) { + return edits; + } + return edits.map(asTextEdit); + } + + function asReferenceParams(textDocument: code.TextDocument, position: code.Position, options: { includeDeclaration: boolean; }): proto.ReferenceParams { + return { + textDocument: asTextDocumentIdentifier(textDocument), + position: asWorkerPosition(position), + context: { includeDeclaration: options.includeDeclaration } + }; + } + + function asCodeActionContext(context: code.CodeActionContext): ls.CodeActionContext { + if (is.undefined(context) || is.nil(context)) { + return context; + } + return ls.CodeActionContext.create(asDiagnostics(context.diagnostics)); + } + + function asCommand(item: code.Command): ls.Command { + let result = ls.Command.create(item.title, item.command); + if (is.defined(item.arguments)) result.arguments = item.arguments; + return result; + } + + function asCodeLens(item: code.CodeLens): ls.CodeLens { + let result = ls.CodeLens.create(asRange(item.range)); + if (is.defined(item.command)) result.command = asCommand(item.command); + if (item instanceof ProtocolCodeLens) { + if (is.defined(item.data)) result.data = item.data; + } + return result; + } + + function asFormattingOptions(item: code.FormattingOptions): ls.FormattingOptions { + return { tabSize: item.tabSize, insertSpaces: item.insertSpaces }; + } + + function asDocumentSymbolParams(textDocument: code.TextDocument): proto.DocumentSymbolParams { + return { + textDocument: asTextDocumentIdentifier(textDocument) + } + } + + function asCodeLensParams(textDocument: code.TextDocument): proto.CodeLensParams { + return { + textDocument: asTextDocumentIdentifier(textDocument) + }; + } + + function asDocumentLink(item: code.DocumentLink): ls.DocumentLink { + let result = ls.DocumentLink.create(asRange(item.range)); + if (is.defined(item.target)) result.target = asUri(item.target); + return result; + } + + function asDocumentLinkParams(textDocument: code.TextDocument): proto.DocumentLinkParams { + return { + textDocument: asTextDocumentIdentifier(textDocument) + }; + } + + function asCapabilitiesParams(client: data.DataProtocolClientCapabilities): proto.CapabiltiesDiscoveryParams { + let params: proto.CapabiltiesDiscoveryParams = { + hostName: client.hostName, + hostVersion: client.hostVersion + }; + return params; + } + + function asConnectionParams(connUri: string, connInfo: data.ConnectionInfo): proto.ConnectParams { + return { + ownerUri: connUri, + connection: { + options: connInfo.options + } + }; + } + + function asMetadataQueryParams(connectionUri: string): ls.MetadataQueryParams { + return { + ownerUri: connectionUri + }; + } + + function asListDatabasesParams(connectionUri: string): proto.ListDatabasesParams { + return { + ownerUri: connectionUri + }; + } + + function asTableMetadataParams(connectionUri: string, metadata: data.ObjectMetadata): proto.TableMetadataParams { + return { + ownerUri: connectionUri, + schema: metadata.schema, + objectName: metadata.name + }; + } + + function asScriptingParams(connectionUri: string, operation: ls.ScriptOperation, metadata: data.ObjectMetadata, paramDetails: data.ScriptingParamDetails): ls.ScriptingParams { + let scriptingObject: ls.ScriptingObject = { + type: metadata.metadataTypeName, + schema: metadata.schema, + name: metadata.name + } + let targetDatabaseEngineEdition = paramDetails.targetDatabaseEngineEdition; + let targetDatabaseEngineType = paramDetails.targetDatabaseEngineType; + let scriptCompatibilityOption = paramDetails.scriptCompatibilityOption; + let options: ls.ScriptOptions = { + scriptCreateDrop: (operation === ls.ScriptOperation.Delete) ? "ScriptDrop" : + (operation === ls.ScriptOperation.Select) ? "ScriptSelect" : "ScriptCreate", + typeOfDataToScript: "SchemaOnly", + scriptStatistics: "ScriptStatsNone", + targetDatabaseEngineEdition: targetDatabaseEngineEdition ? targetDatabaseEngineEdition : "SqlServerEnterpriseEdition", + targetDatabaseEngineType: targetDatabaseEngineType ? targetDatabaseEngineType : "SingleInstance", + scriptCompatibilityOption: scriptCompatibilityOption ? scriptCompatibilityOption : "Script140Compat" + } + return { + connectionString: null, + filePath: paramDetails.filePath, + scriptingObjects: [scriptingObject], + scriptDestination: "ToEditor", + includeObjectCriteria: null, + excludeObjectCriteria: null, + includeSchemas: null, + excludeSchemas: null, + includeTypes: null, + excludeTypes: null, + scriptOptions: options, + connectionDetails: null, + ownerURI: connectionUri, + operation: operation + }; + } + + function asConnectionDetail(connInfo: data.ConnectionInfo): ls.ConnectionDetails { + return { + options: connInfo.options + }; + } + + function asExpandInfo(nodeInfo: data.ExpandNodeInfo): ls.ExpandParams { + return { + sessionId: nodeInfo.sessionId, + nodePath: nodeInfo.nodePath + }; + } + + function asCloseSessionInfo(nodeInfo: data.ObjectExplorerCloseSessionInfo): ls.CloseSessionParams { + return { + sessionId: nodeInfo.sessionId + }; + } + + function asExecutionPlanOptions(planOptions: data.ExecutionPlanOptions): proto.ExecutionPlanOptions { + return { + includeEstimatedExecutionPlanXml: planOptions ? planOptions.displayEstimatedQueryPlan : undefined, + includeActualExecutionPlanXml: planOptions ? planOptions.displayActualQueryPlan : undefined + }; + } + + function asListTasksParams(params: data.ListTasksParams): ls.ListTasksParams { + return { + listActiveTasksOnly: params.listActiveTasksOnly + }; + } + + function asCancelTaskParams(params: data.CancelTaskParams): ls.CancelTaskParams { + return { + taskId: params.taskId + }; + } + + function asRestoreParams(ownerUri: string, params: data.RestoreInfo): ls.RestoreParams { + return { + ownerUri: ownerUri, + options: params.options, + taskExecutionMode: params.taskExecutionMode + }; + } + + function asRestoreConfigInfoParams(ownerUri: string): ls.RestoreConfigInfoRequestParams { + return { + ownerUri: ownerUri + }; + } + + return { + asUri, + asTextDocumentIdentifier, + asOpenTextDocumentParams, + asChangeTextDocumentParams, + asCloseTextDocumentParams, + asSaveTextDocumentParams, + asTextDocumentPositionParams, + asWorkerPosition, + asRange, + asPosition, + asDiagnosticSeverity, + asDiagnostic, + asDiagnostics, + asCompletionItem, + asTextEdit, + asReferenceParams, + asCodeActionContext, + asCommand, + asCodeLens, + asFormattingOptions, + asDocumentSymbolParams, + asCodeLensParams, + asDocumentLink, + asDocumentLinkParams, + asCapabilitiesParams, + asConnectionParams, + asMetadataQueryParams, + asTableMetadataParams, + asListDatabasesParams, + asScriptingParams, + asConnectionDetail, + asExpandInfo, + asCloseSessionInfo, + asExecutionPlanOptions, + asListTasksParams, + asCancelTaskParams, + asRestoreParams, + asRestoreConfigInfoParams + }; +} + +// This for backward compatibility since we exported the converter functions as API. +let defaultConverter = createConverter(); + +export const asTextDocumentIdentifier: (textDocument: code.TextDocument) => ls.TextDocumentIdentifier = defaultConverter.asTextDocumentIdentifier; +export const asOpenTextDocumentParams: (textDocument: code.TextDocument) => proto.DidOpenTextDocumentParams = defaultConverter.asOpenTextDocumentParams; +export const asChangeTextDocumentParams: (arg: code.TextDocumentChangeEvent | code.TextDocument) => proto.DidChangeTextDocumentParams = defaultConverter.asChangeTextDocumentParams; +export const asCloseTextDocumentParams: (textDocument: code.TextDocument) => proto.DidCloseTextDocumentParams = defaultConverter.asCloseTextDocumentParams; +export const asSaveTextDocumentParams: (textDocument: code.TextDocument) => proto.DidSaveTextDocumentParams = defaultConverter.asSaveTextDocumentParams; +export const asTextDocumentPositionParams: (textDocument: code.TextDocument, position: code.Position) => proto.TextDocumentPositionParams = defaultConverter.asTextDocumentPositionParams; +export const asWorkerPosition: (position: code.Position) => ls.Position = defaultConverter.asWorkerPosition; +export const asRange: (value: code.Range) => ls.Range = defaultConverter.asRange; +export const asPosition: (value: code.Position) => ls.Position = defaultConverter.asPosition; +export const asDiagnosticSeverity: (value: code.DiagnosticSeverity) => ls.DiagnosticSeverity = defaultConverter.asDiagnosticSeverity; +export const asDiagnostic: (item: code.Diagnostic) => ls.Diagnostic = defaultConverter.asDiagnostic; +export const asDiagnostics: (items: code.Diagnostic[]) => ls.Diagnostic[] = defaultConverter.asDiagnostics; +export const asCompletionItem: (item: code.CompletionItem) => ls.CompletionItem = defaultConverter.asCompletionItem; +export const asTextEdit: (edit: code.TextEdit) => ls.TextEdit = defaultConverter.asTextEdit; +export const asReferenceParams: (textDocument: code.TextDocument, position: code.Position, options: { includeDeclaration: boolean; }) => proto.ReferenceParams = defaultConverter.asReferenceParams; +export const asCodeActionContext: (context: code.CodeActionContext) => ls.CodeActionContext = defaultConverter.asCodeActionContext; +export const asCommand: (item: code.Command) => ls.Command = defaultConverter.asCommand; +export const asCodeLens: (item: code.CodeLens) => ls.CodeLens = defaultConverter.asCodeLens; +export const asFormattingOptions: (item: code.FormattingOptions) => ls.FormattingOptions = defaultConverter.asFormattingOptions; +export const asDocumentSymbolParams: (textDocument: code.TextDocument) => proto.DocumentSymbolParams = defaultConverter.asDocumentSymbolParams; +export const asCodeLensParams: (textDocument: code.TextDocument) => proto.CodeLensParams = defaultConverter.asCodeLensParams; \ No newline at end of file diff --git a/dataprotocol-node/client/src/main.ts b/dataprotocol-node/client/src/main.ts new file mode 100644 index 0000000000..907eb80890 --- /dev/null +++ b/dataprotocol-node/client/src/main.ts @@ -0,0 +1,2612 @@ +/* -------------------------------------------------------------------------------------------- + * 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(); + } + } +} \ No newline at end of file diff --git a/dataprotocol-node/client/src/protocol.ts b/dataprotocol-node/client/src/protocol.ts new file mode 100644 index 0000000000..d59d3bced6 --- /dev/null +++ b/dataprotocol-node/client/src/protocol.ts @@ -0,0 +1,1601 @@ +/* -------------------------------------------------------------------------------------------- + * 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 { RequestType, NotificationType, ResponseError } from 'dataprotocol-jsonrpc'; + +import { + TextDocument, TextDocumentChangeEvent, TextDocumentContentChangeEvent, + Range, Position, Location, Diagnostic, DiagnosticSeverity, Command, + TextEdit, WorkspaceEdit, WorkspaceChange, TextEditChange, + TextDocumentIdentifier, VersionedTextDocumentIdentifier, TextDocumentItem, + CompletionItemKind, CompletionItem, CompletionList, + Hover, MarkedString, + SignatureHelp, SignatureInformation, ParameterInformation, + Definition, ReferenceContext, + DocumentHighlight, DocumentHighlightKind, + SymbolInformation, SymbolKind, + CodeLens, CodeActionContext, + FormattingOptions, DocumentLink, + ConnectionDetails, ServerInfo, + ConnectionSummary, ConnectionCompleteParams, IntelliSenseReadyParams, + ColumnMetadata, IDbColumn, + ConnectionProviderOptions, DataProtocolServerCapabilities, + CapabiltiesDiscoveryResult, MetadataQueryParams, MetadataQueryResult, + ScriptingParams, ScriptingResult, ScriptingCompleteParams, + BatchSummary, QueryExecuteBatchNotificationParams, ResultSetSummary, IResultMessage, ISelectionData, + DbCellValue, EditCell, EditRow, CreateSessionResponse, SessionCreatedParameters, ExpandParams, ExpandResponse, CloseSessionParams, CloseSessionResponse, + BackupInfo, BackupParams, BackupResponse, + RestoreParams, RestoreResponse, RestorePlanResponse, RestoreConfigInfoRequestParams, RestoreConfigInfoResponse, + LoginInfo, CreateLoginParams, CreateLoginResponse, GetDatabaseInfoParams, GetDatabaseInfoResponse, + DatabaseInfo, BackupConfigInfo, CreateDatabaseParams, CreateDatabaseResponse, + TaskInfo, ListTasksParams, ListTasksResponse, CancelTaskParams, TaskProgressInfo, + DefaultDatabaseInfoParams, DefaultDatabaseInfoResponse, BackupConfigInfoResponse, FileBrowserOpenParams, FileBrowserOpenedParams, + FileBrowserCloseParams, FileBrowserExpandParams, FileBrowserValidateParams, + FileBrowserCloseResponse, FileBrowserExpandedParams, FileBrowserValidatedParams, + StartProfilingParams, StartProfilingResponse, StopProfilingParams, StopProfilingResponse, + ProfilerEventsAvailableParams +} from 'dataprotocol-languageserver-types'; + + +/** + * A parameter literal used in requests to pass a text document and a position inside that + * document. + */ +export interface TextDocumentPositionParams { + /** + * The text document. + */ + textDocument: TextDocumentIdentifier; + + /** + * The position inside the text document. + */ + position: Position; +} + + +//---- Initialize Method ---- + +/** + * Defines the capabilities provided by the client. + */ +export interface ClientCapabilities { +} + +/** + * Defines how the host (editor) should sync + * document changes to the language server. + */ +export enum TextDocumentSyncKind { + /** + * Documents should not be synced at all. + */ + None = 0, + + /** + * Documents are synced by always sending the full content + * of the document. + */ + Full = 1, + + /** + * Documents are synced by sending the full content on open. + * After that only incremental updates to the document are + * send. + */ + Incremental = 2 +} + +/** + * Completion options. + */ +export interface CompletionOptions { + /** + * The server provides support to resolve additional + * information for a completion item. + */ + resolveProvider?: boolean; + + /** + * The characters that trigger completion automatically. + */ + triggerCharacters?: string[]; +} + +/** + * Signature help options. + */ +export interface SignatureHelpOptions { + /** + * The characters that trigger signature help + * automatically. + */ + triggerCharacters?: string[]; +} + +/** + * Code Lens options. + */ +export interface CodeLensOptions { + /** + * Code lens has a resolve provider as well. + */ + resolveProvider?: boolean; +} + +/** + * Format document on type options + */ +export interface DocumentOnTypeFormattingOptions { + /** + * A character on which formatting should be triggered, like `}`. + */ + firstTriggerCharacter: string; + /** + * More trigger characters. + */ + moreTriggerCharacter?: string[] +} + +/** + * Document link options + */ +export interface DocumentLinkOptions { + /** + * Document links have a resolve provider as well. + */ + resolveProvider?: boolean; +} + +/** + * Defines the capabilities provided by a language + * server. + */ +export interface ServerCapabilities { + /** + * Defines how text documents are synced. + */ + textDocumentSync?: number; + /** + * The server provides hover support. + */ + hoverProvider?: boolean; + /** + * The server provides completion support. + */ + completionProvider?: CompletionOptions; + /** + * The server provides signature help support. + */ + signatureHelpProvider?: SignatureHelpOptions; + /** + * The server provides goto definition support. + */ + definitionProvider?: boolean; + /** + * The server provides find references support. + */ + referencesProvider?: boolean; + /** + * The server provides document highlight support. + */ + documentHighlightProvider?: boolean; + /** + * The server provides document symbol support. + */ + documentSymbolProvider?: boolean; + /** + * The server provides workspace symbol support. + */ + workspaceSymbolProvider?: boolean; + /** + * The server provides code actions. + */ + codeActionProvider?: boolean; + /** + * The server provides code lens. + */ + codeLensProvider?: CodeLensOptions; + /** + * The server provides document formatting. + */ + documentFormattingProvider?: boolean; + /** + * The server provides document range formatting. + */ + documentRangeFormattingProvider?: boolean; + /** + * The server provides document formatting on typing. + */ + documentOnTypeFormattingProvider?: DocumentOnTypeFormattingOptions; + /** + * The server provides rename support. + */ + renameProvider?: boolean; + /** + * The server provides document link support. + */ + documentLinkProvider?: DocumentLinkOptions; + + connectionProvider?: boolean; +} + +/** + * The initialize method is sent from the client to the server. + * It is send once as the first method after starting up the + * worker. The requests parameter is of type [InitializeParams](#InitializeParams) + * the response if of type [InitializeResult](#InitializeResult) of a Thenable that + * resolves to such. + */ +export namespace InitializeRequest { + export const type: RequestType = { get method() { return 'initialize'; } }; +} + +/** + * The initialize parameters + */ +export interface InitializeParams { + /** + * The process Id of the parent process that started + * the server. + */ + processId: number; + + /** + * The rootPath of the workspace. Is null + * if no folder is open. + */ + rootPath: string; + + /** + * The capabilities provided by the client (editor) + */ + capabilities: ClientCapabilities; + + /** + * User provided initialization options. + */ + initializationOptions?: any; + + /** + * The initial trace setting. If omitted trace is disabled ('off'). + */ + trace?: 'off' | 'messages' | 'verbose'; +} + +/** + * The result returned from an initilize request. + */ +export interface InitializeResult { + /** + * The capabilities the language server provides. + */ + capabilities: ServerCapabilities; +} + +/** + * The data type of the ResponseError if the + * initialize request fails. + */ +export interface InitializeError { + /** + * Indicates whether the client should retry to send the + * initilize request after showing the message provided + * in the {@link ResponseError} + */ + retry: boolean; +} + +//---- Shutdown Method ---- + +/** + * A shutdown request is sent from the client to the server. + * It is send once when the client descides to shutdown the + * server. The only notification that is sent after a shudown request + * is the exit event. + */ +export namespace ShutdownRequest { + export const type: RequestType = { get method() { return 'shutdown'; } }; +} + +//---- Exit Notification ---- + +/** + * The exit event is sent from the client to the server to + * ask the server to exit its process. + */ +export namespace ExitNotification { + export const type: NotificationType = { get method() { return 'exit'; } }; +} + +//---- Configuration notification ---- + +/** + * The configuration change notification is sent from the client to the server + * when the client's configuration has changed. The notification contains + * the changed configuration as defined by the language client. + */ +export namespace DidChangeConfigurationNotification { + export const type: NotificationType = { get method() { return 'workspace/didChangeConfiguration'; } }; +} + +/** + * The parameters of a change configuration notification. + */ +export interface DidChangeConfigurationParams { + /** + * The actual changed settings + */ + settings: any; +} + +//---- Message show and log notifications ---- + +/** + * The message type + */ +export enum MessageType { + /** + * An error message. + */ + Error = 1, + /** + * A warning message. + */ + Warning = 2, + /** + * An information message. + */ + Info = 3, + /** + * A log message. + */ + Log = 4 +} + +/** + * The parameters of a notification message. + */ +export interface ShowMessageParams { + /** + * The message type. See {@link MessageType} + */ + type: number; + + /** + * The actual message + */ + message: string; +} + +/** + * The show message notification is sent from a server to a client to ask + * the client to display a particular message in the user interface. + */ +export namespace ShowMessageNotification { + export const type: NotificationType = { get method() { return 'window/showMessage'; } }; +} + +export interface MessageActionItem { + /** + * A short title like 'Retry', 'Open Log' etc. + */ + title: string; +} + +export interface ShowMessageRequestParams { + /** + * The message type. See {@link MessageType} + */ + type: number; + + /** + * The actual message + */ + message: string; + + /** + * The message action items to present. + */ + actions?: MessageActionItem[]; +} + +/** + * The show message request is send from the server to the clinet to show a message + * and a set of options actions to the user. + */ +export namespace ShowMessageRequest { + export const type: RequestType = { get method() { return 'window/showMessageRequest'; } }; +} + +/** + * The log message notification is send from the server to the client to ask + * the client to log a particular message. + */ +export namespace LogMessageNotification { + export let type: NotificationType = { get method() { return 'window/logMessage'; } }; +} + +/** + * The log message parameters. + */ +export interface LogMessageParams { + /** + * The message type. See {@link MessageType} + */ + type: number; + + /** + * The actual message + */ + message: string; +} + +//---- Telemetry notification + +/** + * The telemetry event notification is send from the server to the client to ask + * the client to log telemetry data. + */ +export namespace TelemetryEventNotification { + export let type: NotificationType = { get method() { return 'telemetry/event'; } }; +} + +//---- Text document notifications ---- + +/** + * The parameters send in a open text document notification + */ +export interface DidOpenTextDocumentParams { + /** + * The document that was opened. + */ + textDocument: TextDocumentItem; +} + +/** + * The document open notification is sent from the client to the server to signal + * newly opened text documents. The document's truth is now managed by the client + * and the server must not try to read the document's truth using the document's + * uri. + */ +export namespace DidOpenTextDocumentNotification { + export const type: NotificationType = { get method() { return 'textDocument/didOpen'; } }; +} + +/** + * An event describing a change to a text document. If range and rangeLength are omitted + * the new text is considered to be the full content of the document. + */ +export interface TextDocumentContentChangeEvent { + /** + * The range of the document that changed. + */ + range?: Range; + + /** + * The length of the range that got replaced. + */ + rangeLength?: number; + + /** + * The new text of the document. + */ + text: string; +} + +/** + * The change text document notification's parameters. + */ +export interface DidChangeTextDocumentParams { + /** + * The document that did change. The version number points + * to the version after all provided content changes have + * been applied. + */ + textDocument: VersionedTextDocumentIdentifier; + + /** + * The actual content changes. + */ + contentChanges: TextDocumentContentChangeEvent[]; +} + +/** + * The document change notification is sent from the client to the server to signal + * changes to a text document. + */ +export namespace DidChangeTextDocumentNotification { + export const type: NotificationType = { get method() { return 'textDocument/didChange'; } }; +} + +/** + * The parameters send in a close text document notification + */ +export interface DidCloseTextDocumentParams { + /** + * The document that was closed. + */ + textDocument: TextDocumentIdentifier; +} + +/** + * The document close notification is sent from the client to the server when + * the document got closed in the client. The document's truth now exists + * where the document's uri points to (e.g. if the document's uri is a file uri + * the truth now exists on disk). + */ +export namespace DidCloseTextDocumentNotification { + export const type: NotificationType = { get method() { return 'textDocument/didClose'; } }; +} + +/** + * The parameters send in a save text document notification + */ +export interface DidSaveTextDocumentParams { + /** + * The document that was closed. + */ + textDocument: TextDocumentIdentifier; +} + +/** + * The document save notification is sent from the client to the server when + * the document got saved in the client. + */ +export namespace DidSaveTextDocumentNotification { + export const type: NotificationType = { get method() { return 'textDocument/didSave'; } }; +} + +//---- File eventing ---- + +/** + * The watched files notification is sent from the client to the server when + * the client detects changes to file watched by the lanaguage client. + */ +export namespace DidChangeWatchedFilesNotification { + export const type: NotificationType = { get method() { return 'workspace/didChangeWatchedFiles'; } }; +} + +/** + * The watched files change notification's parameters. + */ +export interface DidChangeWatchedFilesParams { + /** + * The actual file events. + */ + changes: FileEvent[]; +} + +/** + * The file event type + */ +export enum FileChangeType { + /** + * The file got created. + */ + Created = 1, + /** + * The file got changed. + */ + Changed = 2, + /** + * The file got deleted. + */ + Deleted = 3 +} + +/** + * An event describing a file change. + */ +export interface FileEvent { + /** + * The file's uri. + */ + uri: string; + /** + * The change type. + */ + type: number; +} + +//---- Diagnostic notification ---- + +/** + * Diagnostics notification are sent from the server to the client to signal + * results of validation runs. + */ +export namespace PublishDiagnosticsNotification { + export const type: NotificationType = { get method() { return 'textDocument/publishDiagnostics'; } }; +} + +/** + * The publish diagnostic notification's parameters. + */ +export interface PublishDiagnosticsParams { + /** + * The URI for which diagnostic information is reported. + */ + uri: string; + + /** + * An array of diagnostic information items. + */ + diagnostics: Diagnostic[]; +} + +//---- Completion Support -------------------------- + +/** + * Request to request completion at a given text document position. The request's + * parameter is of type [TextDocumentPosition](#TextDocumentPosition) the response + * is of type [CompletionItem[]](#CompletionItem) or [CompletionList](#CompletionList) + * or a Thenable that resolves to such. + */ +export namespace CompletionRequest { + export const type: RequestType = { get method() { return 'textDocument/completion'; } }; +} + +/** + * Request to resolve additional information for a given completion item.The request's + * parameter is of type [CompletionItem](#CompletionItem) the response + * is of type [CompletionItem](#CompletionItem) or a Thenable that resolves to such. + */ +export namespace CompletionResolveRequest { + export const type: RequestType = { get method() { return 'completionItem/resolve'; } }; +} + +//---- Hover Support ------------------------------- + +export type MarkedString = string | { language: string; value: string }; + +/** + * Request to request hover information at a given text document position. The request's + * parameter is of type [TextDocumentPosition](#TextDocumentPosition) the response is of + * type [Hover](#Hover) or a Thenable that resolves to such. + */ +export namespace HoverRequest { + export const type: RequestType = { get method() { return 'textDocument/hover'; } }; +} + +//---- SignatureHelp ---------------------------------- + +export namespace SignatureHelpRequest { + export const type: RequestType = { get method() { return 'textDocument/signatureHelp'; } }; +} + +//---- Goto Definition ------------------------------------- + + +/** + * A request to resolve the defintion location of a symbol at a given text + * document position. The request's parameter is of type [TextDocumentPosition] + * (#TextDocumentPosition) the response is of type [Definition](#Definition) or a + * Thenable that resolves to such. + */ +export namespace DefinitionRequest { + export const type: RequestType = { get method() { return 'textDocument/definition'; } }; +} + +//---- Reference Provider ---------------------------------- + +/** + * Parameters for a [ReferencesRequest](#ReferencesRequest). + */ +export interface ReferenceParams extends TextDocumentPositionParams { + context: ReferenceContext +} + +/** + * A request to resolve project-wide references for the symbol denoted + * by the given text document position. The request's parameter is of + * type [ReferenceParams](#ReferenceParams) the response is of type + * [Location[]](#Location) or a Thenable that resolves to such. + */ +export namespace ReferencesRequest { + export const type: RequestType = { get method() { return 'textDocument/references'; } }; +} + +//---- Document Highlight ---------------------------------- + +/** + * Request to resolve a [DocumentHighlight](#DocumentHighlight) for a given + * text document position. The request's parameter is of type [TextDocumentPosition] + * (#TextDocumentPosition) the request reponse is of type [DocumentHighlight[]] + * (#DocumentHighlight) or a Thenable that resolves to such. + */ +export namespace DocumentHighlightRequest { + export const type: RequestType = { get method() { return 'textDocument/documentHighlight'; } }; +} + +//---- Document Symbol Provider --------------------------- + +/** + * Parameters for a [DocumentSymbolRequest](#DocumentSymbolRequest). + */ +export interface DocumentSymbolParams { + /** + * The text document. + */ + textDocument: TextDocumentIdentifier; +} + +/** + * A request to list all symbols found in a given text document. The request's + * parameter is of type [TextDocumentIdentifier](#TextDocumentIdentifier) the + * response is of type [SymbolInformation[]](#SymbolInformation) or a Thenable + * that resolves to such. + */ +export namespace DocumentSymbolRequest { + export const type: RequestType = { get method() { return 'textDocument/documentSymbol'; } }; +} + +//---- Workspace Symbol Provider --------------------------- + +/** + * The parameters of a [WorkspaceSymbolRequest](#WorkspaceSymbolRequest). + */ +export interface WorkspaceSymbolParams { + /** + * A non-empty query string + */ + query: string; +} + +/** + * A request to list project-wide symbols matching the query string given + * by the [WorkspaceSymbolParams](#WorkspaceSymbolParams). The response is + * of type [SymbolInformation[]](#SymbolInformation) or a Thenable that + * resolves to such. + */ +export namespace WorkspaceSymbolRequest { + export const type: RequestType = { get method() { return 'workspace/symbol'; } }; +} + +//---- Code Action Provider ---------------------------------- + + + +/** + * Params for the CodeActionRequest + */ +export interface CodeActionParams { + /** + * The document in which the command was invoked. + */ + textDocument: TextDocumentIdentifier; + + /** + * The range for which the command was invoked. + */ + range: Range; + + /** + * Context carrying additional information. + */ + context: CodeActionContext; +} + +/** + * A request to provide commands for the given text document and range. + */ +export namespace CodeActionRequest { + export const type: RequestType = { get method() { return 'textDocument/codeAction'; } }; +} + +//---- Code Lens Provider ------------------------------------------- + +/** + * Params for the Code Lens request. + */ +export interface CodeLensParams { + /** + * The document to request code lens for. + */ + textDocument: TextDocumentIdentifier; +} + +/** + * A request to provide code lens for the given text document. + */ +export namespace CodeLensRequest { + export const type: RequestType = { get method() { return 'textDocument/codeLens'; } }; +} + +/** + * A request to resolve a command for a given code lens. + */ +export namespace CodeLensResolveRequest { + export const type: RequestType = { get method() { return 'codeLens/resolve'; } }; +} + +//---- Formatting ---------------------------------------------- + +export interface DocumentFormattingParams { + /** + * The document to format. + */ + textDocument: TextDocumentIdentifier; + + /** + * The format options + */ + options: FormattingOptions; +} + +/** + * A request to to format a whole document. + */ +export namespace DocumentFormattingRequest { + export const type: RequestType = { get method() { return 'textDocument/formatting'; } }; +} + +export interface DocumentRangeFormattingParams { + /** + * The document to format. + */ + textDocument: TextDocumentIdentifier; + + /** + * The range to format + */ + range: Range; + + /** + * The format options + */ + options: FormattingOptions; +} + +/** + * A request to to format a range in a document. + */ +export namespace DocumentRangeFormattingRequest { + export const type: RequestType = { get method() { return 'textDocument/rangeFormatting'; } }; +} + +export interface DocumentOnTypeFormattingParams { + /** + * The document to format. + */ + textDocument: TextDocumentIdentifier; + + /** + * The position at which this request was send. + */ + position: Position; + + /** + * The character that has been typed. + */ + ch: string; + + /** + * The format options. + */ + options: FormattingOptions; +} + +/** + * A request to format a document on type. + */ +export namespace DocumentOnTypeFormattingRequest { + export const type: RequestType = { get method() { return 'textDocument/onTypeFormatting'; } }; +} + +//---- Rename ---------------------------------------------- + +export interface RenameParams { + /** + * The document to format. + */ + textDocument: TextDocumentIdentifier; + + /** + * The position at which this request was send. + */ + position: Position; + + /** + * The new name of the symbol. If the given name is not valid the + * request must return a [ResponseError](#ResponseError) with an + * appropriate message set. + */ + newName: string; +} + +/** + * A request to rename a symbol. + */ +export namespace RenameRequest { + export const type: RequestType = { get method() { return 'textDocument/rename'; } }; +} + +//---- Document Links ---------------------------------------------- + +export interface DocumentLinkParams { + /** + * The document to provide document links for. + */ + textDocument: TextDocumentIdentifier; +} + +/** + * A request to provide document links + */ +export namespace DocumentLinkRequest { + export const type: RequestType = { get method() { return 'textDocument/documentLink'; } }; +} + +/** + * Request to resolve additional information for a given document link. The request's + * parameter is of type [DocumentLink](#DocumentLink) the response + * is of type [DocumentLink](#DocumentLink) or a Thenable that resolves to such. + */ +export namespace DocumentLinkResolveRequest { + export const type: RequestType = { get method() { return 'documentLink/resolve'; } }; +} + +//---- Refresh IntelliSense ---------------------------------------- + +/** + * Notification sent when the an IntelliSense cache invalidation is requested + */ +export namespace RebuildIntelliSenseNotification { + export const type: NotificationType = { get method(): string { return 'textDocument/rebuildIntelliSense'; } }; +} + +/** + * Rebuild IntelliSense notification parameters + */ +export class RebuildIntelliSenseParams { + /** + * URI identifying the text document + */ + public ownerUri: string; +} + +// ------------------------------- < Connect Request > ---------------------------------------------- + +/** + * Connection request message format + */ +export interface ConnectParams { + /** + * URI identifying the owner of the connection + */ + ownerUri: string; + + /** + * Details for creating the connection + */ + connection: ConnectionDetails; +} + + +// Connection request message callback declaration +export namespace ConnectionRequest { + export const type: RequestType = { get method(): string { return 'connection/connect'; } }; +} + +// ------------------------------- < Connection Complete Event > ------------------------------------ + + +export namespace ConnectionCompleteNotification { + export const type: NotificationType = { get method(): string { return 'connection/complete'; } }; +} + +// ------------------------------- < Connection Changed Event > ------------------------------------- + +/** + * Parameters for the ConnectionChanged notification. + */ +export class ConnectionChangedParams { + /** + * Owner URI of the connection that changed. + */ + public ownerUri: string; + + /** + * Summary of details containing any connection changes. + */ + public connection: ConnectionSummary; +} + +/** + * Connection changed event callback declaration. + */ +export namespace ConnectionChangedNotification { + export const type: NotificationType = { get method(): string { return 'connection/connectionchanged'; } }; +} + +// ------------------------------- < Disconnect Request > ------------------------------------------- + +// Disconnect request message format +export class DisconnectParams { + // URI identifying the owner of the connection + public ownerUri: string; +} + +// Disconnect response format +export type DisconnectResult = boolean; + +// Disconnect request message callback declaration +export namespace DisconnectRequest { + export const type: RequestType = { get method(): string { return 'connection/disconnect'; } }; +} + +// ------------------------------- < Cancel Connect Request > --------------------------------------- + + +// Cancel connect request message format +export class CancelConnectParams { + /** + * URI identifying the owner of the connection + */ + public ownerUri: string; +} + +// Cancel connect response format. +export type CancelConnectResult = boolean; + +// Cancel connect request message callback declaration +export namespace CancelConnectRequest { + export const type: RequestType = { get method(): string { return 'connection/cancelconnect'; } }; +} + +// ------------------------------- < Change Database Request > ------------------------------------- + +export class ChangeDatabaseParams { + public ownerUri: string; + public newDatabase: string; +} + +export namespace ChangeDatabaseRequest { + export const type: RequestType = { get method(): string { return 'connection/changedatabase'; } }; +} + +// ------------------------------- < List Databases Request > --------------------------------------- + +// List databases request format +export class ListDatabasesParams { + // Connection information to use for querying master + public ownerUri: string; +} + +// List databases response format +export class ListDatabasesResult { + public databaseNames: Array; +} + +// List databases request callback declaration +export namespace ListDatabasesRequest { + export const type: RequestType = { get method(): string { return 'connection/listdatabases'; } }; +} + +// Language Flavor Changed ================================================================================ + +/** + * Parameters to provide when sending a language flavor changed notification + */ +export interface DidChangeLanguageFlavorParams { + uri: string; + language: string; + flavor: string; +} + +// ------------------------------- < Language Flavor Changed Notification > --------------------------------------- +export namespace LanguageFlavorChangedNotification { + export const type: NotificationType = { get method(): string { return 'connection/languageflavorchanged'; } }; +} + +// ------------------------------- < Table Metadata Request > --------------------------------------- + +// Table metadata request format +export class TableMetadataParams { + // Connection information to use for querying master + public ownerUri: string; + + public schema: string; + + public objectName: string; +} + +// Table metadata response format +export class TableMetadataResult { + public columns: ColumnMetadata[]; +} + +// Table metadata request callback declaration +export namespace TableMetadataRequest { + export const type: RequestType = { get method(): string { return 'metadata/table'; } }; +} + +// ------------------------------- < View Metadata Request > --------------------------------------- + +// Table metadata request callback declaration +export namespace ViewMetadataRequest { + export const type: RequestType = { get method(): string { return 'metadata/view'; } }; +} + +/** + * Event sent when the language service is finished updating after a connection + */ +export namespace IntelliSenseReadyNotification { + export const type: NotificationType = { get method(): string { return 'textDocument/intelliSenseReady'; } }; +} + +// ------------------------------- < Capabilties Discovery Event > ------------------------------------ + +export class CapabiltiesDiscoveryParams { + public hostName: string; + + public hostVersion: string; +} + +export namespace CapabiltiesDiscoveryRequest { + export const type: RequestType = { get method(): string { return 'capabilities/list'; } }; +} + +// Query Execution ================================================================================ +// ------------------------------- < Query Cancellation Request > ------------------------------------ +export namespace QueryCancelRequest { + export const type: RequestType = { get method(): string { return 'query/cancel'; } }; +} + +export interface QueryCancelParams { + ownerUri: string; +} + +export interface QueryCancelResult { + messages: string; +} + +// ------------------------------- < Query Dispose Request > ------------------------------------ + +export namespace QueryDisposeRequest { + export const type: RequestType = { get method(): string { return 'query/dispose'; } }; +} + +/** + * Parameters to provide when disposing of a query + */ +export interface QueryDisposeParams { + ownerUri: string; +} + +/** + * Result received upon successful disposal of a query + */ +export interface QueryDisposeResult { +} + +// ------------------------------- < Query Execution Complete Notification > ------------------------------------ +export namespace QueryExecuteCompleteNotification { + export const type: NotificationType = { get method(): string { return 'query/complete'; } }; +} + +/** + * Result received upon successful execution of a query + */ +export interface QueryExecuteCompleteNotificationResult { + ownerUri: string; + batchSummaries: BatchSummary[]; +} + +// ------------------------------- < Query Batch Start Notification > ------------------------------------ +export namespace QueryExecuteBatchStartNotification { + export const type: NotificationType = { get method(): string { return 'query/batchStart'; } }; +} + +// ------------------------------- < Query Batch Complete Notification > ------------------------------------ +export namespace QueryExecuteBatchCompleteNotification { + export const type: NotificationType = { get method(): string { return 'query/batchComplete'; } }; +} + +// ------------------------------- < Query ResultSet Complete Notification > ------------------------------------ +export namespace QueryExecuteResultSetCompleteNotification { + export const type: NotificationType = { get method(): string { return 'query/resultSetComplete'; } }; +} + +export interface QueryExecuteResultSetCompleteNotificationParams { + resultSetSummary: ResultSetSummary; + ownerUri: string; +} + +// ------------------------------- < Query Message Notification > ------------------------------------ +export namespace QueryExecuteMessageNotification { + export const type: NotificationType = { get method(): string { return 'query/message'; } }; +} + +export class QueryExecuteMessageParams { + message: IResultMessage; + ownerUri: string; +} + +// ------------------------------- < Query Execution Request > ------------------------------------ +export namespace QueryExecuteRequest { + export const type: RequestType = { get method(): string { return 'query/executeDocumentSelection'; } }; +} + +export interface ExecutionPlanOptions { + includeEstimatedExecutionPlanXml?: boolean; + includeActualExecutionPlanXml?: boolean; +} + +export interface QueryExecuteParams { + ownerUri: string; + querySelection: ISelectionData; + executionPlanOptions?: ExecutionPlanOptions; +} + +export interface QueryExecuteResult { } + +// ------------------------------- < Query Results Request > ------------------------------------ +export namespace QueryExecuteSubsetRequest { + export const type: RequestType = { get method(): string { return 'query/subset'; } }; +} + +export interface QueryExecuteSubsetParams { + ownerUri: string; + batchIndex: number; + resultSetIndex: number; + rowsStartIndex: number; + rowsCount: number; +} + +export interface ResultSetSubset { + rowCount: number; + rows: DbCellValue[][]; +} + +export interface QueryExecuteSubsetResult { + message: string; + resultSubset: ResultSetSubset; +} + +// ------------------------------- < Execute Statement > ------------------------------------ +export interface QueryExecuteStatementParams { + ownerUri: string; + line: number; + column: number; +} + +export namespace QueryExecuteStatementRequest { + export const type: RequestType = { get method(): string { return 'query/executedocumentstatement'; } }; +} + +// --------------------------------- < Save Results as CSV Request > ------------------------------------------ +export interface SaveResultsRequestParams { + ownerUri: string; + filePath: string; + batchIndex: number; + resultSetIndex: number; + rowStartIndex: number; + rowEndIndex: number; + columnStartIndex: number; + columnEndIndex: number; + includeHeaders?: boolean; +} + +export class SaveResultRequestResult { + messages: string; +} +// save results in csv format +export namespace SaveResultsAsCsvRequest { + export const type: RequestType = { get method(): string { return 'query/saveCsv'; } }; +} +// --------------------------------- ------------------------------------------ + +// --------------------------------- < Save Results as JSON Request > ------------------------------------------ +// save results in json format +export namespace SaveResultsAsJsonRequest { + export const type: RequestType = { get method(): string { return 'query/saveJson'; } }; +} +// --------------------------------- ------------------------------------------ + +// --------------------------------- < Save Results as Excel Request > ------------------------------------------ +// save results in Excel format +export namespace SaveResultsAsExcelRequest { + export const type: RequestType = { get method(): string { return 'query/saveExcel'; } }; +} +// --------------------------------- ------------------------------------------ + +// ------------------------------- < Execute and Return > ----------------------------------- + +export interface SimpleExecuteParams { + queryString: string; + ownerUri: string; +} + +export interface SimpleExecuteResult { + rowCount: number; + columnInfo: IDbColumn[]; + rows: DbCellValue[][]; +} + +export namespace SimpleExecuteRequest { + export const type: RequestType = { get method(): string { return 'query/simpleexecute'; } }; +} + +// ------------------------------- < Execute String > ------------------------------------ +export interface QueryExecuteStringParams { + query: string; + ownerUri: string; +} + +export namespace QueryExecuteStringRequest { + export const type: RequestType = { get method(): string { return 'query/executeString'; } }; +} + +// ------------------------------- < Metadata Events > ------------------------------------ + +export namespace MetadataQueryRequest { + export const type: RequestType = { get method(): string { return 'metadata/list'; } }; +} + +// ------------------------------- < Scripting Events > ------------------------------------ + +export namespace ScriptingRequest { + export const type: RequestType = { get method(): string { return 'scripting/script'; } }; +} + +// ------------------------------- < Scripting Complete Event > ------------------------------------ + +export namespace ScriptingCompleteNotification { + export const type: NotificationType = { get method(): string { return 'scripting/scriptComplete'; } }; +} + + +// Edit Data ====================================================================================== +// Shared Interfaces -------------------------------------------------------------------------- +export interface EditSessionOperationParams { + ownerUri: string; +} + +export interface EditRowOperationParams extends EditSessionOperationParams { + rowId: number; +} + +export interface EditCellResult { + cell: EditCell; + isRowDirty: boolean; +} + +// edit/commit -------------------------------------------------------------------------------- +export namespace EditCommitRequest { + export const type: RequestType = { get method(): string { return 'edit/commit'; } }; +} +export interface EditCommitParams extends EditSessionOperationParams { } +export interface EditCommitResult { } + +// edit/createRow ----------------------------------------------------------------------------- +export namespace EditCreateRowRequest { + export const type: RequestType = { get method(): string { return 'edit/createRow'; } }; +} +export interface EditCreateRowParams extends EditSessionOperationParams { } +export interface EditCreateRowResult { + defaultValues: string[]; + newRowId: number; +} + +// edit/deleteRow ----------------------------------------------------------------------------- +export namespace EditDeleteRowRequest { + export const type: RequestType = { get method(): string { return 'edit/deleteRow'; } }; +} +export interface EditDeleteRowParams extends EditRowOperationParams { } +export interface EditDeleteRowResult { } + +// edit/dispose ------------------------------------------------------------------------------- +export namespace EditDisposeRequest { + export const type: RequestType = { get method(): string { return 'edit/dispose'; } }; +} +export interface EditDisposeParams extends EditSessionOperationParams { } +export interface EditDisposeResult { } + +// edit/initialize ---------------------------------------------------------------------------- +export namespace EditInitializeRequest { + export const type: RequestType = { get method(): string { return 'edit/initialize'; } }; +} +export interface EditInitializeFiltering { + LimitResults?: number; +} +export interface EditInitializeParams extends EditSessionOperationParams { + filters: EditInitializeFiltering; + objectName: string; + schemaName: string; + objectType: string; +} +export interface EditInitializeResult { } + +// edit/revertCell -------------------------------------------------------------------------------- +export namespace EditRevertCellRequest { + export const type: RequestType = { get method(): string { return 'edit/revertCell'; } }; +} +export interface EditRevertCellParams extends EditRowOperationParams { + columnId: number; +} +export interface EditRevertCellResult extends EditCellResult { +} + +// edit/revertRow ----------------------------------------------------------------------------- +export namespace EditRevertRowRequest { + export const type: RequestType = { get method(): string { return 'edit/revertRow'; } }; +} +export interface EditRevertRowParams extends EditRowOperationParams { } +export interface EditRevertRowResult { } + +// edit/sessionReady Event -------------------------------------------------------------------- +export namespace EditSessionReadyNotification { + export const type: NotificationType = { get method(): string { return 'edit/sessionReady'; } }; +} +export interface EditSessionReadyParams { + ownerUri: string; + success: boolean; + message: string; +} + +// edit/updateCell ---------------------------------------------------------------------------- +export namespace EditUpdateCellRequest { + export const type: RequestType = { get method(): string { return 'edit/updateCell'; } }; +} +export interface EditUpdateCellParams extends EditRowOperationParams { + columnId: number; + newValue: string; +} +export interface EditUpdateCellResult extends EditCellResult { } + +// edit/subset ------------------------------------------------------------------------------------ +export namespace EditSubsetRequest { + export const type: RequestType = { get method(): string { return 'edit/subset'; } }; +} +export interface EditSubsetParams extends EditSessionOperationParams { + rowStartIndex: number; + rowCount: number; +} +export interface EditSubsetResult { + rowCount: number; + subset: EditRow[]; +} + +// ------------------------------- < Object Explorer Events > ------------------------------------ + +export namespace ObjectExplorerCreateSessionRequest { + export const type: RequestType = { get method(): string { return 'objectexplorer/createsession'; } }; +} + +export namespace ObjectExplorerExpandRequest { + export const type: RequestType = { get method(): string { return 'objectexplorer/expand'; } }; +} + +export namespace ObjectExplorerRefreshRequest { + export const type: RequestType = { get method(): string { return 'objectexplorer/refresh'; } }; +} + +export namespace ObjectExplorerCloseSessionRequest { + export const type: RequestType = { get method(): string { return 'objectexplorer/closesession'; } }; +} + +// ------------------------------- < Object Explorer Events > ------------------------------------ + + +export namespace ObjectExplorerCreateSessionCompleteNotification { + export const type: NotificationType = { get method(): string { return 'objectexplorer/sessioncreated'; } }; +} + + +export namespace ObjectExplorerExpandCompleteNotification { + export const type: NotificationType = { get method(): string { return 'objectexplorer/expandCompleted'; } }; +} + +// ------------------------------- < Task Service Events > ------------------------------------ + +export namespace ListTasksRequest { + export const type: RequestType = { get method(): string { return 'tasks/listtasks'; } }; +} + +export namespace CancelTaskRequest { + export const type: RequestType = { get method(): string { return 'tasks/canceltask'; } }; +} + +// ------------------------------- < Task Service Events > ------------------------------------ + + +export namespace TaskStatusChangedNotification { + export const type: NotificationType = { get method(): string { return 'tasks/statuschanged'; } }; +} + +export namespace TaskCreatedNotification { + export const type: NotificationType = { get method(): string { return 'tasks/newtaskcreated'; } }; +} + +// ------------------------------- < Admin Service Events > ------------------------------------ + +export namespace CreateDatabaseRequest { + export const type: RequestType = { get method(): string { return 'admin/createdatabase'; } }; +} + +export namespace DefaultDatabaseInfoRequest { + export const type: RequestType = { get method(): string { return 'admin/defaultdatabaseinfo'; } }; +} + +export namespace CreateLoginRequest { + export const type: RequestType = { get method(): string { return 'admin/createlogin'; } }; +} + +export namespace GetDatabaseInfoRequest { + export const type: RequestType = { get method(): string { return 'admin/getdatabaseinfo'; } }; +} + +// ------------------------------- < Disaster Recovery Events > ------------------------------------ + +export namespace BackupRequest { + export const type: RequestType = { get method(): string { return 'disasterrecovery/backup'; } }; +} + +export namespace BackupConfigInfoRequest { + export const type: RequestType = { get method(): string { return 'disasterrecovery/backupconfiginfo'; } }; +} + +export namespace RestoreRequest { + export const type: RequestType = { get method(): string { return 'disasterrecovery/restore'; } }; +} + +export namespace RestorePlanRequest { + export const type: RequestType = { get method(): string { return 'disasterrecovery/restoreplan'; } }; +} + +export namespace CancelRestorePlanRequest { + export const type: RequestType = { get method(): string { return 'disasterrecovery/cancelrestoreplan'; } }; +} + +export namespace RestoreConfigInfoRequest { + export const type: RequestType = { get method(): string { return 'disasterrecovery/restoreconfiginfo'; } }; +} + +// ------------------------------- < File Browser Events > ------------------------------------ + +export namespace FileBrowserOpenRequest { + export const type: RequestType = { get method(): string { return 'filebrowser/open'; } }; +} + +export namespace FileBrowserOpenedNotification { + export const type: NotificationType = { get method(): string { return 'filebrowser/opencomplete'; } }; +} + +export namespace FileBrowserExpandRequest { + export const type: RequestType = { get method(): string { return 'filebrowser/expand'; } }; +} + +export namespace FileBrowserExpandedNotification { + export const type: NotificationType = { get method(): string { return 'filebrowser/expandcomplete'; } }; +} + +export namespace FileBrowserValidateRequest { + export const type: RequestType = { get method(): string { return 'filebrowser/validate'; } }; +} + +export namespace FileBrowserValidatedNotification { + export const type: NotificationType = { get method(): string { return 'filebrowser/validatecomplete'; } }; +} + +export namespace FileBrowserCloseRequest { + export const type: RequestType = { get method(): string { return 'filebrowser/close'; } }; +} + + +// ------------------------------- < Profiler Events > ------------------------------------ + +export namespace StartProfilingRequest { + export const type: RequestType = { get method(): string { return 'profiler/start'; } }; +} + +export namespace StopProfilingRequest { + export const type: RequestType = { get method(): string { return 'profiler/stop'; } }; +} +export namespace ProfilerEventsAvailableNotification { + export const type: NotificationType = { get method(): string { return 'profiler/eventsavailable'; } }; +} diff --git a/dataprotocol-node/client/src/protocolCodeLens.ts b/dataprotocol-node/client/src/protocolCodeLens.ts new file mode 100644 index 0000000000..9dbd4a4bf9 --- /dev/null +++ b/dataprotocol-node/client/src/protocolCodeLens.ts @@ -0,0 +1,16 @@ +/* -------------------------------------------------------------------------------------------- + * 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 code from 'vscode'; + +export default class ProtocolCodeLens extends code.CodeLens { + + public data: any; + + constructor(range: code.Range) { + super(range); + } +} \ No newline at end of file diff --git a/dataprotocol-node/client/src/protocolCompletionItem.ts b/dataprotocol-node/client/src/protocolCompletionItem.ts new file mode 100644 index 0000000000..120f7a605b --- /dev/null +++ b/dataprotocol-node/client/src/protocolCompletionItem.ts @@ -0,0 +1,16 @@ +/* -------------------------------------------------------------------------------------------- + * 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 code from 'vscode'; + +export default class ProtocolCompletionItem extends code.CompletionItem { + + public data: any; + + constructor(label: string) { + super(label); + } +} \ No newline at end of file diff --git a/dataprotocol-node/client/src/protocolConverter.ts b/dataprotocol-node/client/src/protocolConverter.ts new file mode 100644 index 0000000000..b3708fd84d --- /dev/null +++ b/dataprotocol-node/client/src/protocolConverter.ts @@ -0,0 +1,769 @@ +/* -------------------------------------------------------------------------------------------- + * 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 code from 'vscode'; +import * as data from 'data'; +import * as ls from 'dataprotocol-languageserver-types'; +import * as is from './utils/is'; +import ProtocolCompletionItem from './protocolCompletionItem'; +import ProtocolCodeLens from './protocolCodeLens'; + +export interface Converter { + + asUri(value: string): code.Uri; + + asDiagnostics(diagnostics: ls.Diagnostic[]): code.Diagnostic[]; + + asDiagnostic(diagnostic: ls.Diagnostic): code.Diagnostic; + + asRange(value: ls.Range): code.Range; + + asPosition(value: ls.Position): code.Position; + + asDiagnosticSeverity(value: number): code.DiagnosticSeverity; + + asHover(hover: ls.Hover): code.Hover; + + asCompletionResult(result: ls.CompletionItem[] | ls.CompletionList): code.CompletionItem[] | code.CompletionList + + asCompletionItem(item: ls.CompletionItem): ProtocolCompletionItem; + + asTextEdit(edit: ls.TextEdit): code.TextEdit; + + asTextEdits(items: ls.TextEdit[]): code.TextEdit[]; + + asSignatureHelp(item: ls.SignatureHelp): code.SignatureHelp; + + asSignatureInformations(items: ls.SignatureInformation[]): code.SignatureInformation[]; + + asSignatureInformation(item: ls.SignatureInformation): code.SignatureInformation; + + asParameterInformations(item: ls.ParameterInformation[]): code.ParameterInformation[]; + + asParameterInformation(item: ls.ParameterInformation): code.ParameterInformation; + + asDefinitionResult(item: ls.Definition): code.Definition; + + asLocation(item: ls.Location): code.Location; + + asReferences(values: ls.Location[]): code.Location[]; + + asDocumentHighlights(values: ls.DocumentHighlight[]): code.DocumentHighlight[]; + + asDocumentHighlight(item: ls.DocumentHighlight): code.DocumentHighlight; + + asDocumentHighlightKind(item: ls.DocumentHighlightKind): code.DocumentHighlightKind; + + asSymbolInformations(values: ls.SymbolInformation[], uri?: code.Uri): code.SymbolInformation[]; + + asSymbolInformation(item: ls.SymbolInformation, uri?: code.Uri): code.SymbolInformation; + + asCommand(item: ls.Command): code.Command; + + asCommands(items: ls.Command[]): code.Command[]; + + asCodeLens(item: ls.CodeLens): code.CodeLens; + + asCodeLenses(items: ls.CodeLens[]): code.CodeLens[]; + + asWorkspaceEdit(item: ls.WorkspaceEdit): code.WorkspaceEdit; + + asDocumentLink(item: ls.DocumentLink): code.DocumentLink; + + asDocumentLinks(items: ls.DocumentLink[]): code.DocumentLink[]; + + asConnectionSummary(params: ls.ConnectionCompleteParams): data.ConnectionInfoSummary; + + asServerCapabilities(params: ls.CapabiltiesDiscoveryResult): data.DataProtocolServerCapabilities; + + asProviderMetadata(params: ls.MetadataQueryResult): data.ProviderMetadata; + + asScriptingResult(params: ls.ScriptingResult): data.ScriptingResult; + + asObjectExplorerSession(params: ls.SessionCreatedParameters): data.ObjectExplorerSession; + + asObjectExplorerCreateSessionResponse(params: ls.CreateSessionResponse): data.ObjectExplorerSessionResponse; + + asObjectExplorerNodeInfo(params: ls.ExpandResponse): data.ObjectExplorerExpandInfo; + + asObjectExplorerCloseSessionResponse(params: ls.CloseSessionResponse): data.ObjectExplorerCloseSessionResponse; + + asListTasksResponse(response: ls.ListTasksResponse): data.ListTasksResponse; + + asTaskInfo(params: ls.TaskInfo): data.TaskInfo; + + asRestorePlanResponse(params: ls.RestorePlanResponse): data.RestorePlanResponse; + + asRestoreResponse(params: ls.RestoreResponse): data.RestoreResponse; + + asRestoreConfigInfo(params: ls.RestoreConfigInfoResponse): data.RestoreConfigInfo; +} + +export interface URIConverter { + (value: string): code.Uri; +} + +export function createConverter(uriConverter?: URIConverter): Converter { + + const nullConverter = (value: string) => code.Uri.parse(value); + + const _uriConverter: URIConverter = uriConverter || nullConverter; + + function asUri(value: string): code.Uri { + return _uriConverter(value); + } + + function asDiagnostics(diagnostics: ls.Diagnostic[]): code.Diagnostic[] { + return diagnostics.map(asDiagnostic); + } + + function asDiagnostic(diagnostic: ls.Diagnostic): code.Diagnostic { + let result = new code.Diagnostic(asRange(diagnostic.range), diagnostic.message, asDiagnosticSeverity(diagnostic.severity)); + if (is.defined(diagnostic.code)) { + result.code = diagnostic.code; + } + if (is.defined(diagnostic.source)) { + result.source = diagnostic.source; + } + return result; + } + + function asRange(value: ls.Range): code.Range { + if (is.undefined(value)) { + return undefined; + } else if (is.nil(value)) { + return null; + } + return new code.Range(asPosition(value.start), asPosition(value.end)); + } + + function asPosition(value: ls.Position): code.Position { + if (is.undefined(value)) { + return undefined; + } else if (is.nil(value)) { + return null; + } + return new code.Position(value.line, value.character); + } + + function asDiagnosticSeverity(value: number): code.DiagnosticSeverity { + if (is.undefined(value) || is.nil(value)) { + return code.DiagnosticSeverity.Error; + } + switch (value) { + case ls.DiagnosticSeverity.Error: + return code.DiagnosticSeverity.Error; + case ls.DiagnosticSeverity.Warning: + return code.DiagnosticSeverity.Warning; + case ls.DiagnosticSeverity.Information: + return code.DiagnosticSeverity.Information; + case ls.DiagnosticSeverity.Hint: + return code.DiagnosticSeverity.Hint; + } + return code.DiagnosticSeverity.Error; + } + + function asHover(hover: ls.Hover): code.Hover { + if (is.undefined(hover)) { + return undefined; + } + if (is.nil(hover)) { + return null; + } + if (is.nil(hover.contents) || is.undefined(hover.contents)) { + // Contents must be defined or hover will throw + return null; + } + return new code.Hover(hover.contents, is.defined(hover.range) ? asRange(hover.range) : undefined); + } + + function asCompletionResult(result: ls.CompletionItem[] | ls.CompletionList): code.CompletionItem[] | code.CompletionList { + if (is.undefined(result)) { + return undefined; + } else if (is.nil(result)) { + return null; + } + if (Array.isArray(result)) { + let items = result; + return items.map(asCompletionItem); + } + let list = result; + return new code.CompletionList(list.items.map(asCompletionItem), list.isIncomplete); + } + + function set(value: T, func: () => void): void { + if (is.defined(value)) { + func(); + } + } + + function asCompletionItem(item: ls.CompletionItem): ProtocolCompletionItem { + let result = new ProtocolCompletionItem(item.label); + set(item.detail, () => result.detail = item.detail); + set(item.documentation, () => result.documentation = item.documentation); + set(item.filterText, () => result.filterText = item.filterText); + set(item.insertText, () => result.insertText = item.insertText); + // Protocol item kind is 1 based, codes item kind is zero based. + set(item.kind, () => result.kind = item.kind - 1); + set(item.sortText, () => result.sortText = item.sortText); + set(item.textEdit, () => result.textEdit = asTextEdit(item.textEdit)); + set(item.additionalTextEdits, () => result.additionalTextEdits = asTextEdits(item.additionalTextEdits)); + set(item.command, () => result.command = asCommand(item.command)); + set(item.data, () => result.data = item.data); + return result; + } + + function asTextEdit(edit: ls.TextEdit): code.TextEdit { + return new code.TextEdit(asRange(edit.range), edit.newText); + } + + function asTextEdits(items: ls.TextEdit[]): code.TextEdit[] { + if (is.undefined(items)) { + return undefined; + } else if (is.nil(items)) { + return null; + } + return items.map(asTextEdit); + } + + function asSignatureHelp(item: ls.SignatureHelp): code.SignatureHelp { + if (is.undefined(item)) { + return undefined; + } else if (is.nil(item)) { + return null; + } + let result = new code.SignatureHelp(); + set(item.activeParameter, () => result.activeParameter = item.activeParameter); + set(item.activeSignature, () => result.activeSignature = item.activeSignature); + set(item.signatures, () => result.signatures = asSignatureInformations(item.signatures)); + return result; + } + + function asSignatureInformations(items: ls.SignatureInformation[]): code.SignatureInformation[] { + return items ? items.map(asSignatureInformation) : undefined; + } + + function asSignatureInformation(item: ls.SignatureInformation): code.SignatureInformation { + if (!item) { + return undefined; + } + let result = new code.SignatureInformation(item.label); + set(item.documentation, () => result.documentation = item.documentation); + set(item.parameters, () => result.parameters = asParameterInformations(item.parameters)); + return result; + } + + function asParameterInformations(item: ls.ParameterInformation[]): code.ParameterInformation[] { + return item.map(asParameterInformation); + } + + function asParameterInformation(item: ls.ParameterInformation): code.ParameterInformation { + let result = new code.ParameterInformation(item.label); + set(item.documentation, () => result.documentation = item.documentation); + return result; + } + + function asDefinitionResult(item: ls.Definition): code.Definition { + if (is.undefined(item)) { + return undefined; + } else if (is.nil(item)) { + return null; + } + if (is.array(item)) { + return item.map(asLocation); + } else { + return asLocation(item); + } + } + + function asLocation(item: ls.Location): code.Location { + if (is.undefined(item)) { + return undefined; + } + if (is.nil(item)) { + return null; + } + return new code.Location(_uriConverter(item.uri), asRange(item.range)); + } + + function asReferences(values: ls.Location[]): code.Location[] { + if (is.undefined(values)) { + return undefined; + } + if (is.nil(values)) { + return null; + } + return values.map(asLocation); + } + + function asDocumentHighlights(values: ls.DocumentHighlight[]): code.DocumentHighlight[] { + if (is.undefined(values)) { + return undefined; + } + if (is.nil(values)) { + return null; + } + return values.map(asDocumentHighlight); + } + + function asDocumentHighlight(item: ls.DocumentHighlight): code.DocumentHighlight { + let result = new code.DocumentHighlight(asRange(item.range)); + set(item.kind, () => result.kind = asDocumentHighlightKind(item.kind)); + return result; + } + + function asDocumentHighlightKind(item: ls.DocumentHighlightKind): code.DocumentHighlightKind { + switch (item) { + case ls.DocumentHighlightKind.Text: + return code.DocumentHighlightKind.Text; + case ls.DocumentHighlightKind.Read: + return code.DocumentHighlightKind.Read; + case ls.DocumentHighlightKind.Write: + return code.DocumentHighlightKind.Write; + } + return code.DocumentHighlightKind.Text; + } + + function asSymbolInformations(values: ls.SymbolInformation[], uri?: code.Uri): code.SymbolInformation[] { + if (is.undefined(values)) { + return undefined; + } + if (is.nil(values)) { + return null; + } + return values.map(information => asSymbolInformation(information, uri)); + } + + function asSymbolInformation(item: ls.SymbolInformation, uri?: code.Uri): code.SymbolInformation { + // Symbol kind is one based in the protocol and zero based in code. + let result = new code.SymbolInformation( + item.name, item.kind - 1, + asRange(item.location.range), + item.location.uri ? _uriConverter(item.location.uri) : uri); + set(item.containerName, () => result.containerName = item.containerName); + return result; + } + + function asCommand(item: ls.Command): code.Command { + let result: code.Command = { title: item.title, command: item.command }; + set(item.arguments, () => result.arguments = item.arguments); + return result; + } + + function asCommands(items: ls.Command[]): code.Command[] { + if (is.undefined(items)) { + return undefined; + } + if (is.nil(items)) { + return null; + } + return items.map(asCommand); + } + + function asCodeLens(item: ls.CodeLens): code.CodeLens { + let result: ProtocolCodeLens = new ProtocolCodeLens(asRange(item.range)); + if (is.defined(item.command)) { result.command = asCommand(item.command); } + if (is.defined(item.data)) { result.data = item.data; } + return result; + } + + function asCodeLenses(items: ls.CodeLens[]): code.CodeLens[] { + if (is.undefined(items)) { + return undefined; + } + if (is.nil(items)) { + return null; + } + return items.map(asCodeLens); + } + + function asWorkspaceEdit(item: ls.WorkspaceEdit): code.WorkspaceEdit { + if (is.undefined(item)) { + return undefined; + } + if (is.nil(item)) { + return null; + } + let result = new code.WorkspaceEdit(); + let keys = Object.keys(item.changes); + keys.forEach(key => result.set(_uriConverter(key), asTextEdits(item.changes[key]))); + return result; + } + + function asDocumentLink(item: ls.DocumentLink): code.DocumentLink { + let range = asRange(item.range); + let target = is.defined(item.target) && asUri(item.target); + return new code.DocumentLink(range, target); + } + + function asDocumentLinks(items: ls.DocumentLink[]): code.DocumentLink[] { + if (is.undefined(items)) { + return undefined; + } + if (is.nil(items)) { + return null; + } + return items.map(asDocumentLink); + } + + function asConnectionSummary(params: ls.ConnectionCompleteParams): data.ConnectionInfoSummary { + let connSummary: data.ConnectionInfoSummary = { + ownerUri: params.ownerUri, + connectionId: params.connectionId, + messages: params.messages, + errorMessage: params.errorMessage, + errorNumber: params.errorNumber, + serverInfo: params.serverInfo, + connectionSummary: params.connectionSummary + }; + return connSummary; + } + + function asServiceOptionType(val: string): data.ServiceOptionType { + if (val === 'string') { + return data.ServiceOptionType.string; + } else if (val === 'multistring') { + return data.ServiceOptionType.multistring; + } else if (val === 'password') { + return data.ServiceOptionType.password; + } else if (val === 'number') { + return data.ServiceOptionType.number; + } else if (val === 'boolean') { + return data.ServiceOptionType.boolean; + } else if (val === 'category') { + return data.ServiceOptionType.category; + } else if (val === 'object') { + return data.ServiceOptionType.object; + } + + // assume string for unknown value types + return data.ServiceOptionType.string; + } + + function asServerCapabilities(result: ls.CapabiltiesDiscoveryResult): data.DataProtocolServerCapabilities { + let capabilities: data.DataProtocolServerCapabilities = { + protocolVersion: result.capabilities.protocolVersion, + providerName: result.capabilities.providerName, + providerDisplayName: result.capabilities.providerDisplayName, + connectionProvider: undefined, + adminServicesProvider: undefined, + features: [] + }; + + if (result.capabilities.adminServicesProvider) { + capabilities.adminServicesProvider = { + databaseInfoOptions: new Array(), + databaseFileInfoOptions: new Array(), + fileGroupInfoOptions: new Array() + }; + + if (result.capabilities.adminServicesProvider.databaseInfoOptions + && result.capabilities.adminServicesProvider.databaseInfoOptions.length > 0) { + for (let i = 0; i < result.capabilities.adminServicesProvider.databaseInfoOptions.length; ++i) { + let srcOption: ls.ServiceOption = result.capabilities.adminServicesProvider.databaseInfoOptions[i]; + let descOption: data.ServiceOption = buildServiceOption(srcOption); + capabilities.adminServicesProvider.databaseInfoOptions.push(descOption); + } + } + + if (result.capabilities.adminServicesProvider.databaseFileInfoOptions + && result.capabilities.adminServicesProvider.databaseFileInfoOptions.length > 0) { + for (let i = 0; i < result.capabilities.adminServicesProvider.databaseFileInfoOptions.length; ++i) { + let srcOption: ls.ServiceOption = result.capabilities.adminServicesProvider.databaseFileInfoOptions[i]; + let descOption: data.ServiceOption = buildServiceOption(srcOption); + capabilities.adminServicesProvider.databaseFileInfoOptions.push(descOption); + } + } + + if (result.capabilities.adminServicesProvider.fileGroupInfoOptions + && result.capabilities.adminServicesProvider.fileGroupInfoOptions.length > 0) { + for (let i = 0; i < result.capabilities.adminServicesProvider.fileGroupInfoOptions.length; ++i) { + let srcOption: ls.ServiceOption = result.capabilities.adminServicesProvider.fileGroupInfoOptions[i]; + let descOption: data.ServiceOption = buildServiceOption(srcOption); + capabilities.adminServicesProvider.fileGroupInfoOptions.push(descOption); + } + } + } + + if (result.capabilities.connectionProvider + && result.capabilities.connectionProvider.options + && result.capabilities.connectionProvider.options.length > 0) { + capabilities.connectionProvider = { + options: new Array() + }; + for (let i = 0; i < result.capabilities.connectionProvider.options.length; ++i) { + let srcOption: ls.ConnectionOption = result.capabilities.connectionProvider.options[i]; + let descOption: data.ConnectionOption = { + name: srcOption.name, + displayName: srcOption.displayName ? srcOption.displayName : srcOption.name, + description: srcOption.description, + groupName: srcOption.groupName, + defaultValue: srcOption.defaultValue, + categoryValues: srcOption.categoryValues, + isIdentity: srcOption.isIdentity, + isRequired: srcOption.isRequired, + valueType: asServiceOptionType(srcOption.valueType), + specialValueType: undefined + }; + + if (srcOption.specialValueType === 'serverName') { + descOption.specialValueType = data.ConnectionOptionSpecialType.serverName; + } else if (srcOption.specialValueType === 'databaseName') { + descOption.specialValueType = data.ConnectionOptionSpecialType.databaseName; + } else if (srcOption.specialValueType === 'authType') { + descOption.specialValueType = data.ConnectionOptionSpecialType.authType; + } else if (srcOption.specialValueType === 'userName') { + descOption.specialValueType = data.ConnectionOptionSpecialType.userName; + } else if (srcOption.specialValueType === 'password') { + descOption.specialValueType = data.ConnectionOptionSpecialType.password; + } else if (srcOption.specialValueType === 'appName') { + descOption.specialValueType = data.ConnectionOptionSpecialType.appName; + } + + capabilities.connectionProvider.options.push(descOption); + } + } + + if (result.capabilities.features + && result.capabilities.features.length > 0) { + result.capabilities.features.forEach(feature => { + let descFeature: data.FeatureMetadataProvider = { + enabled: feature.enabled, + featureName: feature.featureName, + optionsMetadata: [] + }; + capabilities.features.push(descFeature); + if (feature.optionsMetadata) { + feature.optionsMetadata.forEach(srcOption => { + descFeature.optionsMetadata.push(buildServiceOption(srcOption)); + }); + } + }); + } + + return capabilities; + } + + function buildServiceOption(srcOption: ls.ServiceOption): data.ServiceOption { + return { + name: srcOption.name, + displayName: srcOption.displayName ? srcOption.displayName : srcOption.name, + description: srcOption.description, + groupName: srcOption.groupName, + defaultValue: srcOption.defaultValue, + categoryValues: srcOption.categoryValues, + isRequired: srcOption.isRequired, + isArray: srcOption.isArray, + objectType: srcOption.objectType, + valueType: asServiceOptionType(srcOption.valueType), + }; + } + + function asProviderMetadata(params: ls.MetadataQueryResult): data.ProviderMetadata { + let objectMetadata: data.ObjectMetadata[] = []; + + if (!params.metadata || !params.metadata.length) { + return { + objectMetadata: objectMetadata + }; + } + + for (let i = 0; i < params.metadata.length; ++i) { + let metadata: ls.ObjectMetadata = params.metadata[i]; + + let metadataTypeName: string; + if (metadata.metadataTypeName) { + // Read from the provider since it's defined + metadataTypeName = metadata.metadataTypeName; + } else if (metadata.metadataType === ls.MetadataType.View) { + metadataTypeName = 'View'; + } else if (metadata.metadataType === ls.MetadataType.SProc) { + metadataTypeName = 'StoredProcedure'; + } else if (metadata.metadataType === ls.MetadataType.Function) { + metadataTypeName = 'Function'; + } else { + metadataTypeName = 'Table'; + } + + objectMetadata.push({ + metadataTypeName: metadataTypeName, + metadataType: metadata.metadataType, + name: metadata.name, + schema: metadata.schema, + urn: metadata.urn + }); + } + + return { + objectMetadata: objectMetadata + }; + } + + function asObjectExplorerSession(params: ls.SessionCreatedParameters): data.ObjectExplorerSession { + return { + success: params.success, + sessionId: params.sessionId, + rootNode: params.rootNode, + errorMessage: params.errorMessage + }; + } + + function asObjectExplorerCreateSessionResponse(params: ls.CreateSessionResponse): data.ObjectExplorerSessionResponse { + return { + sessionId: params.sessionId + }; + } + + function asObjectExplorerNodeInfo(params: ls.ExpandResponse): data.ObjectExplorerExpandInfo { + return { + sessionId: params.sessionId, + nodes: params.nodes, + errorMessage: params.errorMessage, + nodePath: params.nodePath + }; + } + + function asObjectExplorerCloseSessionResponse(params: ls.CloseSessionResponse): data.ObjectExplorerCloseSessionResponse { + return { + sessionId: params.sessionId, + success: params.success + }; + } + + function asScriptingResult(params: ls.ScriptingResult): data.ScriptingResult { + return { + operationId: params.operationId, + script: params.script + }; + } + + function asListTasksResponse(response: ls.ListTasksResponse): data.ListTasksResponse { + return { + tasks: response.tasks + }; + } + + function asTaskInfo(params: ls.TaskInfo): data.TaskInfo { + return { + taskId: params.taskId, + status: params.status, + taskExecutionMode: params.taskExecutionMode, + serverName: params.serverName, + name: params.name, + databaseName: params.databaseName, + description: params.description, + providerName: params.providerName, + isCancelable: params.isCancelable, + }; + } + + function asRestorePlanResponse(params: ls.RestorePlanResponse): data.RestorePlanResponse { + return { + backupSetsToRestore: params.backupSetsToRestore, + canRestore: params.canRestore, + databaseNamesFromBackupSets: params.databaseNamesFromBackupSets, + dbFiles: params.dbFiles, + errorMessage: params.errorMessage, + planDetails: params.planDetails, + sessionId: params.sessionId + }; + } + + function asRestoreResponse(params: ls.RestoreResponse): data.RestoreResponse { + return { + result: params.result, + errorMessage: params.errorMessage, + taskId: params.taskId + }; + } + + function asRestoreConfigInfo(params: ls.RestoreConfigInfoResponse): data.RestoreConfigInfo { + return { + configInfo: params.configInfo + }; + } + + return { + asUri, + asDiagnostics, + asDiagnostic, + asRange, + asPosition, + asDiagnosticSeverity, + asHover, + asCompletionResult, + asCompletionItem, + asTextEdit, + asTextEdits, + asSignatureHelp, + asSignatureInformations, + asSignatureInformation, + asParameterInformations, + asParameterInformation, + asDefinitionResult, + asLocation, + asReferences, + asDocumentHighlights, + asDocumentHighlight, + asDocumentHighlightKind, + asSymbolInformations, + asSymbolInformation, + asCommand, + asCommands, + asCodeLens, + asCodeLenses, + asWorkspaceEdit, + asDocumentLink, + asDocumentLinks, + asConnectionSummary, + asServerCapabilities, + asProviderMetadata, + asScriptingResult, + asObjectExplorerSession, + asObjectExplorerCreateSessionResponse, + asObjectExplorerNodeInfo, + asObjectExplorerCloseSessionResponse, + asListTasksResponse, + asTaskInfo, + asRestorePlanResponse, + asRestoreResponse, + asRestoreConfigInfo + }; +} + +// This for backward compatibility since we exported the converter functions as API. +const defaultConverter = createConverter(); + +export const asDiagnostics: (diagnostics: ls.Diagnostic[]) => code.Diagnostic[] = defaultConverter.asDiagnostics; +export const asDiagnostic: (diagnostic: ls.Diagnostic) => code.Diagnostic = defaultConverter.asDiagnostic; +export const asRange: (value: ls.Range) => code.Range = defaultConverter.asRange; +export const asPosition: (value: ls.Position) => code.Position = defaultConverter.asPosition; +export const asDiagnosticSeverity: (value: number) => code.DiagnosticSeverity = defaultConverter.asDiagnosticSeverity; +export const asHover: (hover: ls.Hover) => code.Hover = defaultConverter.asHover; +export const asCompletionResult: (result: ls.CompletionItem[] | ls.CompletionList) => code.CompletionItem[] | code.CompletionList = defaultConverter.asCompletionResult; +export const asCompletionItem: (item: ls.CompletionItem) => ProtocolCompletionItem = defaultConverter.asCompletionItem; +export const asTextEdit: (edit: ls.TextEdit) => code.TextEdit = defaultConverter.asTextEdit; +export const asTextEdits: (items: ls.TextEdit[]) => code.TextEdit[] = defaultConverter.asTextEdits; +export const asSignatureHelp: (item: ls.SignatureHelp) => code.SignatureHelp = defaultConverter.asSignatureHelp; +export const asSignatureInformations: (items: ls.SignatureInformation[]) => code.SignatureInformation[] = defaultConverter.asSignatureInformations; +export const asSignatureInformation: (item: ls.SignatureInformation) => code.SignatureInformation = defaultConverter.asSignatureInformation; +export const asParameterInformations: (item: ls.ParameterInformation[]) => code.ParameterInformation[] = defaultConverter.asParameterInformations; +export const asParameterInformation: (item: ls.ParameterInformation) => code.ParameterInformation = defaultConverter.asParameterInformation; +export const asDefinitionResult: (item: ls.Definition) => code.Definition = defaultConverter.asDefinitionResult; +export const asLocation: (item: ls.Location) => code.Location = defaultConverter.asLocation; +export const asReferences: (values: ls.Location[]) => code.Location[] = defaultConverter.asReferences; +export const asDocumentHighlights: (values: ls.DocumentHighlight[]) => code.DocumentHighlight[] = defaultConverter.asDocumentHighlights; +export const asDocumentHighlight: (item: ls.DocumentHighlight) => code.DocumentHighlight = defaultConverter.asDocumentHighlight; +export const asDocumentHighlightKind: (item: ls.DocumentHighlightKind) => code.DocumentHighlightKind = defaultConverter.asDocumentHighlightKind; +export const asSymbolInformations: (values: ls.SymbolInformation[], uri?: code.Uri) => code.SymbolInformation[] = defaultConverter.asSymbolInformations; +export const asSymbolInformation: (item: ls.SymbolInformation, uri?: code.Uri) => code.SymbolInformation = defaultConverter.asSymbolInformation; +export const asCommand: (item: ls.Command) => code.Command = defaultConverter.asCommand; +export const asCommands: (items: ls.Command[]) => code.Command[] = defaultConverter.asCommands; +export const asCodeLens: (item: ls.CodeLens) => code.CodeLens = defaultConverter.asCodeLens; +export const asCodeLenses: (items: ls.CodeLens[]) => code.CodeLens[] = defaultConverter.asCodeLenses; +export const asWorkspaceEdit: (item: ls.WorkspaceEdit) => code.WorkspaceEdit = defaultConverter.asWorkspaceEdit; +export const asDocumentLink: (item: ls.DocumentLink) => code.DocumentLink = defaultConverter.asDocumentLink; +export const asDocumentLinks: (item: ls.DocumentLink[]) => code.DocumentLink[] = defaultConverter.asDocumentLinks; \ No newline at end of file diff --git a/dataprotocol-node/client/src/tsconfig.json b/dataprotocol-node/client/src/tsconfig.json new file mode 100644 index 0000000000..79b9f99c48 --- /dev/null +++ b/dataprotocol-node/client/src/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": false, + "inlineSources": false, + "declaration": true, + "stripInternal": true, + "outDir": "../lib" + } +} \ No newline at end of file diff --git a/dataprotocol-node/client/src/typings/es6-promise/index.d.ts b/dataprotocol-node/client/src/typings/es6-promise/index.d.ts new file mode 100644 index 0000000000..4af46edfa6 --- /dev/null +++ b/dataprotocol-node/client/src/typings/es6-promise/index.d.ts @@ -0,0 +1,84 @@ +// Type definitions for es6-promise +// Project: https://github.com/jakearchibald/ES6-Promise +// Definitions by: François de Campredon , vvakame +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +interface Thenable { + then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => U | Thenable): Thenable; + then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => void): Thenable; +} + +declare class Promise implements Thenable { + /** + * If you call resolve in the body of the callback passed to the constructor, + * your promise is fulfilled with result object passed to resolve. + * If you call reject your promise is rejected with the object passed to reject. + * For consistency and debugging (eg stack traces), obj should be an instanceof Error. + * Any errors thrown in the constructor callback will be implicitly passed to reject(). + */ + constructor(callback: (resolve : (value?: T | Thenable) => void, reject: (error?: any) => void) => void); + + /** + * onFulfilled is called when/if "promise" resolves. onRejected is called when/if "promise" rejects. + * Both are optional, if either/both are omitted the next onFulfilled/onRejected in the chain is called. + * Both callbacks have a single parameter , the fulfillment value or rejection reason. + * "then" returns a new promise equivalent to the value you return from onFulfilled/onRejected after being passed through Promise.resolve. + * If an error is thrown in the callback, the returned promise rejects with that error. + * + * @param onFulfilled called when/if "promise" resolves + * @param onRejected called when/if "promise" rejects + */ + then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => U | Thenable): Promise; + then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => void): Promise; + + /** + * Sugar for promise.then(undefined, onRejected) + * + * @param onRejected called when/if "promise" rejects + */ + catch(onRejected?: (error: any) => U | Thenable): Promise; +} + +declare namespace Promise { + /** + * Make a new promise from the thenable. + * A thenable is promise-like in as far as it has a "then" method. + */ + function resolve(value?: T | Thenable): Promise; + + /** + * Make a promise that rejects to obj. For consistency and debugging (eg stack traces), obj should be an instanceof Error + */ + function reject(error: any): Promise; + function reject(error: T): Promise; + + /** + * Make a promise that fulfills when every item in the array fulfills, and rejects if (and when) any item rejects. + * the array passed to all can be a mixture of promise-like objects and other objects. + * The fulfillment value is an array (in order) of fulfillment values. The rejection value is the first rejection value. + */ + function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable , T5 | Thenable, T6 | Thenable, T7 | Thenable, T8 | Thenable, T9 | Thenable, T10 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>; + function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable , T5 | Thenable, T6 | Thenable, T7 | Thenable, T8 | Thenable, T9 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>; + function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable , T5 | Thenable, T6 | Thenable, T7 | Thenable, T8 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>; + function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable , T5 | Thenable, T6 | Thenable, T7 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6, T7]>; + function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable , T5 | Thenable, T6 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6]>; + function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable , T5 | Thenable]): Promise<[T1, T2, T3, T4, T5]>; + function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable ]): Promise<[T1, T2, T3, T4]>; + function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable]): Promise<[T1, T2, T3]>; + function all(values: [T1 | Thenable, T2 | Thenable]): Promise<[T1, T2]>; + function all(values: (T | Thenable)[]): Promise; + + /** + * Make a Promise that fulfills when any item fulfills, and rejects if any item rejects. + */ + function race(promises: (T | Thenable)[]): Promise; +} + +declare module 'es6-promise' { + var foo: typeof Promise; // Temp variable to reference Promise in local context + namespace rsvp { + export var Promise: typeof foo; + export function polyfill(): void; + } + export = rsvp; +} diff --git a/dataprotocol-node/client/src/typings/es6-promise/tsconfig.json b/dataprotocol-node/client/src/typings/es6-promise/tsconfig.json new file mode 100644 index 0000000000..bc50e7c4fb --- /dev/null +++ b/dataprotocol-node/client/src/typings/es6-promise/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es5", + "dom" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": false, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "es6-promise-tests.ts" + ] +} \ No newline at end of file diff --git a/dataprotocol-node/client/src/typings/ref.d.ts b/dataprotocol-node/client/src/typings/ref.d.ts new file mode 100644 index 0000000000..e21045c0c8 --- /dev/null +++ b/dataprotocol-node/client/src/typings/ref.d.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/// +/// diff --git a/dataprotocol-node/client/src/utils/async.ts b/dataprotocol-node/client/src/utils/async.ts new file mode 100644 index 0000000000..691901c20d --- /dev/null +++ b/dataprotocol-node/client/src/utils/async.ts @@ -0,0 +1,82 @@ +/* -------------------------------------------------------------------------------------------- + * 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 ITask { + (): T; +} + +export class Delayer { + + public defaultDelay: number; + private timeout: NodeJS.Timer; + private completionPromise: Promise; + private onSuccess: (value?: T | Thenable) => void; + private task: ITask; + + constructor(defaultDelay: number) { + this.defaultDelay = defaultDelay; + this.timeout = null; + this.completionPromise = null; + this.onSuccess = null; + this.task = null; + } + + public trigger(task: ITask, delay: number = this.defaultDelay): Promise { + this.task = task; + if (delay >= 0) { + this.cancelTimeout(); + } + + if (!this.completionPromise) { + this.completionPromise = new Promise((resolve) => { + this.onSuccess = resolve + }).then(() => { + this.completionPromise = null; + this.onSuccess = null; + var result = this.task(); + this.task = null; + return result; + }); + } + + if (delay >= 0 || this.timeout === null) { + this.timeout = setTimeout(() => { + this.timeout = null; + this.onSuccess(null); + }, delay >= 0 ? delay : this.defaultDelay); + } + + return this.completionPromise; + } + + public forceDelivery(): T { + if (!this.completionPromise) { + return null; + } + this.cancelTimeout(); + let result: T = this.task(); + this.completionPromise = null; + this.onSuccess = null; + this.task = null; + return result; + } + + public isTriggered(): boolean { + return this.timeout !== null; + } + + public cancel(): void { + this.cancelTimeout(); + this.completionPromise = null; + } + + private cancelTimeout(): void { + if (this.timeout !== null) { + clearTimeout(this.timeout); + this.timeout = null; + } + } +} \ No newline at end of file diff --git a/dataprotocol-node/client/src/utils/electron.ts b/dataprotocol-node/client/src/utils/electron.ts new file mode 100644 index 0000000000..f55c1c344f --- /dev/null +++ b/dataprotocol-node/client/src/utils/electron.ts @@ -0,0 +1,123 @@ +/* -------------------------------------------------------------------------------------------- + * 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 path = require('path'); +import os = require('os'); +import net = require('net'); +import cp = require('child_process'); + +export interface IForkOptions { + cwd?: string; + env?: any; + encoding?: string; + execArgv?: string[]; +} + +function makeRandomHexString(length: number): string { + let chars = ['0', '1', '2', '3', '4', '5', '6', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + let result = ''; + for (let i = 0; i < length; i++) { + let idx = Math.floor(chars.length * Math.random()); + result += chars[idx]; + } + return result; +} + +function generatePipeName(): string { + var randomName = 'vscode-' + makeRandomHexString(40); + if (process.platform === 'win32') { + return '\\\\.\\pipe\\' + randomName + '-sock'; + } + + // Mac/Unix: use socket file + return path.join(os.tmpdir(), randomName + '.sock'); +} + +function generatePatchedEnv(env: any, stdInPipeName: string, stdOutPipeName: string): any { + // Set the two unique pipe names and the electron flag as process env + + var newEnv: any = {}; + for (var key in env) { + newEnv[key] = env[key]; + } + + newEnv['STDIN_PIPE_NAME'] = stdInPipeName; + newEnv['STDOUT_PIPE_NAME'] = stdOutPipeName; + newEnv['ATOM_SHELL_INTERNAL_RUN_AS_NODE'] = '1'; + + return newEnv; +} + +export function fork(modulePath: string, args: string[], options: IForkOptions, callback: (error: any, cp: cp.ChildProcess) => void): void { + + var callbackCalled = false; + var resolve = (result: cp.ChildProcess) => { + if (callbackCalled) { + return; + } + callbackCalled = true; + callback(null, result); + }; + var reject = (err: any) => { + if (callbackCalled) { + return; + } + callbackCalled = true; + callback(err, null); + }; + + // Generate two unique pipe names + var stdInPipeName = generatePipeName(); + var stdOutPipeName = generatePipeName(); + + var newEnv = generatePatchedEnv(options.env || process.env, stdInPipeName, stdOutPipeName); + + var childProcess: cp.ChildProcess; + + // Begin listening to stdout pipe + var server = net.createServer((stream) => { + // The child process will write exactly one chunk with content `ready` when it has installed a listener to the stdin pipe + + stream.once('data', (chunk: Buffer) => { + // The child process is sending me the `ready` chunk, time to connect to the stdin pipe + childProcess.stdin = net.connect(stdInPipeName); + + // From now on the childProcess.stdout is available for reading + childProcess.stdout = stream; + + resolve(childProcess); + }); + }); + server.listen(stdOutPipeName); + + var serverClosed = false; + var closeServer = () => { + if (serverClosed) { + return; + } + serverClosed = true; + server.close(); + } + + // Create the process + let bootstrapperPath = path.join(__dirname, 'electronForkStart'); + childProcess = cp.fork(bootstrapperPath, [modulePath].concat(args), { + silent: true, + cwd: options.cwd, + env: newEnv, + execArgv: options.execArgv + }); + + childProcess.once('error', (err: Error) => { + closeServer(); + reject(err); + }); + + childProcess.once('exit', (err: Error) => { + closeServer(); + reject(err); + }); +} diff --git a/dataprotocol-node/client/src/utils/electronForkStart.ts b/dataprotocol-node/client/src/utils/electronForkStart.ts new file mode 100644 index 0000000000..53c7c6e2c4 --- /dev/null +++ b/dataprotocol-node/client/src/utils/electronForkStart.ts @@ -0,0 +1,183 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ +var net = require('net'), + fs = require('fs'), + stream = require('stream'), + util = require('util'); + +var ENABLE_LOGGING = false; + +var log = (function () { + if (!ENABLE_LOGGING) { + return function () { }; + } + var isFirst = true; + var LOG_LOCATION = 'C:\\stdFork.log'; + return function log(str) { + if (isFirst) { + isFirst = false; + fs.writeFileSync(LOG_LOCATION, str + '\n'); + return; + } + fs.appendFileSync(LOG_LOCATION, str + '\n'); + } +})(); + +var stdInPipeName = process.env['STDIN_PIPE_NAME']; +var stdOutPipeName = process.env['STDOUT_PIPE_NAME']; + +log('STDIN_PIPE_NAME: ' + stdInPipeName); +log('STDOUT_PIPE_NAME: ' + stdOutPipeName); +log('ATOM_SHELL_INTERNAL_RUN_AS_NODE: ' + process.env['ATOM_SHELL_INTERNAL_RUN_AS_NODE']); + +// stdout redirection to named pipe +(function () { + log('Beginning stdout redirection...'); + + // Create a writing stream to the stdout pipe + var stdOutStream = net.connect(stdOutPipeName); + + // unref stdOutStream to behave like a normal standard out + stdOutStream.unref(); + + // handle process.stdout + (process).__defineGetter__('stdout', function () { return stdOutStream; }); + + // handle process.stderr + (process).__defineGetter__('stderr', function () { return stdOutStream; }); + + var fsWriteSyncString = function (fd, str, position, encoding) { + // fs.writeSync(fd, string[, position[, encoding]]); + var buf = new Buffer(str, encoding || 'utf8'); + return fsWriteSyncBuffer(fd, buf, 0, buf.length); + }; + + var fsWriteSyncBuffer = function (fd, buffer, off, len) { + off = Math.abs(off | 0); + len = Math.abs(len | 0); + + // fs.writeSync(fd, buffer, offset, length[, position]); + var buffer_length = buffer.length; + + if (off > buffer_length) { + throw new Error('offset out of bounds'); + } + if (len > buffer_length) { + throw new Error('length out of bounds'); + } + if (((off + len) | 0) < off) { + throw new Error('off + len overflow'); + } + if (buffer_length - off < len) { + // Asking for more than is left over in the buffer + throw new Error('off + len > buffer.length'); + } + + var slicedBuffer = buffer; + if (off !== 0 || len !== buffer_length) { + slicedBuffer = buffer.slice(off, off + len); + } + + stdOutStream.write(slicedBuffer); + return slicedBuffer.length; + }; + + // handle fs.writeSync(1, ...) + var originalWriteSync = fs.writeSync; + fs.writeSync = function (fd, data, position, encoding) { + if (fd !== 1) { + return originalWriteSync.apply(fs, arguments); + } + // usage: + // fs.writeSync(fd, buffer, offset, length[, position]); + // OR + // fs.writeSync(fd, string[, position[, encoding]]); + + if (data instanceof Buffer) { + return fsWriteSyncBuffer.apply(null, arguments); + } + + // For compatibility reasons with fs.writeSync, writing null will write "null", etc + if (typeof data !== 'string') { + data += ''; + } + + return fsWriteSyncString.apply(null, arguments); + }; + + log('Finished defining process.stdout, process.stderr and fs.writeSync'); +})(); + +// stdin redirection to named pipe +(function () { + + // Begin listening to stdin pipe + var server = net.createServer(function (stream) { + // Stop accepting new connections, keep the existing one alive + server.close(); + + log('Parent process has connected to my stdin. All should be good now.'); + + // handle process.stdin + (process).__defineGetter__('stdin', function () { + return stream; + }); + + // Remove myself from process.argv + process.argv.splice(1, 1); + + // Load the actual program + var program = process.argv[1]; + log('Loading program: ' + program); + + // Unset the custom environmental variables that should not get inherited + delete process.env['STDIN_PIPE_NAME']; + delete process.env['STDOUT_PIPE_NAME']; + delete process.env['ATOM_SHELL_INTERNAL_RUN_AS_NODE']; + + require(program); + + log('Finished loading program.'); + + var stdinIsReferenced = true; + var timer = setInterval(function () { + var listenerCount = ( + stream.listeners('data').length + + stream.listeners('end').length + + stream.listeners('close').length + + stream.listeners('error').length + ); + // log('listenerCount: ' + listenerCount); + if (listenerCount <= 1) { + // No more "actual" listeners, only internal node + if (stdinIsReferenced) { + stdinIsReferenced = false; + // log('unreferencing stream!!!'); + stream.unref(); + } + } else { + // There are "actual" listeners + if (!stdinIsReferenced) { + stdinIsReferenced = true; + stream.ref(); + } + } + // log( + // '' + stream.listeners('data').length + + // ' ' + stream.listeners('end').length + + // ' ' + stream.listeners('close').length + + // ' ' + stream.listeners('error').length + // ); + }, 1000); + timer.unref(); + }); + + + server.listen(stdInPipeName, function () { + // signal via stdout that the parent process can now begin writing to stdin pipe + process.stdout.write('ready'); + }); + +})(); \ No newline at end of file diff --git a/dataprotocol-node/client/src/utils/is.ts b/dataprotocol-node/client/src/utils/is.ts new file mode 100644 index 0000000000..f155ae89ae --- /dev/null +++ b/dataprotocol-node/client/src/utils/is.ts @@ -0,0 +1,55 @@ +/* -------------------------------------------------------------------------------------------- + * 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(value: any): value is T[] { + return Array.isArray(value); +} + +export function stringArray(value: any): value is string[] { + return array(value) && (value).every(elem => string(elem)); +} + +export function typedArray(value: any, check: (value: any) => boolean): value is T[] { + return Array.isArray(value) && (value).every(check); +} + +export function thenable(value: any): value is Thenable { + return value && func(value.then); +} \ No newline at end of file diff --git a/dataprotocol-node/client/src/utils/processes.ts b/dataprotocol-node/client/src/utils/processes.ts new file mode 100644 index 0000000000..abfebb53fa --- /dev/null +++ b/dataprotocol-node/client/src/utils/processes.ts @@ -0,0 +1,44 @@ +/* -------------------------------------------------------------------------------------------- + * 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 ChildProcess = cp.ChildProcess; + +import { join } from 'path'; + +const isWindows = (process.platform === 'win32'); +const isMacintosh = (process.platform === 'darwin'); +const isLinux = (process.platform === 'linux'); +export function terminate(process: ChildProcess, cwd?: string): boolean { + if (isWindows) { + try { + // This we run in Atom execFileSync is available. + // Ignore stderr since this is otherwise piped to parent.stderr + // which might be already closed. + let options: any = { + stdio: ['pipe', 'pipe', 'ignore'] + }; + if (cwd) { + options.cwd = cwd + } + (cp).execFileSync('taskkill', ['/T', '/F', '/PID', process.pid.toString()], options); + return true; + } catch (err) { + return false; + } + } else if (isLinux || isMacintosh) { + try { + var cmd = join(__dirname, 'terminateProcess.sh'); + var result = (cp).spawnSync(cmd, [process.pid.toString()]); + return result.error ? false : true; + } catch (err) { + return false; + } + } else { + process.kill('SIGKILL'); + return true; + } +} \ No newline at end of file diff --git a/dataprotocol-node/client/src/utils/terminateProcess.sh b/dataprotocol-node/client/src/utils/terminateProcess.sh new file mode 100644 index 0000000000..8ef631053a --- /dev/null +++ b/dataprotocol-node/client/src/utils/terminateProcess.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the Source EULA. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +terminateTree() { + for cpid in $(pgrep -P $1); do + terminateTree $cpid + done + kill -9 $1 > /dev/null 2>&1 +} + +for pid in $*; do + terminateTree $pid +done \ No newline at end of file diff --git a/dataprotocol-node/client/thirdpartynotices.txt b/dataprotocol-node/client/thirdpartynotices.txt new file mode 100644 index 0000000000..35cb195abb --- /dev/null +++ b/dataprotocol-node/client/thirdpartynotices.txt @@ -0,0 +1,31 @@ +THIRD-PARTY SOFTWARE NOTICES AND INFORMATION +For Microsoft vscode-languageclient + +This project incorporates material from the project(s) listed below (collectively, “Third Party Code”). +Microsoft is not the original author of the Third Party Code. The original copyright notice and license +under which Microsoft received such Third Party Code are set out below. This Third Party Code is licensed +to you under their original license terms set forth below. Microsoft reserves all other rights not expressly +granted, whether by implication, estoppel or otherwise. + +1. DefinitelyTyped version 0.0.1 (https://github.com/borisyankov/DefinitelyTyped) + +This project is licensed under the MIT license. +Copyrights are respective of each contributor listed at the beginning of each definition file. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/dataprotocol-node/jsonrpc/.eslintrc b/dataprotocol-node/jsonrpc/.eslintrc new file mode 100644 index 0000000000..306df0be07 --- /dev/null +++ b/dataprotocol-node/jsonrpc/.eslintrc @@ -0,0 +1,24 @@ +{ + "rules": { + "indent": [ + 2, + "tab" + ], + "quotes": [ + 2, + "single" + ], + "linebreak-style": [ + 2, + "windows" + ], + "semi": [ + 2, + "always" + ] + }, + "env": { + "node": true + }, + "extends": "eslint:recommended" +} \ No newline at end of file diff --git a/dataprotocol-node/jsonrpc/.npmignore b/dataprotocol-node/jsonrpc/.npmignore new file mode 100644 index 0000000000..2b1e5b19e1 --- /dev/null +++ b/dataprotocol-node/jsonrpc/.npmignore @@ -0,0 +1,9 @@ +.vscode/ +lib/test/ +lib/*.map +src/ +test/ +.eslintrc +.gitignore +gulpfile.js +tsd.json \ No newline at end of file diff --git a/dataprotocol-node/jsonrpc/.vscode/launch.json b/dataprotocol-node/jsonrpc/.vscode/launch.json new file mode 100644 index 0000000000..10cab6e424 --- /dev/null +++ b/dataprotocol-node/jsonrpc/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + "version": "0.1.0", + // List of configurations. Add new configurations or edit existing ones. + // ONLY "node" and "mono" are supported, change "type" to switch. + "configurations": [ + { + "request": "launch", + // Name of configuration; appears in the launch configuration drop down menu. + "name": "Mocha", + // Type of configuration. Possible values: "node", "mono". + "type": "node", + // Workspace relative or absolute path to the program. + "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", + // Automatically stop program after launch. + "stopOnEntry": false, + // Command line arguments passed to the program. + "args": ["--timeout", "999999"], + // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. + "cwd": "${workspaceRoot}", + // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. + "runtimeExecutable": null, + // Optional arguments passed to the runtime executable. + "runtimeArgs": [], + // Environment variables passed to the program. + "env": { }, + // Use JavaScript source maps (if they exist). + "sourceMaps": true, + // If JavaScript source maps are enabled, the generated code is expected in this directory. + "outDir": "${workspaceRoot}/lib" + } + ] +} diff --git a/dataprotocol-node/jsonrpc/.vscode/settings.json b/dataprotocol-node/jsonrpc/.vscode/settings.json new file mode 100644 index 0000000000..0c0c4bcd8e --- /dev/null +++ b/dataprotocol-node/jsonrpc/.vscode/settings.json @@ -0,0 +1,9 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "javascript.validate.enable": false, + "files.trimTrailingWhitespace": true, + "eslint.enable": false, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "typescript.tsdk": "./node_modules/typescript/lib" +} \ No newline at end of file diff --git a/dataprotocol-node/jsonrpc/.vscode/tasks.json b/dataprotocol-node/jsonrpc/.vscode/tasks.json new file mode 100644 index 0000000000..0ed84877c1 --- /dev/null +++ b/dataprotocol-node/jsonrpc/.vscode/tasks.json @@ -0,0 +1,9 @@ +{ + "version": "0.1.0", + "command": "npm", + "isShellCommand": true, + "args": ["run", "watch"], + "showOutput": "silent", + "isWatching": true, + "problemMatcher": "$tsc-watch" +} \ No newline at end of file diff --git a/dataprotocol-node/jsonrpc/README.md b/dataprotocol-node/jsonrpc/README.md new file mode 100644 index 0000000000..cdfdf7fbf9 --- /dev/null +++ b/dataprotocol-node/jsonrpc/README.md @@ -0,0 +1,4 @@ +# Microsoft Data Management Protocol - Node + +## License +[MIT](https://github.com/Microsoft/carbon/blob/dev/license.txt) \ No newline at end of file diff --git a/dataprotocol-node/jsonrpc/package.json b/dataprotocol-node/jsonrpc/package.json new file mode 100644 index 0000000000..32d46ce19d --- /dev/null +++ b/dataprotocol-node/jsonrpc/package.json @@ -0,0 +1,29 @@ +{ + "name": "dataprotocol-jsonrpc", + "description": "A json rpc implementation over streams", + "version": "2.4.0", + "author": "Microsoft Corporation", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-languageserver-node.git" + }, + "bugs": { + "url": "https://github.com/Microsoft/vscode-languageserver-node/issues" + }, + "engines": { + "node": ">=4.0.0 || >=6.0.0" + }, + "main": "./lib/main.js", + "typings": "./lib/main", + "devDependencies": { + "mocha": "^3.0.2", + "typescript": "2.0.3" + }, + "scripts": { + "prepublish": "tsc -p ./src", + "compile": "tsc -p ./src", + "watch": "tsc -w -p ./src", + "test": "mocha" + } +} diff --git a/dataprotocol-node/jsonrpc/src/cancellation.ts b/dataprotocol-node/jsonrpc/src/cancellation.ts new file mode 100644 index 0000000000..75e165e351 --- /dev/null +++ b/dataprotocol-node/jsonrpc/src/cancellation.ts @@ -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; +} + +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 = 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; + + 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 { + if (this._isCancelled) { + return shortcutEvent; + } + if (!this._emitter) { + this._emitter = new Emitter(); + } + 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 { + (this._token).cancel(); + } + } + + dispose(): void { + this.cancel(); + } +} \ No newline at end of file diff --git a/dataprotocol-node/jsonrpc/src/events.ts b/dataprotocol-node/jsonrpc/src/events.ts new file mode 100644 index 0000000000..9b2c612d4d --- /dev/null +++ b/dataprotocol-node/jsonrpc/src/events.ts @@ -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 { + + /** + * + * @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 = 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 { + + private static _noop = function () { }; + + private _event: Event; + private _callbacks: CallbackList; + + constructor(private _options?: EmitterOptions) { + } + + /** + * For the public to allow to subscribe + * to events from this Emitter + */ + get event(): Event { + 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; + } + } +} diff --git a/dataprotocol-node/jsonrpc/src/is.ts b/dataprotocol-node/jsonrpc/src/is.ts new file mode 100644 index 0000000000..182ef48358 --- /dev/null +++ b/dataprotocol-node/jsonrpc/src/is.ts @@ -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(value: any): value is T[] { + return Array.isArray(value); +} + +export function stringArray(value: any): value is string[] { + return array(value) && (value).every(elem => string(elem)); +} \ No newline at end of file diff --git a/dataprotocol-node/jsonrpc/src/main.ts b/dataprotocol-node/jsonrpc/src/main.ts new file mode 100644 index 0000000000..d27c0fc7fa --- /dev/null +++ b/dataprotocol-node/jsonrpc/src/main.ts @@ -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 = { get method() { return '$/cancelRequest'; } }; +} + +export interface RequestHandler { + (params: P, token: CancellationToken): R | ResponseError | Thenable>; +} + +export interface NotificationHandler

{ + (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 = { get method() { return '$/setTraceNotification'; } }; +} + +export interface LogTraceParams { + message: string; + verbose?: string; +} + +export namespace LogTraceNotification { + export const type: NotificationType = { get method() { return '$/logTraceNotification'; } }; +} + +export interface Tracer { + log(message: string, data?: string): void; +} + +export interface MessageConnection { + sendRequest(type: RequestType, params: P, token?: CancellationToken): Thenable; + onRequest(type: RequestType, handler: RequestHandler): void; + sendNotification

(type: NotificationType

, params?: P): void; + onNotification

(type: NotificationType

, handler: NotificationHandler

): void; + trace(value: Trace, tracer: Tracer, sendNotification?: boolean): void; + onError: Event<[Error, Message, number]>; + onClose: Event; + onUnhandledNotification: Event; + listen(); + onDispose: Event; + 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(messageReader: MessageReader, messageWriter: MessageWriter, logger: Logger, client: boolean = false): T { + let sequenceNumber = 0; + const version: string = '2.0'; + + let requestHandlers: { [name: string]: RequestHandler } = Object.create(null); + let eventHandlers: { [name: string]: NotificationHandler } = 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 = new Emitter(); + let unhandledNotificationEmitter: Emitter = new Emitter(); + let disposeEmitter: Emitter = new Emitter(); + + 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): void { + let message: ResponseMessage = { + jsonrpc: version, + id: requestMessage.id + }; + if (resultOrError instanceof ResponseError) { + message.error = (>resultOrError).toJson(); + } else { + message.result = is.undefined(resultOrError) ? null : resultOrError; + } + messageWriter.write(message); + } + function replyError(error: ResponseError) { + 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 = >>handlerResult; + if (!handlerResult) { + delete requestTokens[tokenKey]; + replySuccess(handlerResult); + } else if (promise.then) { + promise.then((resultOrError): any | ResponseError => { + delete requestTokens[tokenKey]; + reply(resultOrError); + }, error => { + delete requestTokens[tokenKey]; + if (error instanceof ResponseError) { + replyError(>error); + } else if (error && is.string(error.message)) { + replyError(new ResponseError(ErrorCodes.InternalError, `Request ${requestMessage.method} failed with message: ${error.message}`)); + } else { + replyError(new ResponseError(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(>error); + } else if (error && is.string(error.message)) { + replyError(new ResponseError(ErrorCodes.InternalError, `Request ${requestMessage.method} failed with message: ${error.message}`)); + } else { + replyError(new ResponseError(ErrorCodes.InternalError, `Request ${requestMessage.method} failed unexpectedly without providing any details.`)); + } + } + } else { + replyError(new ResponseError(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; + 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:

(type: NotificationType

, params): void => { + throwIfClosedOrDisposed(); + + let notificatioMessage: NotificationMessage = { + jsonrpc: version, + method: type.method, + params: params + } + if (trace != Trace.Off && tracer) { + traceSendNotification(notificatioMessage); + } + messageWriter.write(notificatioMessage); + }, + onNotification:

(type: NotificationType

, handler: NotificationHandler

) => { + throwIfClosedOrDisposed(); + + eventHandlers[type.method] = handler; + }, + sendRequest: (type: RequestType, params: P, token?: CancellationToken) => { + throwIfClosedOrDisposed(); + + let id = sequenceNumber++; + let result = new Promise>((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(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: (type: RequestType, handler: RequestHandler) => { + 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(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(reader, writer, logger, true); +} diff --git a/dataprotocol-node/jsonrpc/src/messageReader.ts b/dataprotocol-node/jsonrpc/src/messageReader.ts new file mode 100644 index 0000000000..2bf18c3620 --- /dev/null +++ b/dataprotocol-node/jsonrpc/src/messageReader.ts @@ -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 = chunk; + if (typeof (chunk) == 'string') { + var str = 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; + onClose: Event; + onPartialMessage: Event; + listen(callback: DataCallback): void; +} + +export abstract class AbstractMessageReader { + + private errorEmitter: Emitter; + private closeEmitter: Emitter; + + private partialMessageEmitter: Emitter; + + constructor() { + this.errorEmitter = new Emitter(); + this.closeEmitter = new Emitter(); + this.partialMessageEmitter = new Emitter(); + } + + public get onError(): Event { + return this.errorEmitter.event; + } + + protected fireError(error: any): void { + this.errorEmitter.fire(this.asError(error)); + } + + public get onClose(): Event { + return this.closeEmitter.event; + } + + protected fireClose(): void { + this.closeEmitter.fire(undefined); + } + + public get onPartialMessage(): Event { + 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 = this.process; + eventEmitter.on('error', (error: any) => this.fireError(error)); + eventEmitter.on('close', () => this.fireClose()); + } + + public listen(callback: DataCallback): void { + let eventEmitter: NodeJS.EventEmitter = this.process; + eventEmitter.on('message', callback); + } +} \ No newline at end of file diff --git a/dataprotocol-node/jsonrpc/src/messageWriter.ts b/dataprotocol-node/jsonrpc/src/messageWriter.ts new file mode 100644 index 0000000000..3076364de0 --- /dev/null +++ b/dataprotocol-node/jsonrpc/src/messageWriter.ts @@ -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; + write(msg: Message): void; +} + +export abstract class AbstractMessageWriter { + + private errorEmitter: Emitter<[Error, Message, number]>; + private closeEmitter: Emitter; + + constructor() { + this.errorEmitter = new Emitter<[Error, Message, number]>(); + this.closeEmitter = new Emitter(); + } + + 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 { + 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 = this.process; + eventEmitter.on('error', (error) => this.fireError(error)); + eventEmitter.on('close', () => this.fireClose); + } + + public write(msg: Message): void { + try { + (this.process.send as Function)(msg); + this.errorCount = 0; + } catch (error) { + this.errorCount++; + this.fireError(error, msg, this.errorCount); + } + } +} \ No newline at end of file diff --git a/dataprotocol-node/jsonrpc/src/messages.ts b/dataprotocol-node/jsonrpc/src/messages.ts new file mode 100644 index 0000000000..6dbf5cb07b --- /dev/null +++ b/dataprotocol-node/jsonrpc/src/messages.ts @@ -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 { + /** + * 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 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 { + let result: ResponseErrorLiteral = { + 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; +} + +/** + * A interface to type the request parameter / response pair + */ +export interface RequestType { + 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

{ + method: string; +} + +/** + * Tests if the given message is a request message + */ +export function isRequestMessage(message: Message): message is RequestMessage { + let candidate = 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 = message; + return candidate && is.string(candidate.method) && is.undefined((message).id); +} + +/** + * Tests if the given message is a response message + */ +export function isReponseMessage(message: Message): message is ResponseMessage { + let candidate = message; + return candidate && (is.defined(candidate.result) || is.defined(candidate.error)) && (is.string(candidate.id) || is.number(candidate.id)); +} \ No newline at end of file diff --git a/dataprotocol-node/jsonrpc/src/tsconfig.json b/dataprotocol-node/jsonrpc/src/tsconfig.json new file mode 100644 index 0000000000..6744afdba5 --- /dev/null +++ b/dataprotocol-node/jsonrpc/src/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "declaration": true, + "stripInternal": true, + "outDir": "../lib" + } +} \ No newline at end of file diff --git a/dataprotocol-node/jsonrpc/src/typings/promise.d.ts b/dataprotocol-node/jsonrpc/src/typings/promise.d.ts new file mode 100644 index 0000000000..925c943df5 --- /dev/null +++ b/dataprotocol-node/jsonrpc/src/typings/promise.d.ts @@ -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 { + /** + * 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(onfulfilled?: (value: R) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Thenable; + then(onfulfilled?: (value: R) => TResult | Thenable, onrejected?: (reason: any) => void): Thenable; +} + +/** + * Represents the completion of an asynchronous operation + */ +interface Promise extends Thenable { + /** + * 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(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Promise; + then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => void): Promise; + + /** + * 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): Promise; +} + +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 (executor: (resolve: (value?: T | Thenable) => void, reject: (reason?: any) => void) => void): Promise; + + /** + * 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(values: Array>): Promise; + + /** + * 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(values: Array>): Promise; + + /** + * 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; + + /** + * 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; + + /** + * Creates a new resolved promise for the provided value. + * @param value A promise. + * @returns A promise whose internal state matches the provided promise. + */ + resolve(value: T | Thenable): Promise; + + /** + * Creates a new resolved promise . + * @returns A resolved promise. + */ + resolve(): Promise; +} + +declare var Promise: PromiseConstructor; diff --git a/dataprotocol-node/jsonrpc/thirdpartynotices.txt b/dataprotocol-node/jsonrpc/thirdpartynotices.txt new file mode 100644 index 0000000000..4a6720c560 --- /dev/null +++ b/dataprotocol-node/jsonrpc/thirdpartynotices.txt @@ -0,0 +1,31 @@ +THIRD-PARTY SOFTWARE NOTICES AND INFORMATION +For Microsoft vscode-jsonrpc + +This project incorporates material from the project(s) listed below (collectively, “Third Party Code”). +Microsoft is not the original author of the Third Party Code. The original copyright notice and license +under which Microsoft received such Third Party Code are set out below. This Third Party Code is licensed +to you under their original license terms set forth below. Microsoft reserves all other rights not expressly +granted, whether by implication, estoppel or otherwise. + +1. DefinitelyTyped version 0.0.1 (https://github.com/borisyankov/DefinitelyTyped) + +This project is licensed under the MIT license. +Copyrights are respective of each contributor listed at the beginning of each definition file. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/dataprotocol-node/package.json b/dataprotocol-node/package.json deleted file mode 100644 index cef0a61a81..0000000000 --- a/dataprotocol-node/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "dataprotocol-node", - "version": "1.0.0", - "description": "sqlops studio implementation of vscode-language-client", - "main": "lib/main.js", - "scripts": {}, - "author": "Microsoft", - "license": "ISC", - "dependencies": { - "vscode-languageclient": "^3.5.0" - }, - "devDependencies": { - "typescript": "^2.6.2" - } -} diff --git a/dataprotocol-node/src/main.ts b/dataprotocol-node/src/main.ts deleted file mode 100644 index a24b30389b..0000000000 --- a/dataprotocol-node/src/main.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { - LanguageClient, ServerOptions, LanguageClientOptions, DynamicFeature, ServerCapabilities, RegistrationData, - RPCMessageType, Disposable -} from 'vscode-languageclient'; - -import { ClientCapabilities } from './protocol'; - -function ensure(target: T, key: K): T[K] { - if (target[key] === void 0) { - target[key] = {} as any; - } - return target[key]; -} - -/** - * - */ -export abstract class SqlOpsFeature implements DynamicFeature { - - protected _providers: Map = new Map(); - - constructor(protected _client: SqlOpsDataClient, private _message: RPCMessageType) { - } - - public get messages(): RPCMessageType { - return this._message; - } - - public abstract fillClientCapabilities(capabilities: ClientCapabilities): void; - - public abstract initialize(capabilities: ServerCapabilities): void; - - register(message: RPCMessageType, data: RegistrationData): void { - if (message.method !== this.messages.method) { - throw new Error(`Register called on wrong feature. Requested ${message.method} but reached feature ${this.messages.method}`); - } - let provider = this.registerProvider(data.registerOptions); - if (provider) { - this._providers.set(data.id, provider); - } - } - - protected abstract registerProvider(options: T): Disposable; - - public unregister(id: string): void { - let provider = this._providers.get(id); - if (provider) { - provider.dispose(); - } - } - - public dispose(): void { - this._providers.forEach((value) => { - value.dispose(); - }); - } -} - -class ConnectionFeature extends SqlOpsFeature { - public fillClientCapabilities(capabilities: ClientCapabilities): void { - ensure(ensure(capabilities, 'connection')!, 'connection')!.dynamicRegistration = true; - } - public initialize(capabilities: ServerCapabilities): void { - throw new Error("Method not implemented."); - } - protected registerProvider(options: undefined): Disposable { - throw new Error("Method not implemented."); - } - -} - -/** - * - */ -export class SqlOpsDataClient extends LanguageClient { - - 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) { - if (typeof arg2 === 'string') { - super(arg1, arg2, arg3 as ServerOptions, arg4 as LanguageClientOptions, arg5); - } else { - super(arg1, arg2 as ServerOptions, arg3 as LanguageClientOptions, arg4 as boolean); - } - this.registerDataFeatures(); - } - - private registerDataFeatures() { - - } -} diff --git a/dataprotocol-node/src/protocol.ts b/dataprotocol-node/src/protocol.ts deleted file mode 100644 index 7b15efeff8..0000000000 --- a/dataprotocol-node/src/protocol.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { ClientCapabilities as VSClientCapabilities } from 'vscode-languageclient'; - -export interface ConnectionClientCapabilities { - connection?: { - /** - * Whether the connection support dynamic registration - */ - dynamicRegistration?: boolean; - }, - backup?: { - /** - * Whether the backup support dynamic registration - */ - dynamicRegistration?: boolean; - }, - restore?: { - /** - * Whether the restore support dynamic registration - */ - dynamicRegistration?: boolean; - }, - query?: { - /** - * Whether the query support dynamic registration - */ - dynamicRegistration?: boolean; - }, - objectExplorer?: { - /** - * Whether the object explorer support dynamic registration - */ - dynamicRegistration?: boolean; - }, - scripting?: { - - /** - * Whether the scripting support dynamic registration - */ - dynamicRegistration?: boolean; - }, - taskServices?: { - /** - * Whether the task services support dynamic registration - */ - dynamicRegistration?: boolean; - }, - fileBrowser?: { - /** - * Whether the file browser support dynamic registration - */ - dynamicRegistration?: boolean; - }, - profiler?: { - /** - * Whether the profiler support dynamic registration - */ - dynamicRegistration?: boolean; - } -} - -export interface ClientCapabilities extends VSClientCapabilities { - connection?: ConnectionClientCapabilities; -} diff --git a/dataprotocol-node/types/.eslintrc b/dataprotocol-node/types/.eslintrc new file mode 100644 index 0000000000..306df0be07 --- /dev/null +++ b/dataprotocol-node/types/.eslintrc @@ -0,0 +1,24 @@ +{ + "rules": { + "indent": [ + 2, + "tab" + ], + "quotes": [ + 2, + "single" + ], + "linebreak-style": [ + 2, + "windows" + ], + "semi": [ + 2, + "always" + ] + }, + "env": { + "node": true + }, + "extends": "eslint:recommended" +} \ No newline at end of file diff --git a/dataprotocol-node/types/.gitignore b/dataprotocol-node/types/.gitignore new file mode 100644 index 0000000000..08eb0a07a6 --- /dev/null +++ b/dataprotocol-node/types/.gitignore @@ -0,0 +1 @@ +!bin/ \ No newline at end of file diff --git a/dataprotocol-node/types/.npmignore b/dataprotocol-node/types/.npmignore new file mode 100644 index 0000000000..2b1e5b19e1 --- /dev/null +++ b/dataprotocol-node/types/.npmignore @@ -0,0 +1,9 @@ +.vscode/ +lib/test/ +lib/*.map +src/ +test/ +.eslintrc +.gitignore +gulpfile.js +tsd.json \ No newline at end of file diff --git a/dataprotocol-node/types/.vscode/settings.json b/dataprotocol-node/types/.vscode/settings.json new file mode 100644 index 0000000000..0c0c4bcd8e --- /dev/null +++ b/dataprotocol-node/types/.vscode/settings.json @@ -0,0 +1,9 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "javascript.validate.enable": false, + "files.trimTrailingWhitespace": true, + "eslint.enable": false, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "typescript.tsdk": "./node_modules/typescript/lib" +} \ No newline at end of file diff --git a/dataprotocol-node/types/.vscode/tasks.json b/dataprotocol-node/types/.vscode/tasks.json new file mode 100644 index 0000000000..0ed84877c1 --- /dev/null +++ b/dataprotocol-node/types/.vscode/tasks.json @@ -0,0 +1,9 @@ +{ + "version": "0.1.0", + "command": "npm", + "isShellCommand": true, + "args": ["run", "watch"], + "showOutput": "silent", + "isWatching": true, + "problemMatcher": "$tsc-watch" +} \ No newline at end of file diff --git a/dataprotocol-node/types/README.md b/dataprotocol-node/types/README.md new file mode 100644 index 0000000000..cdfdf7fbf9 --- /dev/null +++ b/dataprotocol-node/types/README.md @@ -0,0 +1,4 @@ +# Microsoft Data Management Protocol - Node + +## License +[MIT](https://github.com/Microsoft/carbon/blob/dev/license.txt) \ No newline at end of file diff --git a/dataprotocol-node/types/package.json b/dataprotocol-node/types/package.json new file mode 100644 index 0000000000..e8dcaf1e07 --- /dev/null +++ b/dataprotocol-node/types/package.json @@ -0,0 +1,26 @@ +{ + "name": "dataprotocol-languageserver-types", + "description": "Types used by the Language server for node", + "version": "1.0.4", + "author": "Microsoft Corporation", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-languageserver-node.git" + }, + "bugs": { + "url": "https://github.com/Microsoft/vscode-languageserver-node/issues" + }, + "main": "./lib/main.js", + "typings": "./lib/main", + "devDependencies": { + "mocha": "^2.4.5", + "typescript": "2.0.3" + }, + "scripts": { + "prepublish": "tsc -p ./src", + "compile": "tsc -p ./src", + "watch": "tsc -w -p ./src", + "test": "mocha" + } +} diff --git a/dataprotocol-node/types/src/main.ts b/dataprotocol-node/types/src/main.ts new file mode 100644 index 0000000000..35a0089647 --- /dev/null +++ b/dataprotocol-node/types/src/main.ts @@ -0,0 +1,2541 @@ +/*--------------------------------------------------------------------------------------------- + * 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 CreateSessionResponse { + sessionId: string; +} + +export interface SessionCreatedParameters { + success: boolean; + sessionId: string; + rootNode: NodeInfo; + errorMessage: string; +} + +export interface ExpandResponse { + nodePath: string; + sessionId: string; + nodes: NodeInfo[]; + errorMessage: string; +} + +export interface NodeInfo { + nodePath: string; + nodeType: string; + nodeSubType: string; + nodeStatus: string; + label: string; + isLeaf: boolean; + metadata: ObjectMetadata; + errorMessage: string; +} + +export interface ExpandParams { + sessionId: string; + nodePath: string; +} + +export interface CloseSessionParams { + sessionId: string; +} + +export interface CloseSessionResponse { + success: boolean; + sessionId: string; +} + +export interface CategoryValue { + displayName: string; + + name: string; +} + +export interface ServiceOption { + name: string; + + displayName: string; + + description: string; + + groupName: string; + + valueType: string; + + defaultValue: string; + + objectType: string; + + categoryValues: CategoryValue[]; + + isRequired: boolean; + + isArray: boolean; +} + +export interface ConnectionOption { + name: string; + + displayName: string; + + description: string; + + groupName: string; + + valueType: string; + + defaultValue: string; + + objectType: string; + + categoryValues: CategoryValue[]; + + specialValueType: string; + + isIdentity: boolean; + + isRequired: boolean; + + isArray: boolean; +} + +export interface ConnectionProviderOptions { + options: ConnectionOption[]; +} + +export interface AdminServicesProviderOptions { + databaseInfoOptions: ServiceOption[]; + + databaseFileInfoOptions: ServiceOption[]; + + fileGroupInfoOptions: ServiceOption[]; +} + +export interface FeatureMetadataProvider { + enabled: boolean; + featureName: string; + optionsMetadata: ServiceOption[]; +} + +export interface DataProtocolServerCapabilities { + protocolVersion: string; + + providerName: string; + + providerDisplayName: string; + + connectionProvider: ConnectionProviderOptions; + + adminServicesProvider: AdminServicesProviderOptions; + + features: FeatureMetadataProvider[]; +} + +/** + * Parameters to initialize a connection to a database + */ +export interface ConnectionDetails { + + /** + * connection options + */ + options: {}; +} + +/** + * Summary that identifies a unique database connection. + */ +export class ConnectionSummary { + /** + * server name + */ + public serverName: string; + + /** + * database name + */ + public databaseName: string; + + /** + * user name + */ + public userName: string; +} + +/** + * Connection response format. + */ +export class ConnectionCompleteParams { + /** + * URI identifying the owner of the connection + */ + public ownerUri: string; + + /** + * connection id returned from service host. + */ + public connectionId: string; + + /** + * any diagnostic messages return from the service host. + */ + public messages: string; + + /** + * Error message returned from the engine, if any. + */ + public errorMessage: string; + + /** + * Error number returned from the engine, if any. + */ + public errorNumber: number; + + /** + * Information about the connected server. + */ + public serverInfo: ServerInfo; + + /** + * information about the actual connection established + */ + public connectionSummary: ConnectionSummary; +} + +/** + * Update event parameters + */ +export class IntelliSenseReadyParams { + /** + * URI identifying the text document + */ + public ownerUri: string; +} + +/** + * Information about a SQL Server instance. + */ +export class ServerInfo { + /** + * The major version of the SQL Server instance. + */ + public serverMajorVersion: number; + + /** + * The minor version of the SQL Server instance. + */ + public serverMinorVersion: number; + + /** + * The build of the SQL Server instance. + */ + public serverReleaseVersion: number; + + /** + * The ID of the engine edition of the SQL Server instance. + */ + public engineEditionId: number; + + /** + * String containing the full server version text. + */ + public serverVersion: string; + + /** + * String describing the product level of the server. + */ + public serverLevel: string; + + /** + * The edition of the SQL Server instance. + */ + public serverEdition: string; + + /** + * Whether the SQL Server instance is running in the cloud (Azure) or not. + */ + public isCloud: boolean; + + /** + * The version of Azure that the SQL Server instance is running on, if applicable. + */ + public azureVersion: number; + + /** + * The Operating System version string of the machine running the SQL Server instance. + */ + public osVersion: string; +} + +export class CapabiltiesDiscoveryResult { + public capabilities: DataProtocolServerCapabilities; +} + +// Task Services types + +export enum TaskStatus { + notStarted = 0, + inProgress = 1, + succeeded = 2, + succeededWithWarning = 3, + failed = 4, + canceled = 5 +} + +export enum TaskExecutionMode { + execute = 0, + script = 1, + executeAndScript = 2, +} + +export interface TaskInfo { + taskId: string; + status: TaskStatus; + taskExecutionMode: TaskExecutionMode; + serverName: string; + databaseName: string; + name: string; + description: string; + providerName: string; + isCancelable: boolean; +} + +export interface ListTasksParams { + + listActiveTasksOnly: boolean; +} + +export interface ListTasksResponse { + tasks: TaskInfo[]; +} + +export interface CancelTaskParams { + taskId: string; +} + +export interface TaskProgressInfo { + taskId: string; + status: TaskStatus; + message: string; + script: string; + duration: number; +} + +// Admin Services types + +export interface DatabaseInfo { + /** + * database options + */ + options: {}; +} + +export interface BackupConfigInfo { + recoveryModel: string; + defaultBackupFolder: string; + backupEncryptors: {}; +} + +export interface LoginInfo { + name: string; +} + +export interface CreateDatabaseParams { + ownerUri: string; + + databaseInfo: DatabaseInfo; +} + +export interface CreateDatabaseResponse { + result: boolean; + taskId: number; +} + +export interface DefaultDatabaseInfoParams { + ownerUri: string; +} + +export interface DefaultDatabaseInfoResponse { + defaultDatabaseInfo: DatabaseInfo; +} + +export interface GetDatabaseInfoResponse { + databaseInfo: DatabaseInfo; +} + +export interface GetDatabaseInfoParams { + ownerUri: string; +} + +export interface BackupConfigInfoResponse { + backupConfigInfo: BackupConfigInfo; +} + +export interface CreateLoginParams { + ownerUri: string; + + loginInfo: LoginInfo; +} + +export interface CreateLoginResponse { + result: boolean; + taskId: number; +} + +// Disaster Recovery types + +export interface BackupInfo { + ownerUri: string; + + databaseName: string; + + backupType: number; + + backupComponent: number; + + backupDeviceType: number; + + selectedFiles: string; + + backupsetName: string; + + selectedFileGroup: { [path: string]: string }; + + // List of {key: backup path, value: device type} + backupPathDevices: { [path: string]: number }; + + backupPathList: [string]; + + isCopyOnly: boolean; + + formatMedia: boolean; + + initialize: boolean; + + skipTapeHeader: boolean; + + mediaName: string; + + mediaDescription: string; + + checksum: boolean; + + continueAfterError: boolean; + + logTruncation: boolean; + + tailLogBackup: boolean; + + retainDays: number; + + compressionOption: number; + + verifyBackupRequired: boolean; + + encryptionAlgorithm: number; + + encryptorType: number; + + encryptorName: string; +} + +export interface BackupParams { + ownerUri: string; + + backupInfo: BackupInfo; + + taskExecutionMode: TaskExecutionMode; +} + +export interface BackupResponse { + result: boolean; + taskId: number; +} + +export interface RestoreParams { + ownerUri: string; + options: {}; + taskExecutionMode: TaskExecutionMode; +} + +export interface RestoreConfigInfoRequestParams { + ownerUri: string; +} + +export interface RestoreConfigInfoResponse { + configInfo: { [key: string]: any }; +} + +export interface RestoreDatabaseFileInfo { + fileType: string; + + logicalFileName: string; + + originalFileName: string; + + restoreAsFileName: string; +} + +export interface FileBrowserOpenParams { + ownerUri: string; + expandPath: string; + fileFilters: string[]; + changeFilter: boolean; +} + +export interface FileTreeNode { + children: FileTreeNode[]; + isExpanded: boolean; + isFile: boolean; + name: string; + fullPath: string; +} + +export interface FileTree { + rootNode: FileTreeNode; + selectedNode: FileTreeNode; +} + +export interface FileBrowserOpenedParams { + ownerUri: string; + fileTree: FileTree; + succeeded: boolean; + message: string; +} + +export interface FileBrowserExpandParams { + ownerUri: string; + expandPath: string; +} + +export interface FileBrowserExpandedParams { + ownerUri: string; + expandPath: string; + children: FileTreeNode[]; + succeeded: boolean; + message: string; +} + +export interface FileBrowserValidateParams { + ownerUri: string; + serviceType: string; + selectedFiles: string[]; +} + +export interface FileBrowserValidatedParams { + succeeded: boolean; + message: string; +} + +export interface FileBrowserCloseParams { + ownerUri: string; +} + +export interface FileBrowserCloseResponse { + succeeded: boolean; + message: string; +} + +export interface DatabaseFileInfo { + properties: LocalizedPropertyInfo[]; + id: string; + isSelected: boolean; +} + +export interface LocalizedPropertyInfo { + propertyName: string; + propertyValue: string; + propertyDisplayName: string; + propertyValueDisplayName: string; +} + +export interface RestorePlanDetailInfo { + name: string; + currentValue: any; + isReadOnly: boolean; + isVisible: boolean; + defaultValue: any; + +} + +export interface RestorePlanResponse { + sessionId: string; + backupSetsToRestore: DatabaseFileInfo[]; + canRestore: boolean; + errorMessage: string; + dbFiles: RestoreDatabaseFileInfo[]; + databaseNamesFromBackupSets: string[]; + planDetails: { [key: string]: RestorePlanDetailInfo }; +} + +export interface RestoreResponse { + result: boolean; + taskId: string; + errorMessage: string; +} + +// Query Execution types +export interface ResultSetSummary { + id: number; + batchId: number; + rowCount: number; + columnInfo: IDbColumn[]; +} + +export interface BatchSummary { + hasError: boolean; + id: number; + selection: ISelectionData; + resultSetSummaries: ResultSetSummary[]; + executionElapsed: string; + executionEnd: string; + executionStart: string; +} + +export interface IDbColumn { + allowDBNull?: boolean; + baseCatalogName: string; + baseColumnName: string; + baseSchemaName: string; + baseServerName: string; + baseTableName: string; + columnName: string; + columnOrdinal?: number; + columnSize?: number; + isAliased?: boolean; + isAutoIncrement?: boolean; + isExpression?: boolean; + isHidden?: boolean; + isIdentity?: boolean; + isKey?: boolean; + isBytes?: boolean; + isChars?: boolean; + isSqlVariant?: boolean; + isUdt?: boolean; + dataType: string; + isXml?: boolean; + isJson?: boolean; + isLong?: boolean; + isReadOnly?: boolean; + isUnique?: boolean; + numericPrecision?: number; + numericScale?: number; + udtAssemblyQualifiedName: string; + dataTypeName: string; +} + +export interface IGridResultSet { + columns: IDbColumn[]; + rowsUri: string; + numberOfRows: number; +} + +export interface IResultMessage { + batchId?: number; + isError: boolean; + time: string; + message: string; +} + +export interface ISelectionData { + startLine: number; + startColumn: number; + endLine: number; + endColumn: number; +} + +export interface QueryExecuteBatchNotificationParams { + batchSummary: BatchSummary; + ownerUri: string; +} + +export interface DbCellValue { + displayValue: string; + isNull: boolean; +} + +export enum EditRowState { + clean = 0, + dirtyInsert = 1, + dirtyDelete = 2, + dirtyUpdate = 3 +} + +export interface EditRow { + cells: DbCellValue[]; + id: number; + isDirty: boolean; + state: EditRowState; +} + +export interface EditCell extends DbCellValue { + isDirty: boolean; +} + +export interface EditCell extends DbCellValue { + +} + +export class MetadataQueryParams { + /** + * Owner URI of the connection that changed. + */ + public ownerUri: string; +} + +export enum MetadataType { + Table = 0, + View = 1, + SProc = 2, + Function = 3 +} + +export class ObjectMetadata { + metadataType: MetadataType; + + metadataTypeName: string; + + urn: string; + + name: string; + + schema: string; +} + +export class MetadataQueryResult { + public metadata: ObjectMetadata[]; +} + +export interface ScriptingParamDetails { + filePath: string; + scriptCompatibilityOption: string; + targetDatabaseEngineEdition: string; + targetDatabaseEngineType: string; +} + +export enum ScriptOperation { + Select = 0, + Create = 1, + Insert = 2, + Update = 3, + Delete = 4, + Execute = 5, + Alter = 6 +} + +export interface ScriptOptions { + /** + * Generate ANSI padding statements + */ + scriptANSIPadding?: boolean; + + /** + * Append the generated script to a file + */ + appendToFile?: boolean; + + /** + * Continue to script if an error occurs. Otherwise, stop. + */ + continueScriptingOnError?: boolean; + + /** + * Convert user-defined data types to base types. + */ + convertUDDTToBaseType?: boolean; + + /** + * Generate script for dependent objects for each object scripted. + */ + generateScriptForDependentObjects?: boolean; + + /** + * Include descriptive headers for each object generated. + */ + includeDescriptiveHeaders?: boolean; + + /** + * Check that an object with the given name exists before dropping or altering or that an object with the given name does not exist before creating. + */ + includeIfNotExists?: boolean; + + /** + * Script options to set vardecimal storage format. + */ + includeVarDecimal?: boolean; + + /** + * Include system generated constraint names to enforce declarative referential integrity. + */ + scriptDRIIncludeSystemNames?: boolean; + + /** + * Include statements in the script that are not supported on the specified SQL Server database engine type. + */ + includeUnsupportedStatements?: boolean; + + /** + * Prefix object names with the object schema. + */ + schemaQualify?: boolean; + + /** + * Script options to set bindings option. + */ + bindings?: boolean; + + /** + * Script the objects that use collation. + */ + collation?: boolean; + + /** + * Script the default values. + */ + default?: boolean; + + /** + * Script Object CREATE/DROP statements. + */ + scriptCreateDrop: string; + + /** + * Script the Extended Properties for each object scripted. + */ + scriptExtendedProperties?: boolean; + + /** + * Script only features compatible with the specified version of SQL Server. + */ + scriptCompatibilityOption: string; + + /** + * Script only features compatible with the specified SQL Server database engine type. + */ + targetDatabaseEngineType: string; + + /** + * Script only features compatible with the specified SQL Server database engine edition. + */ + targetDatabaseEngineEdition: string; + + /** + * Script all logins available on the server. Passwords will not be scripted. + */ + scriptLogins?: boolean; + + /** + * Generate object-level permissions. + */ + scriptObjectLevelPermissions?: boolean; + + /** + * Script owner for the objects. + */ + scriptOwner?: boolean; + + /** + * Script statistics, and optionally include histograms, for each selected table or view. + */ + scriptStatistics: string; + + /** + * Generate USE DATABASE statement. + */ + scripUseDatabase?: boolean; + + /** + * Generate script that contains schema only or schema and data. + */ + typeOfDataToScript: string; + + /** + * Scripts the change tracking information. + */ + scriptChangeTracking?: boolean; + + /** + * Script the check constraints for each table or view scripted. + */ + scriptCheckConstraints?: boolean; + + /** + * Scripts the data compression information. + */ + scriptDataCompressionOptions?: boolean; + + /** + * Script the foreign keys for each table scripted. + */ + scriptForeignKeys?: boolean; + + /** + * Script the full-text indexes for each table or indexed view scripted. + */ + scriptFullTextIndexes?: boolean; + + /** + * Script the indexes (including XML and clustered indexes) for each table or indexed view scripted. + */ + scriptIndexes?: boolean; + + /** + * Script the primary keys for each table or view scripted + */ + scriptPrimaryKeys?: boolean; + + /** + * Script the triggers for each table or view scripted + */ + scriptTriggers?: boolean; + + /** + * Script the unique keys for each table or view scripted. + */ + uniqueKeys?: boolean; +} + +export interface ScriptingObject { + /** + * The database object type + */ + type: string; + + /** + * The schema of the database object + */ + schema: string; + + /** + * The database object name + */ + name: string; +} + +export interface ScriptingParams { + /** + * File path used when writing out the script. + */ + filePath: string; + + /** + * Whether scripting to a single file or file per object. + */ + scriptDestination: string; + + /** + * Connection string of the target database the scripting operation will run against. + */ + connectionString: string; + + /** + * A list of scripting objects to script + */ + scriptingObjects: ScriptingObject[]; + + /** + * A list of scripting object which specify the include criteria of objects to script. + */ + includeObjectCriteria: ScriptingObject[]; + + /** + * A list of scripting object which specify the exclude criteria of objects to not script. + */ + excludeObjectCriteria: ScriptingObject[]; + + /** + * A list of schema name of objects to script. + */ + includeSchemas: string[]; + + /** + * A list of schema name of objects to not script. + */ + excludeSchemas: string[]; + + /** + * A list of type name of objects to script. + */ + includeTypes: string[]; + + /** + * A list of type name of objects to not script. + */ + excludeTypes: string[]; + + /** + * Scripting options for the ScriptingParams + */ + scriptOptions: ScriptOptions; + + /** + * Connection details for the ScriptingParams + */ + connectionDetails: ConnectionDetails; + + /** + * Owner URI of the connection + */ + ownerURI: string; + + /** + * Whether the scripting operation is for + * select script statements + */ + selectScript: boolean; + + /** + * Operation associated with the script request + */ + operation: ScriptOperation; +} + +export interface ScriptingResult { + + operationId: string; + script: string; +} + +export interface ScriptingCompleteParams { + /** + * The error details for an error that occurred during the scripting operation. + */ + errorDetails: string; + + /** + * The error message for an error that occurred during the scripting operation. + */ + errorMessage: string; + + /** + * A value to indicate an error occurred during the scripting operation. + */ + hasError: boolean; + + /** + * A value to indicate the scripting operation was canceled. + */ + canceled: boolean; + + /** + * A value to indicate the scripting operation successfully completed. + */ + success: boolean; +} + +export class ColumnMetadata { + + hasExtendedProperties: boolean; + + defaultValue: string; + + ///

+ /// Escaped identifier for the name of the column + /// + escapedName: string; + + /// + /// Whether or not the column is computed + /// + isComputed: boolean; + + /// + /// Whether or not the column is deterministically computed + /// + isDeterministic: boolean; + + /// + /// Whether or not the column is an identity column + /// + isIdentity: boolean; + + /// + /// The ordinal ID of the column + /// + ordinal: number; + + /// + /// Whether or not the column is calculated on the server side. This could be a computed + /// column or a identity column. + /// + isCalculated: boolean; + + /// + /// Whether or not the column is used in a key to uniquely identify a row + /// + isKey: boolean; + + /// + /// Whether or not the column can be trusted for uniqueness + /// + isTrustworthyForUniqueness: boolean; + +} + +export class TableMetadata { + + columns: ColumnMetadata[]; + +} + +/** + * Parameters to start a profiler session + */ +export interface StartProfilingParams { + /** + * Session Owner URI + */ + ownerUri: string; + + /** + * Session options + */ + options: {}; +} + +export interface StartProfilingResponse { + succeeded: string; + errorMessage: string; +} + +/** + * Parameters to start a profiler session + */ +export interface StopProfilingParams { + /** + * Session Owner URI + */ + ownerUri: string; +} + +export interface StopProfilingResponse { + succeeded: string; + errorMessage: string; +} + +/** + * Profiler Event + */ +export interface ProfilerEvent { + /** + * Event class name + */ + name: string; + + /** + * Event timestamp + */ + timestamp: string; + + /** + * Event values + */ + values: {}; +} + +/** + * Profiler events available notification parameters + */ +export interface ProfilerEventsAvailableParams +{ + /** + * Session owner URI + */ + ownerUri: string; + + /** + * New profiler events available + */ + events: ProfilerEvent[]; +} + +/** + * Position in a text document expressed as zero-based line and character offset. + */ +export interface Position { + /** + * Line position in a document (zero-based). + */ + line: number; + + /** + * Character offset on a line in a document (zero-based). + */ + character: number; +} + +/** + * The Position namespace provides helper functions to work with + * [Position](#Position) literals. + */ +export namespace Position { + /** + * Creates a new Position literal from the given line and character. + * @param line The position's line. + * @param character The position's character. + */ + export function create(line: number, character: number): Position { + return { line, character }; + } + /** + * Checks whether the given liternal conforms to the [Position](#Position) interface. + */ + export function is(value: any): value is Position { + let candidate = value as Position; + return Is.defined(candidate) && Is.number(candidate.line) && Is.number(candidate.character); + } +} + +/** + * A range in a text document expressed as (zero-based) start and end positions. + */ +export interface Range { + /** + * The range's start position + */ + start: Position; + + /** + * The range's end position + */ + end: Position; +} + +/** + * The Range namespace provides helper functions to work with + * [Range](#Range) literals. + */ +export namespace Range { + /** + * Create a new Range liternal. + * @param start The range's start position. + * @param end The range's end position. + */ + export function create(start: Position, end: Position): Range; + /** + * Create a new Range liternal. + * @param startLine The start line number. + * @param startCharacter The start character. + * @param endLine The end line number. + * @param endCharacter The end character. + */ + export function create(startLine: number, startCharacter: number, endLine: number, endCharacter: number): Range; + export function create(one: Position | number, two: Position | number, three?: number, four?: number): Range { + if (Is.number(one) && Is.number(two) && Is.number(three) && Is.number(four)) { + return { start: Position.create(one, two), end: Position.create(three, four) }; + } else if (Position.is(one) && Position.is(two)) { + return { start: one, end: two }; + } else { + throw new Error(`Range#create called with invalid arguments[${one}, ${two}, ${three}, ${four}]`); + } + } + /** + * Checks whether the given literal conforms to the [Range](#Range) interface. + */ + export function is(value: any): value is Range { + let candidate = value as Range; + return Is.defined(candidate) && Position.is(candidate.start) && Position.is(candidate.end); + } +} + +/** + * Represents a location inside a resource, such as a line + * inside a text file. + */ +export interface Location { + uri: string; + range: Range; +} + +/** + * The Location namespace provides helper functions to work with + * [Location](#Location) literals. + */ +export namespace Location { + /** + * Creates a Location literal. + * @param uri The location's uri. + * @param range The location's range. + */ + export function create(uri: string, range: Range): Location { + return { uri, range }; + } + /** + * Checks whether the given literal conforms to the [Location](#Location) interface. + */ + export function is(value: any): value is Location { + let candidate = value as Location; + return Is.defined(candidate) && Range.is(candidate.range) && (Is.string(candidate.uri) || Is.undefined(candidate.uri)); + } +} + +/** + * The diagnostic's serverity. + */ +export const enum DiagnosticSeverity { + /** + * Reports an error. + */ + Error = 1, + /** + * Reports a warning. + */ + Warning = 2, + /** + * Reports an information. + */ + Information = 3, + /** + * Reports a hint. + */ + Hint = 4 +} + +/** + * Represents a diagnostic, such as a compiler error or warning. Diagnostic objects + * are only valid in the scope of a resource. + */ +export interface Diagnostic { + /** + * The range at which the message applies + */ + range: Range; + + /** + * The diagnostic's severity. Can be omitted. If omitted it is up to the + * client to interpret diagnostics as error, warning, info or hint. + */ + severity?: number; + + /** + * The diagnostic's code. Can be omitted. + */ + code?: number | string; + + /** + * A human-readable string describing the source of this + * diagnostic, e.g. 'typescript' or 'super lint'. + */ + source?: string; + + /** + * The diagnostic's message. + */ + message: string; +} + +/** + * The Diagnostic namespace provides helper functions to work with + * [Diagnostic](#Diagnostic) literals. + */ +export namespace Diagnostic { + /** + * Creates a new Diagnostic literal. + */ + export function create(range: Range, message: string, severity?: number, code?: number | string, source?: string): Diagnostic { + let result: Diagnostic = { range, message }; + if (Is.defined(severity)) { + result.severity = severity; + } + if (Is.defined(code)) { + result.code = code; + } + if (Is.defined(source)) { + result.source = source; + } + return result; + } + /** + * Checks whether the given literal conforms to the [Diagnostic](#Diagnostic) interface. + */ + export function is(value: any): value is Diagnostic { + let candidate = value as Diagnostic; + return Is.defined(candidate) + && Range.is(candidate.range) + && Is.string(candidate.message) + && (Is.number(candidate.severity) || Is.undefined(candidate.severity)) + && (Is.number(candidate.code) || Is.string(candidate.code) || Is.undefined(candidate.code)) + && (Is.string(candidate.source) || Is.undefined(candidate.source)); + } +} + + +/** + * Represents a reference to a command. Provides a title which + * will be used to represent a command in the UI and, optionally, + * an array of arguments which will be passed to the command handler + * function when invoked. + */ +export interface Command { + /** + * Title of the command, like `save`. + */ + title: string; + /** + * The identifier of the actual command handler. + */ + command: string; + /** + * Arguments that the command handler should be + * invoked with. + */ + arguments?: any[]; +} + + +/** + * The Command namespace provides helper functions to work with + * [Command](#Command) literals. + */ +export namespace Command { + /** + * Creates a new Command literal. + */ + export function create(title: string, command: string, ...args: any[]): Command { + let result: Command = { title, command }; + if (Is.defined(args) && args.length > 0) { + result.arguments = args; + } + return result; + } + /** + * Checks whether the given literal conforms to the [Command](#Command) interface. + */ + export function is(value: any): value is Command { + let candidate = value as Command; + return Is.defined(candidate) && Is.string(candidate.title) && Is.string(candidate.title); + } +} + +/** + * A text edit applicable to a text document. + */ +export interface TextEdit { + /** + * The range of the text document to be manipulated. To insert + * text into a document create a range where start === end. + */ + range: Range; + + /** + * The string to be inserted. For delete operations use an + * empty string. + */ + newText: string; +} + +/** + * The TextEdit namespace provides helper function to create replace, + * insert and delete edits more easily. + */ +export namespace TextEdit { + /** + * Creates a replace text edit. + * @param range The range of text to be replaced. + * @param newText The new text. + */ + export function replace(range: Range, newText: string): TextEdit { + return { range, newText }; + } + /** + * Creates a insert text edit. + * @param psotion The position to insert the text at. + * @param newText The text to be inserted. + */ + export function insert(position: Position, newText: string): TextEdit { + return { range: { start: position, end: position }, newText }; + } + /** + * Creates a delete text edit. + * @param range The range of text to be deleted. + */ + export function del(range: Range): TextEdit { + return { range, newText: '' }; + } +} + +/** + * A workspace edit represents changes to many resources managed + * in the workspace. + */ +export interface WorkspaceEdit { + // creates: { [uri: string]: string; }; + /** + * Holds changes to existing resources. + */ + changes: { [uri: string]: TextEdit[]; }; + // deletes: string[]; +} + +/** + * A change to capture text edits for existing resources. + */ +export interface TextEditChange { + /** + * Gets all text edits for this change. + * + * @return An array of text edits. + */ + all(): TextEdit[]; + + /** + * Clears the edits for this change. + */ + clear(): void; + + /** + * Insert the given text at the given position. + * + * @param position A position. + * @param newText A string. + */ + insert(position: Position, newText: string): void; + + /** + * Replace the given range with given text for the given resource. + * + * @param range A range. + * @param newText A string. + */ + replace(range: Range, newText: string): void; + + /** + * Delete the text at the given range. + * + * @param range A range. + */ + delete(range: Range): void; +} + +/** + * A workspace change helps constructing changes to a workspace. + */ +export class WorkspaceChange { + private workspaceEdit: WorkspaceEdit; + private textEditChanges: { [uri: string]: TextEditChange }; + + constructor() { + this.workspaceEdit = { + changes: Object.create(null) + }; + this.textEditChanges = Object.create(null); + } + + /** + * Returns the underlying [WorkspaceEdit](#WorkspaceEdit) literal + * use to be returned from a workspace edit operation like rename. + */ + public get edit(): WorkspaceEdit { + return this.workspaceEdit; + } + + /** + * Returns the [TextEditChange](#TextEditChange) to manage text edits + * for resources. + */ + public getTextEditChange(uri: string): TextEditChange { + class TextEditChangeImpl implements TextEditChange { + private edits: TextEdit[]; + constructor(edits: TextEdit[]) { + this.edits = edits; + } + insert(position: Position, newText: string): void { + this.edits.push(TextEdit.insert(position, newText)); + } + replace(range: Range, newText: string): void { + this.edits.push(TextEdit.replace(range, newText)); + } + delete(range: Range): void { + this.edits.push(TextEdit.del(range)); + } + all(): TextEdit[] { + return this.edits; + } + clear(): void { + this.edits.splice(0, this.edits.length); + } + } + let result = this.textEditChanges[uri]; + if (!result) { + let edits: TextEdit[] = []; + this.workspaceEdit.changes[uri] = edits; + result = new TextEditChangeImpl(edits); + this.textEditChanges[uri] = result; + } + return result; + } +} + +/** + * A literal to identify a text document in the client. + */ +export interface TextDocumentIdentifier { + /** + * The text document's uri. + */ + uri: string; +} + +/** + * The TextDocumentIdentifier namespace provides helper functions to work with + * [TextDocumentIdentifier](#TextDocumentIdentifier) literals. + */ +export namespace TextDocumentIdentifier { + /** + * Creates a new TextDocumentIdentifier literal. + * @param uri The document's uri. + */ + export function create(uri: string): TextDocumentIdentifier { + return { uri }; + } + /** + * Checks whether the given literal conforms to the [TextDocumentIdentifier](#TextDocumentIdentifier) interface. + */ + export function is(value: any): value is TextDocumentIdentifier { + let candidate = value as TextDocumentIdentifier; + return Is.defined(candidate) && Is.string(candidate.uri); + } +} + +/** + * An identifier to denote a specific version of a text document. + */ +export interface VersionedTextDocumentIdentifier extends TextDocumentIdentifier { + /** + * The version number of this document. + */ + version: number; +} + +/** + * The VersionedTextDocumentIdentifier namespace provides helper functions to work with + * [VersionedTextDocumentIdentifier](#VersionedTextDocumentIdentifier) literals. + */ +export namespace VersionedTextDocumentIdentifier { + /** + * Creates a new VersionedTextDocumentIdentifier literal. + * @param uri The document's uri. + * @param uri The document's text. + */ + export function create(uri: string, version: number): VersionedTextDocumentIdentifier { + return { uri, version }; + } + + /** + * Checks whether the given literal conforms to the [VersionedTextDocumentIdentifier](#VersionedTextDocumentIdentifier) interface. + */ + export function is(value: any): value is VersionedTextDocumentIdentifier { + let candidate = value as VersionedTextDocumentIdentifier; + return Is.defined(candidate) && Is.string(candidate.uri) && Is.number(candidate.version); + } +} + + +/** + * An item to transfer a text document from the client to the + * server. + */ +export interface TextDocumentItem { + /** + * The text document's uri. + */ + uri: string; + + /** + * The text document's language identifier + */ + languageId: string; + + /** + * The version number of this document (it will strictly increase after each + * change, including undo/redo). + */ + version: number; + + /** + * The content of the opened text document. + */ + text: string; +} + +/** + * The TextDocumentItem namespace provides helper functions to work with + * [TextDocumentItem](#TextDocumentItem) literals. + */ +export namespace TextDocumentItem { + /** + * Creates a new TextDocumentItem literal. + * @param uri The document's uri. + * @param uri The document's language identifier. + * @param uri The document's version number. + * @param uri The document's text. + */ + export function create(uri: string, languageId: string, version: number, text: string): TextDocumentItem { + return { uri, languageId, version, text }; + } + + /** + * Checks whether the given literal conforms to the [TextDocumentItem](#TextDocumentItem) interface. + */ + export function is(value: any): value is TextDocumentItem { + let candidate = value as TextDocumentItem; + return Is.defined(candidate) && Is.string(candidate.uri) && Is.string(candidate.languageId) && Is.number(candidate.version) && Is.string(candidate.text); + } +} + +/** + * The kind of a completion entry. + */ +export const enum CompletionItemKind { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18 +} + +/** + * A completion item represents a text snippet that is + * proposed to complete text that is being typed. + */ +export interface CompletionItem { + /** + * The label of this completion item. By default + * also the text that is inserted when selecting + * this completion. + */ + label: string; + + /** + * The kind of this completion item. Based of the kind + * an icon is chosen by the editor. + */ + kind?: number; + + /** + * A human-readable string with additional information + * about this item, like type or symbol information. + */ + detail?: string; + + /** + * A human-readable string that represents a doc-comment. + */ + documentation?: string; + + /** + * A string that shoud be used when comparing this item + * with other items. When `falsy` the [label](#CompletionItem.label) + * is used. + */ + sortText?: string; + + /** + * A string that should be used when filtering a set of + * completion items. When `falsy` the [label](#CompletionItem.label) + * is used. + */ + filterText?: string; + + /** + * A string that should be inserted a document when selecting + * this completion. When `falsy` the [label](#CompletionItem.label) + * is used. + */ + insertText?: string; + + /** + * An [edit](#TextEdit) which is applied to a document when selecting + * this completion. When an edit is provided the value of + * [insertText](#CompletionItem.insertText) is ignored. + */ + textEdit?: TextEdit; + + /** + * An optional array of additional [text edits](#TextEdit) that are applied when + * selecting this completion. Edits must not overlap with the main [edit](#CompletionItem.textEdit) + * nor with themselves. + */ + additionalTextEdits?: TextEdit[]; + + /** + * An optional [command](#Command) that is executed *after* inserting this completion. *Note* that + * additional modifications to the current document should be described with the + * [additionalTextEdits](#CompletionItem.additionalTextEdits)-property. + */ + command?: Command; + + /** + * An data entry field that is preserved on a completion item between + * a [CompletionRequest](#CompletionRequest) and a [CompletionResolveRequest] + * (#CompletionResolveRequest) + */ + data?: any +} + +/** + * The CompletionItem namespace provides functions to deal with + * completion items. + */ +export namespace CompletionItem { + /** + * Create a completion item and seed it with a label. + * @param label The completion item's label + */ + export function create(label: string): CompletionItem { + return { label }; + } +} + +/** + * Represents a collection of [completion items](#CompletionItem) to be presented + * in the editor. + */ +export interface CompletionList { + /** + * This list it not complete. Further typing should result in recomputing + * this list. + */ + isIncomplete: boolean; + + /** + * The completion items. + */ + items: CompletionItem[]; +} + +/** + * The CompletionList namespace provides functions to deal with + * completion lists. + */ +export namespace CompletionList { + /** + * Creates a new completion list. + * + * @param items The completion items. + * @param isIncomplete The list is not complete. + */ + export function create(items?: CompletionItem[], isIncomplete?: boolean): CompletionList { + return { items: items ? items : [], isIncomplete: !!isIncomplete }; + } +} + +/** + * MarkedString can be used to render human readable text. It is either a markdown string + * or a code-block that provides a language and a code snippet. Note that + * markdown strings will be sanitized - that means html will be escaped. + */ +export type MarkedString = string | { language: string; value: string }; + +export namespace MarkedString { + /** + * Creates a marked string from plain text. + * + * @param plainText The plain text. + */ + export function fromPlainText(plainText: string): MarkedString { + return plainText.replace(/[\\`*_{}[\]()#+\-.!]/g, "\\$&"); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash + } +} + +/** + * The result of a hove request. + */ +export interface Hover { + /** + * The hover's content + */ + contents: MarkedString | MarkedString[]; + + /** + * An optional range + */ + range?: Range; +} + +/** + * Represents a parameter of a callable-signature. A parameter can + * have a label and a doc-comment. + */ +export interface ParameterInformation { + /** + * The label of this signature. Will be shown in + * the UI. + */ + label: string; + + /** + * The human-readable doc-comment of this signature. Will be shown + * in the UI but can be omitted. + */ + documentation?: string; +} + +/** + * The ParameterInformation namespace provides helper functions to work with + * [ParameterInformation](#ParameterInformation) literals. + */ +export namespace ParameterInformation { + /** + * Creates a new parameter information literal. + * + * @param label A label string. + * @param documentation A doc string. + */ + export function create(label: string, documentation?: string): ParameterInformation { + return documentation ? { label, documentation } : { label }; + }; +} + +/** + * Represents the signature of something callable. A signature + * can have a label, like a function-name, a doc-comment, and + * a set of parameters. + */ +export interface SignatureInformation { + /** + * The label of this signature. Will be shown in + * the UI. + */ + label: string; + + /** + * The human-readable doc-comment of this signature. Will be shown + * in the UI but can be omitted. + */ + documentation?: string; + + /** + * The parameters of this signature. + */ + parameters?: ParameterInformation[]; +} + +/** + * The SignatureInformation namespace provides helper functions to work with + * [SignatureInformation](#SignatureInformation) literals. + */ +export namespace SignatureInformation { + export function create(label: string, documentation?: string, ...parameters: ParameterInformation[]): SignatureInformation { + let result: SignatureInformation = { label }; + if (Is.defined(documentation)) { + result.documentation = documentation; + } + if (Is.defined(parameters)) { + result.parameters = parameters; + } else { + result.parameters = []; + } + return result; + } +} + +/** + * Signature help represents the signature of something + * callable. There can be multiple signature but only one + * active and only one active parameter. + */ +export interface SignatureHelp { + /** + * One or more signatures. + */ + signatures: SignatureInformation[]; + + /** + * The active signature. + */ + activeSignature?: number; + + /** + * The active parameter of the active signature. + */ + activeParameter?: number; +} + +/** + * The definition of a symbol represented as one or many [locations](#Location). + * For most programming languages there is only one location at which a symbol is + * defined. + */ +export type Definition = Location | Location[]; + +/** + * Value-object that contains additional information when + * requesting references. + */ +export interface ReferenceContext { + /** + * Include the declaration of the current symbol. + */ + includeDeclaration: boolean; +} + + +/** + * A document highlight kind. + */ +export const enum DocumentHighlightKind { + /** + * A textual occurrance. + */ + Text = 1, + + /** + * Read-access of a symbol, like reading a variable. + */ + Read = 2, + + /** + * Write-access of a symbol, like writing to a variable. + */ + Write = 3 +} + +/** + * A document highlight is a range inside a text document which deserves + * special attention. Usually a document highlight is visualized by changing + * the background color of its range. + */ +export interface DocumentHighlight { + /** + * The range this highlight applies to. + */ + range: Range; + + /** + * The highlight kind, default is [text](#DocumentHighlightKind.Text). + */ + kind?: number; +} + +/** + * DocumentHighlight namespace to provide helper functions to work with + * [DocumentHighlight](#DocumentHighlight) literals. + */ +export namespace DocumentHighlight { + /** + * Create a DocumentHighlight object. + * @param range The range the highlight applies to. + */ + export function create(range: Range, kind?: number): DocumentHighlight { + let result: DocumentHighlight = { range }; + if (Is.number(kind)) { + result.kind = kind; + } + return result; + } +} + +/** + * A symbol kind. + */ +export const enum SymbolKind { + File = 1, + Module = 2, + Namespace = 3, + Package = 4, + Class = 5, + Method = 6, + Property = 7, + Field = 8, + Constructor = 9, + Enum = 10, + Interface = 11, + Function = 12, + Variable = 13, + Constant = 14, + String = 15, + Number = 16, + Boolean = 17, + Array = 18, +} + +/** + * Represents information about programming constructs like variables, classes, + * interfaces etc. + */ +export interface SymbolInformation { + /** + * The name of this symbol. + */ + name: string; + + /** + * The kind of this symbol. + */ + kind: number; + + /** + * The location of this symbol. + */ + location: Location; + + /** + * The name of the symbol containing this symbol. + */ + containerName?: string; +} + +export namespace SymbolInformation { + /** + * Creates a new symbol information literal. + * + * @param name The name of the symbol. + * @param kind The kind of the symbol. + * @param range The range of the location of the symbol. + * @param uri The resource of the location of symbol, defaults to the current document. + * @param containerName The name of the symbol containg the symbol. + */ + export function create(name: string, kind: SymbolKind, range: Range, uri?: string, containerName?: string): SymbolInformation { + let result: SymbolInformation = { + name, + kind, + location: { uri, range } + } + if (containerName) { + result.containerName = containerName; + } + return result; + } +} + +/** + * Parameters for a [DocumentSymbolRequest](#DocumentSymbolRequest). + */ +export interface DocumentSymbolParams { + /** + * The text document. + */ + textDocument: TextDocumentIdentifier; +} + +/** + * The parameters of a [WorkspaceSymbolRequest](#WorkspaceSymbolRequest). + */ +export interface WorkspaceSymbolParams { + /** + * A non-empty query string + */ + query: string; +} + +/** + * Contains additional diagnostic information about the context in which + * a [code action](#CodeActionProvider.provideCodeActions) is run. + */ +export interface CodeActionContext { + /** + * An array of diagnostics. + */ + diagnostics: Diagnostic[]; +} + +/** + * The CodeActionContext namespace provides helper functions to work with + * [CodeActionContext](#CodeActionContext) literals. + */ +export namespace CodeActionContext { + /** + * Creates a new CodeActionContext literal. + */ + export function create(diagnostics: Diagnostic[]): CodeActionContext { + return { diagnostics }; + } + /** + * Checks whether the given literal conforms to the [CodeActionContext](#CodeActionContext) interface. + */ + export function is(value: any): value is CodeActionContext { + let candidate = value as CodeActionContext; + return Is.defined(candidate) && Is.typedArray(candidate.diagnostics, Diagnostic.is); + } +} + +/** + * A code lens represents a [command](#Command) that should be shown along with + * source text, like the number of references, a way to run tests, etc. + * + * A code lens is _unresolved_ when no command is associated to it. For performance + * reasons the creation of a code lens and resolving should be done to two stages. + */ +export interface CodeLens { + /** + * The range in which this code lens is valid. Should only span a single line. + */ + range: Range; + + /** + * The command this code lens represents. + */ + command?: Command; + + /** + * An data entry field that is preserved on a code lens item between + * a [CodeLensRequest](#CodeLensRequest) and a [CodeLensResolveRequest] + * (#CodeLensResolveRequest) + */ + data?: any +} + +/** + * The CodeLens namespace provides helper functions to work with + * [CodeLens](#CodeLens) literals. + */ +export namespace CodeLens { + /** + * Creates a new CodeLens literal. + */ + export function create(range: Range, data?: any): CodeLens { + let result: CodeLens = { range }; + if (Is.defined(data)) result.data = data; + return result; + } + /** + * Checks whether the given literal conforms to the [CodeLens](#CodeLens) interface. + */ + export function is(value: any): value is CodeLens { + let candidate = value as CodeLens; + return Is.defined(candidate) && Range.is(candidate.range) && (Is.undefined(candidate.command) || Command.is(candidate.command)); + } +} + +/** + * Value-object describing what options formatting should use. + */ +export interface FormattingOptions { + /** + * Size of a tab in spaces. + */ + tabSize: number; + + /** + * Prefer spaces over tabs. + */ + insertSpaces: boolean; + + /** + * Signature for further properties. + */ + [key: string]: boolean | number | string; +} + +/** + * The FormattingOptions namespace provides helper functions to work with + * [FormattingOptions](#FormattingOptions) literals. + */ +export namespace FormattingOptions { + /** + * Creates a new FormattingOptions literal. + */ + export function create(tabSize: number, insertSpaces: boolean): FormattingOptions { + return { tabSize, insertSpaces }; + } + /** + * Checks whether the given literal conforms to the [FormattingOptions](#FormattingOptions) interface. + */ + export function is(value: any): value is FormattingOptions { + let candidate = value as FormattingOptions; + return Is.defined(candidate) && Is.number(candidate.tabSize) && Is.boolean(candidate.insertSpaces); + } +} + +/** + * A document link is a range in a text document that links to an internal or external resource, like another + * text document or a web site. + */ +export class DocumentLink { + + /** + * The range this link applies to. + */ + range: Range; + + /** + * The uri this link points to. + */ + target: string; +} + +/** + * The DocumentLink namespace provides helper functions to work with + * [DocumentLink](#DocumentLink) literals. + */ +export namespace DocumentLink { + /** + * Creates a new DocumentLink literal. + */ + export function create(range: Range, target?: string): DocumentLink { + return { range, target }; + } + + /** + * Checks whether the given literal conforms to the [DocumentLink](#DocumentLink) interface. + */ + export function is(value: any): value is DocumentLink { + let candidate = value as DocumentLink; + return Is.defined(candidate) && Range.is(candidate.range) && (Is.undefined(candidate.target) || Is.string(candidate.target)); + } +} + +/** + * A simple text document. Not to be implemenented. + */ +export interface TextDocument { + + /** + * The associated URI for this document. Most documents have the __file__-scheme, indicating that they + * represent files on disk. However, some documents may have other schemes indicating that they are not + * available on disk. + * + * @readonly + */ + uri: string; + + /** + * The identifier of the language associated with this document. + * + * @readonly + */ + languageId: string; + + /** + * The version number of this document (it will strictly increase after each + * change, including undo/redo). + * + * @readonly + */ + version: number; + + /** + * Get the text of this document. + * + * @return The text of this document. + */ + getText(): string; + + /** + * Converts a zero-based offset to a position. + * + * @param offset A zero-based offset. + * @return A valid [position](#Position). + */ + positionAt(offset: number): Position; + + /** + * Converts the position to a zero-based offset. + * + * The position will be [adjusted](#TextDocument.validatePosition). + * + * @param position A position. + * @return A valid zero-based offset. + */ + offsetAt(position: Position): number; + + /** + * The number of lines in this document. + * + * @readonly + */ + lineCount: number; +} + +export namespace TextDocument { + /** + * Creates a new ITextDocument literal from the given uri and content. + * @param uri The document's uri. + * @param languageId The document's language Id. + * @param content The document's content. + */ + export function create(uri: string, languageId: string, version: number, content: string): TextDocument { + return new FullTextDocument(uri, languageId, version, content); + } + /** + * Checks whether the given literal conforms to the [ITextDocument](#ITextDocument) interface. + */ + export function is(value: any): value is TextDocument { + let candidate = value as TextDocument; + return Is.defined(candidate) && Is.string(candidate.uri) && (Is.undefined(candidate.languageId) || Is.string(candidate.languageId)) && Is.number(candidate.lineCount) + && Is.func(candidate.getText) && Is.func(candidate.positionAt) && Is.func(candidate.offsetAt) ? true : false; + } +} + +/** + * Event to signal changes to a simple text document. + */ +export interface TextDocumentChangeEvent { + /** + * The document that has changed. + */ + document: TextDocument; +} + +/** + * An event describing a change to a text document. If range and rangeLength are omitted + * the new text is considered to be the full content of the document. + */ +export interface TextDocumentContentChangeEvent { + /** + * The range of the document that changed. + */ + range?: Range; + + /** + * The length of the range that got replaced. + */ + rangeLength?: number; + + /** + * The new text of the document. + */ + text: string; +} + +class FullTextDocument implements TextDocument { + + private _uri: string; + private _languageId: string; + private _version: number; + private _content: string; + private _lineOffsets: number[]; + + public constructor(uri: string, languageId: string, version: number, content: string) { + this._uri = uri; + this._languageId = languageId; + this._version = version; + this._content = content; + this._lineOffsets = null; + } + + public get uri(): string { + return this._uri; + } + + public get languageId(): string { + return this._languageId; + } + + public get version(): number { + return this._version; + } + + public getText(): string { + return this._content; + } + + public update(event: TextDocumentContentChangeEvent, version: number): void { + this._content = event.text; + this._version = version; + this._lineOffsets = null; + } + + private getLineOffsets(): number[] { + if (this._lineOffsets === null) { + let lineOffsets: number[] = []; + let text = this._content; + let isLineStart = true; + for (let i = 0; i < text.length; i++) { + if (isLineStart) { + lineOffsets.push(i); + isLineStart = false; + } + let ch = text.charAt(i); + isLineStart = (ch === '\r' || ch === '\n'); + if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') { + i++; + } + } + if (isLineStart && text.length > 0) { + lineOffsets.push(text.length); + } + this._lineOffsets = lineOffsets; + } + return this._lineOffsets; + } + + public positionAt(offset: number) { + offset = Math.max(Math.min(offset, this._content.length), 0); + + let lineOffsets = this.getLineOffsets(); + let low = 0, high = lineOffsets.length; + if (high === 0) { + return Position.create(0, offset); + } + while (low < high) { + let mid = Math.floor((low + high) / 2); + if (lineOffsets[mid] > offset) { + high = mid; + } else { + low = mid + 1; + } + } + // low is the least x for which the line offset is larger than the current offset + // or array.length if no line offset is larger than the current offset + let line = low - 1; + return Position.create(line, offset - lineOffsets[line]); + } + + public offsetAt(position: Position) { + let lineOffsets = this.getLineOffsets(); + if (position.line >= lineOffsets.length) { + return this._content.length; + } else if (position.line < 0) { + return 0; + } + let lineOffset = lineOffsets[position.line]; + let nextLineOffset = (position.line + 1 < lineOffsets.length) ? lineOffsets[position.line + 1] : this._content.length; + return Math.max(Math.min(lineOffset + position.character, nextLineOffset), lineOffset); + } + + public get lineCount() { + return this.getLineOffsets().length; + } +} + +namespace Is { + + 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 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 func(value: any): value is Function { + return toString.call(value) === '[object Function]'; + } + + export function typedArray(value: any, check: (value: any) => boolean): value is T[] { + return Array.isArray(value) && (value).every(check); + } + +} diff --git a/dataprotocol-node/types/src/tsconfig.json b/dataprotocol-node/types/src/tsconfig.json new file mode 100644 index 0000000000..c8c604f238 --- /dev/null +++ b/dataprotocol-node/types/src/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES5", + "module": "umd", + "sourceMap": false, + "declaration": true, + "stripInternal": true, + "outDir": "../lib" + } +} \ No newline at end of file diff --git a/dataprotocol-node/types/src/typings/promise.d.ts b/dataprotocol-node/types/src/typings/promise.d.ts new file mode 100644 index 0000000000..925c943df5 --- /dev/null +++ b/dataprotocol-node/types/src/typings/promise.d.ts @@ -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 { + /** + * 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(onfulfilled?: (value: R) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Thenable; + then(onfulfilled?: (value: R) => TResult | Thenable, onrejected?: (reason: any) => void): Thenable; +} + +/** + * Represents the completion of an asynchronous operation + */ +interface Promise extends Thenable { + /** + * 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(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Promise; + then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => void): Promise; + + /** + * 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): Promise; +} + +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 (executor: (resolve: (value?: T | Thenable) => void, reject: (reason?: any) => void) => void): Promise; + + /** + * 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(values: Array>): Promise; + + /** + * 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(values: Array>): Promise; + + /** + * 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; + + /** + * 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; + + /** + * Creates a new resolved promise for the provided value. + * @param value A promise. + * @returns A promise whose internal state matches the provided promise. + */ + resolve(value: T | Thenable): Promise; + + /** + * Creates a new resolved promise . + * @returns A resolved promise. + */ + resolve(): Promise; +} + +declare var Promise: PromiseConstructor;