diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js
index d4eb9fed61..8e72f637f2 100644
--- a/build/gulpfile.hygiene.js
+++ b/build/gulpfile.hygiene.js
@@ -172,6 +172,7 @@ const copyrightFilter = [
'!extensions/import/flatfileimportservice/**',
'!extensions/notebook/src/prompts/**',
'!extensions/mssql/src/prompts/**',
+ '!extensions/kusto/src/prompts/**',
'!extensions/notebook/resources/jupyter_config/**',
'!extensions/query-history/images/**',
'!**/*.gif',
diff --git a/build/lib/extensions.js b/build/lib/extensions.js
index fb143c6a84..a3f2f7ebec 100644
--- a/build/lib/extensions.js
+++ b/build/lib/extensions.js
@@ -218,6 +218,7 @@ const externalExtensions = [
'schema-compare',
'cms',
'query-history',
+ 'kusto',
'liveshare',
'sql-database-projects',
'machine-learning',
diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts
index bcda51df74..4dfededc6d 100644
--- a/build/lib/extensions.ts
+++ b/build/lib/extensions.ts
@@ -252,6 +252,7 @@ const externalExtensions = [
'schema-compare',
'cms',
'query-history',
+ 'kusto',
'liveshare',
'sql-database-projects',
'machine-learning',
diff --git a/extensions/kusto/.gitignore b/extensions/kusto/.gitignore
new file mode 100644
index 0000000000..e58344bcee
--- /dev/null
+++ b/extensions/kusto/.gitignore
@@ -0,0 +1 @@
+sqltoolsservice
diff --git a/extensions/kusto/.vscodeignore b/extensions/kusto/.vscodeignore
new file mode 100644
index 0000000000..f3d973bca1
--- /dev/null
+++ b/extensions/kusto/.vscodeignore
@@ -0,0 +1,6 @@
+src/**
+out/**
+tsconfig.json
+extension.webpack.config.js
+yarn.lock
+.vscode
diff --git a/extensions/kusto/config.json b/extensions/kusto/config.json
new file mode 100644
index 0000000000..8c0f497834
--- /dev/null
+++ b/extensions/kusto/config.json
@@ -0,0 +1,26 @@
+{
+ "downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
+ "version": "2.0.0-release.15",
+ "downloadFileNames": {
+ "Windows_86": "win-x86-netcoreapp2.2.zip",
+ "Windows_64": "win-x64-netcoreapp2.2.zip",
+ "OSX": "osx-x64-netcoreapp2.2.tar.gz",
+ "CentOS_7": "rhel-x64-netcoreapp2.2.tar.gz",
+ "Debian_8": "rhel-x64-netcoreapp2.2.tar.gz",
+ "Fedora_23": "rhel-x64-netcoreapp2.2.tar.gz",
+ "OpenSUSE_13_2": "rhel-x64-netcoreapp2.2.tar.gz",
+ "RHEL_7": "rhel-x64-netcoreapp2.2.tar.gz",
+ "SLES_12_2": "rhel-x64-netcoreapp2.2.tar.gz",
+ "Ubuntu_14": "rhel-x64-netcoreapp2.2.tar.gz",
+ "Ubuntu_16": "rhel-x64-netcoreapp2.2.tar.gz"
+ },
+ "installDirectory": "../sqltoolsservice/{#platform#}/{#version#}",
+ "executableFiles": ["MicrosoftKustoServiceLayer.exe", "MicrosoftKustoServiceLayer"],
+ "retry": {
+ "retries": 15,
+ "factor": 2,
+ "minTimeout": 1000,
+ "maxTimeout": 300000,
+ "randomize": false
+ }
+}
diff --git a/extensions/kusto/extension.webpack.config.js b/extensions/kusto/extension.webpack.config.js
new file mode 100644
index 0000000000..b63c59c65d
--- /dev/null
+++ b/extensions/kusto/extension.webpack.config.js
@@ -0,0 +1,17 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+//@ts-check
+
+'use strict';
+
+const withDefaults = require('../shared.webpack.config');
+
+module.exports = withDefaults({
+ context: __dirname,
+ entry: {
+ main: './src/main.ts'
+ }
+});
diff --git a/extensions/kusto/language-configuration.json b/extensions/kusto/language-configuration.json
new file mode 100644
index 0000000000..358d2c8384
--- /dev/null
+++ b/extensions/kusto/language-configuration.json
@@ -0,0 +1,73 @@
+// If changing this file please also submit the change in kusto-language-server language-configuration.json
+{
+ "comments": {
+ // symbol used for single line comment. Remove this entry if your language does not support line comments
+ "lineComment": "//",
+ // symbols used for start and end a block comment. Remove this entry if your language does not support block comments
+ "blockComment": [
+ "/*",
+ "*/"
+ ]
+ },
+ // symbols used as brackets
+ "brackets": [
+ [
+ "{",
+ "}"
+ ],
+ [
+ "[",
+ "]"
+ ],
+ [
+ "(",
+ ")"
+ ]
+ ],
+ // symbols that are auto closed when typing
+ "autoClosingPairs": [
+ [
+ "{",
+ "}"
+ ],
+ [
+ "[",
+ "]"
+ ],
+ [
+ "(",
+ ")"
+ ],
+ [
+ "\"",
+ "\""
+ ],
+ [
+ "'",
+ "'"
+ ]
+ ],
+ // symbols that that can be used to surround a selection
+ "surroundingPairs": [
+ [
+ "{",
+ "}"
+ ],
+ [
+ "[",
+ "]"
+ ],
+ [
+ "(",
+ ")"
+ ],
+ [
+ "\"",
+ "\""
+ ],
+ [
+ "'",
+ "'"
+ ]
+ ]
+}
diff --git a/extensions/kusto/package.json b/extensions/kusto/package.json
new file mode 100644
index 0000000000..36553da217
--- /dev/null
+++ b/extensions/kusto/package.json
@@ -0,0 +1,418 @@
+{
+ "name": "kusto",
+ "version": "0.1.0",
+ "publisher": "Microsoft",
+ "aiKey": "AIF-444c3af9-8e69-4462-ab49-4191e6ad1916",
+ "activationEvents": [
+ "*"
+ ],
+ "engines": {
+ "vscode": "*",
+ "azdata": ">=1.22.0"
+ },
+ "main": "./out/main",
+ "typings": "./src/kusto",
+ "scripts": {
+ "compile": "gulp compile-extension:kusto-client",
+ "update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-kusto syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json"
+ },
+ "contributes": {
+ "commands": [
+ {
+ "command": "kustoCluster.task.newNotebook",
+ "title": "%notebook.command.new%",
+ "icon": {
+ "dark": "resources/dark/new_notebook_inverse.svg",
+ "light": "resources/light/new_notebook.svg"
+ }
+ },
+ {
+ "command": "kustoCluster.task.openNotebook",
+ "title": "%notebook.command.open%",
+ "icon": {
+ "dark": "resources/dark/open_notebook_inverse.svg",
+ "light": "resources/light/open_notebook.svg"
+ }
+ }
+ ],
+ "languages": [
+ {
+ "id": "kusto",
+ "aliases": [
+ "Kusto",
+ "kusto"
+ ],
+ "extensions": [
+ ".kql",
+ ".kusto",
+ ".csl"
+ ],
+ "configuration": "./language-configuration.json"
+ }
+ ],
+ "grammars": [
+ {
+ "language": "kusto",
+ "scopeName": "source.kusto",
+ "path": "./syntaxes/kusto.tmLanguage"
+ }
+ ],
+ "themes": [
+ {
+ "label": "[Kuskus] Kusto (Dark)",
+ "uiTheme": "vs-dark",
+ "path": "./themes/kuskus-kusto-dark.json"
+ }
+ ],
+ "outputChannels": [
+ "Kusto"
+ ],
+ "snippets": [
+ {
+ "language": "kusto",
+ "path": "./snippets/kusto.json"
+ }
+ ],
+ "configuration": {
+ "type": "object",
+ "title": "%kusto.configuration.title%",
+ "properties": {
+ "kusto.query.displayBitAsNumber": {
+ "type": "boolean",
+ "default": true,
+ "description": "%kusto.query.displayBitAsNumber%"
+ },
+ "kusto.format.alignColumnDefinitionsInColumns": {
+ "type": "boolean",
+ "description": "%kusto.format.alignColumnDefinitionsInColumns%",
+ "default": false
+ },
+ "kusto.format.datatypeCasing": {
+ "type": "string",
+ "description": "%kusto.format.datatypeCasing%",
+ "default": "none",
+ "enum": [
+ "none",
+ "uppercase",
+ "lowercase"
+ ]
+ },
+ "kusto.format.keywordCasing": {
+ "type": "string",
+ "description": "%kusto.format.keywordCasing%",
+ "default": "none",
+ "enum": [
+ "none",
+ "uppercase",
+ "lowercase"
+ ]
+ },
+ "kusto.logDebugInfo": {
+ "type": "boolean",
+ "default": false,
+ "description": "%kusto.logDebugInfo%"
+ },
+ "kusto.tracingLevel": {
+ "type": "string",
+ "description": "%kusto.tracingLevel%",
+ "default": "Critical",
+ "enum": [
+ "All",
+ "Off",
+ "Critical",
+ "Error",
+ "Warning",
+ "Information",
+ "Verbose"
+ ]
+ }
+ }
+ },
+ "menus": {
+ "commandPalette": [
+ {
+ "command": "kustoCluster.task.newNotebook",
+ "when": "false"
+ },
+ {
+ "command": "kustoCluster.task.openNotebook",
+ "when": "false"
+ }
+ ]
+ },
+ "dashboard": {
+ "provider": "KUSTO",
+ "flavors": [
+ {
+ "flavor": "cloud",
+ "conditions": [
+ {
+ "field": "isCloud",
+ "operator": "==",
+ "value": true
+ }
+ ],
+ "databaseProperties": [
+ {
+ "displayName": "%cloud.databaseProperties.name%",
+ "value": "name"
+ },
+ {
+ "displayName": "%cloud.databaseProperties.size%",
+ "value": "sizeInMB"
+ }
+ ],
+ "serverProperties": [
+ {
+ "displayName": "%cloud.serverProperties.summary%",
+ "value": "summary"
+ },
+ {
+ "displayName": "%cloud.serverProperties.machinesTotal%",
+ "value": "machinesTotal"
+ },
+ {
+ "displayName": "%cloud.serverProperties.diskCacheCapacity%",
+ "value": "diskCacheCapacity"
+ }
+ ],
+ "databasesListProperties": [
+ {
+ "displayName": "%databasesListProperties.name%",
+ "value": "name",
+ "widthWeight": 60
+ },
+ {
+ "displayName": "%databasesListProperties.size%",
+ "value": "sizeInMB",
+ "widthWeight": 20
+ }
+ ],
+ "objectsListProperties": [
+ {
+ "displayName": "%objectsListProperties.name%",
+ "value": "name",
+ "widthWeight": 60
+ },
+ {
+ "displayName": "%objectsListProperties.metadataTypeName%",
+ "value": "metadataTypeName",
+ "widthWeight": 20
+ }
+ ]
+ }
+ ]
+ },
+ "connectionProvider": {
+ "providerId": "KUSTO",
+ "languageMode": "kusto",
+ "notebookKernelAlias": "Kusto",
+ "displayName": "%kusto.provider.displayName%",
+ "iconPath": [
+ {
+ "id": "kusto:cloud",
+ "path": {
+ "light": "resources/light/azureDE.svg",
+ "dark": "resources/dark/azureDE_inverse.svg"
+ }
+ },
+ {
+ "id": "kusto:cluster",
+ "path": {
+ "light": "resources/light/sql_bigdata_cluster.svg",
+ "dark": "resources/dark/sql_bigdata_cluster_inverse.svg"
+ }
+ }
+ ],
+ "connectionOptions": [
+ {
+ "specialValueType": "connectionName",
+ "isIdentity": true,
+ "name": "connectionName",
+ "displayName": "%kusto.connectionOptions.connectionName.displayName%",
+ "description": "%kusto.connectionOptions.connectionName.description%",
+ "groupName": "Source",
+ "valueType": "string",
+ "defaultValue": null,
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": false,
+ "isArray": false
+ },
+ {
+ "specialValueType": "serverName",
+ "isIdentity": true,
+ "name": "server",
+ "displayName": "%kusto.connectionOptions.serverName.displayName%",
+ "description": "%kusto.connectionOptions.serverName.description%",
+ "groupName": "Source",
+ "valueType": "string",
+ "defaultValue": null,
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": true,
+ "isArray": false
+ },
+ {
+ "specialValueType": "databaseName",
+ "isIdentity": true,
+ "name": "database",
+ "displayName": "%kusto.connectionOptions.databaseName.displayName%",
+ "description": "%kusto.connectionOptions.databaseName.description%",
+ "groupName": "Source",
+ "valueType": "string",
+ "defaultValue": null,
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": false,
+ "isArray": false
+ },
+ {
+ "specialValueType": "authType",
+ "isIdentity": true,
+ "name": "authenticationType",
+ "displayName": "%kusto.connectionOptions.authType.displayName%",
+ "description": "%kusto.connectionOptions.authType.description%",
+ "groupName": "Security",
+ "valueType": "category",
+ "defaultValue": "AzureMFA",
+ "objectType": null,
+ "categoryValues": [
+ {
+ "displayName": "%kusto.connectionOptions.authType.categoryValues.azureMFA%",
+ "name": "AzureMFA"
+ }
+ ],
+ "isRequired": true,
+ "isArray": false
+ },
+ {
+ "specialValueType": "userName",
+ "isIdentity": true,
+ "name": "user",
+ "displayName": "%kusto.connectionOptions.userName.displayName%",
+ "description": "%kusto.connectionOptions.userName.description%",
+ "groupName": "Security",
+ "valueType": "string",
+ "defaultValue": null,
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": true,
+ "isArray": false
+ },
+ {
+ "specialValueType": "password",
+ "isIdentity": true,
+ "name": "password",
+ "displayName": "%kusto.connectionOptions.password.displayName%",
+ "description": "%kusto.connectionOptions.password.description%",
+ "groupName": "Security",
+ "valueType": "password",
+ "defaultValue": null,
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": true,
+ "isArray": false
+ },
+ {
+ "specialValueType": null,
+ "isIdentity": false,
+ "name": "connectTimeout",
+ "displayName": "%kusto.connectionOptions.connectTimeout.displayName%",
+ "description": "%kusto.connectionOptions.connectTimeout.description%",
+ "groupName": "Initialization",
+ "valueType": "number",
+ "defaultValue": "30",
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": false,
+ "isArray": false
+ },
+ {
+ "specialValueType": null,
+ "isIdentity": false,
+ "name": "connectRetryCount",
+ "displayName": "%kusto.connectionOptions.connectRetryCount.displayName%",
+ "description": "%kusto.connectionOptions.connectRetryCount.description%",
+ "groupName": "Connection Resiliency",
+ "valueType": "number",
+ "defaultValue": "1",
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": false,
+ "isArray": false
+ },
+ {
+ "specialValueType": null,
+ "isIdentity": false,
+ "name": "connectRetryInterval",
+ "displayName": "%kusto.connectionOptions.connectRetryInterval.displayName%",
+ "description": "%kusto.connectionOptions.connectRetryInterval.description%",
+ "groupName": "Connection Resiliency",
+ "valueType": "number",
+ "defaultValue": "10",
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": false,
+ "isArray": false
+ },
+ {
+ "specialValueType": "appName",
+ "isIdentity": false,
+ "name": "applicationName",
+ "displayName": "%kusto.connectionOptions.applicationName.displayName%",
+ "description": "%kusto.connectionOptions.applicationName.description%",
+ "groupName": "Context",
+ "valueType": "string",
+ "defaultValue": null,
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": false,
+ "isArray": false
+ },
+ {
+ "specialValueType": null,
+ "isIdentity": false,
+ "name": "workstationId",
+ "displayName": "%kusto.connectionOptions.workstationId.displayName%",
+ "description": "%kusto.connectionOptions.workstationId.description%",
+ "groupName": "Context",
+ "valueType": "string",
+ "defaultValue": null,
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": false,
+ "isArray": false
+ },
+ {
+ "specialValueType": null,
+ "isIdentity": false,
+ "name": "failoverPartner",
+ "displayName": "%kusto.connectionOptions.failoverPartner.displayName%",
+ "description": "%kusto.connectionOptions.failoverPartner.description%",
+ "groupName": " Source",
+ "valueType": "string",
+ "defaultValue": null,
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": false,
+ "isArray": false
+ }
+ ]
+ }
+ },
+ "dependencies": {
+ "dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#1.1.0",
+ "figures": "^2.0.0",
+ "find-remove": "1.2.1",
+ "service-downloader": "github:anthonydresser/service-downloader#0.1.6",
+ "vscode-extension-telemetry": "0.1.0",
+ "vscode-languageclient": "5.2.1",
+ "vscode-nls": "^4.0.0"
+ },
+ "devDependencies": {
+ "@types/kerberos": "^1.1.0",
+ "@types/request": "^2.48.2",
+ "@types/through2": "^2.0.34"
+ }
+}
diff --git a/extensions/kusto/package.nls.json b/extensions/kusto/package.nls.json
new file mode 100644
index 0000000000..36937c247b
--- /dev/null
+++ b/extensions/kusto/package.nls.json
@@ -0,0 +1,52 @@
+{
+ "notebook.command.new": "New Notebook",
+ "notebook.command.open": "Open Notebook",
+
+ "cloud.databaseProperties.name": "Database Name",
+ "cloud.databaseProperties.size": "Size (MB)",
+
+ "cloud.serverProperties.summary": "Status",
+ "cloud.serverProperties.machinesTotal": "Total Machines in the cluster",
+ "cloud.serverProperties.diskCacheCapacity": "% of Cluster data capacity used",
+
+ "databasesListProperties.name": "Name",
+ "databasesListProperties.size": "Size (MB)",
+
+ "objectsListProperties.name": "Name",
+ "objectsListProperties.metadataTypeName": "Type",
+
+ "kusto.configuration.title": "KUSTO configuration",
+ "kusto.query.displayBitAsNumber": "Should BIT columns be displayed as numbers (1 or 0)? If false, BIT columns will be displayed as 'true' or 'false'",
+ "kusto.format.alignColumnDefinitionsInColumns": "Should column definitions be aligned?",
+ "kusto.format.datatypeCasing": "Should data types be formatted as UPPERCASE, lowercase, or none (not formatted)",
+ "kusto.format.keywordCasing": "Should keywords be formatted as UPPERCASE, lowercase, or none (not formatted)",
+ "kusto.logDebugInfo": "[Optional] Log debug output to the console (View -> Output) and then select appropriate output channel from the dropdown",
+ "kusto.tracingLevel": "[Optional] Log level for backend services. Azure Data Studio generates a file name every time it starts and if the file already exists the logs entries are appended to that file. For cleanup of old log files see logRetentionMinutes and logFilesRemovalLimit settings. The default tracingLevel does not log much. Changing verbosity could lead to extensive logging and disk space requirements for the logs. Error includes Critical, Warning includes Error, Information includes Warning and Verbose includes Information",
+
+ "kusto.provider.displayName": "Kusto",
+ "kusto.connectionOptions.connectionName.displayName": "Name (optional)",
+ "kusto.connectionOptions.connectionName.description": "Custom name of the connection",
+ "kusto.connectionOptions.serverName.displayName": "Cluster",
+ "kusto.connectionOptions.serverName.description": "Kusto cluster name",
+ "kusto.connectionOptions.databaseName.displayName": "Database",
+ "kusto.connectionOptions.databaseName.description": "The name of the initial catalog or database in the data source",
+ "kusto.connectionOptions.authType.displayName": "Authentication type",
+ "kusto.connectionOptions.authType.description": "Specifies the method of authenticating with Kusto Server",
+ "kusto.connectionOptions.authType.categoryValues.azureMFA": "Azure Active Directory - Universal with MFA support",
+ "kusto.connectionOptions.userName.displayName": "User name",
+ "kusto.connectionOptions.userName.description": "Indicates the user ID to be used when connecting to the data source",
+ "kusto.connectionOptions.password.displayName": "Password",
+ "kusto.connectionOptions.password.description": "Indicates the password to be used when connecting to the data source",
+ "kusto.connectionOptions.connectTimeout.displayName": "Connect timeout",
+ "kusto.connectionOptions.connectTimeout.description": "The length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error",
+ "kusto.connectionOptions.connectRetryCount.displayName": "Connect retry count",
+ "kusto.connectionOptions.connectRetryCount.description": "Number of attempts to restore connection",
+ "kusto.connectionOptions.connectRetryInterval.displayName": "Connect retry interval",
+ "kusto.connectionOptions.connectRetryInterval.description": "Delay between attempts to restore connection",
+ "kusto.connectionOptions.applicationName.displayName": "Application name",
+ "kusto.connectionOptions.applicationName.description": "The name of the application",
+ "kusto.connectionOptions.workstationId.displayName": "Workstation Id",
+ "kusto.connectionOptions.workstationId.description": "The name of the workstation connecting to Kusto Server",
+ "kusto.connectionOptions.failoverPartner.displayName": "Failover partner",
+ "kusto.connectionOptions.failoverPartner.description": "The name or network address of the instance of Kusto Server that acts as a failover partner"
+}
diff --git a/extensions/kusto/resources/dark/azureDE_inverse.svg b/extensions/kusto/resources/dark/azureDE_inverse.svg
new file mode 100644
index 0000000000..e6f96e5442
--- /dev/null
+++ b/extensions/kusto/resources/dark/azureDE_inverse.svg
@@ -0,0 +1,3 @@
+
diff --git a/extensions/kusto/resources/dark/new_notebook_inverse.svg b/extensions/kusto/resources/dark/new_notebook_inverse.svg
new file mode 100644
index 0000000000..e0072afee1
--- /dev/null
+++ b/extensions/kusto/resources/dark/new_notebook_inverse.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/kusto/resources/dark/open_notebook_inverse.svg b/extensions/kusto/resources/dark/open_notebook_inverse.svg
new file mode 100644
index 0000000000..a95750c49f
--- /dev/null
+++ b/extensions/kusto/resources/dark/open_notebook_inverse.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/kusto/resources/light/azureDE.svg b/extensions/kusto/resources/light/azureDE.svg
new file mode 100644
index 0000000000..d6caba532a
--- /dev/null
+++ b/extensions/kusto/resources/light/azureDE.svg
@@ -0,0 +1,3 @@
+
diff --git a/extensions/kusto/resources/light/new_notebook.svg b/extensions/kusto/resources/light/new_notebook.svg
new file mode 100644
index 0000000000..9618487568
--- /dev/null
+++ b/extensions/kusto/resources/light/new_notebook.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/kusto/resources/light/open_notebook.svg b/extensions/kusto/resources/light/open_notebook.svg
new file mode 100644
index 0000000000..0041ae9b21
--- /dev/null
+++ b/extensions/kusto/resources/light/open_notebook.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/kusto/snippets/kusto.json b/extensions/kusto/snippets/kusto.json
new file mode 100644
index 0000000000..f7087a2d6b
--- /dev/null
+++ b/extensions/kusto/snippets/kusto.json
@@ -0,0 +1,10 @@
+{
+ "Top N by count": {
+ "prefix": "kustoTopNested",
+ "body": [
+ "// Get top ${1:N} of column: ${2:ColumnName} by count",
+ "top-nested ${1:N} of ${2:ColumnName} by agg_${2:ColumnName}=count() desc"
+ ],
+ "description": "Get the top N of a column by the count of rows"
+ }
+}
diff --git a/extensions/kusto/src/appContext.ts b/extensions/kusto/src/appContext.ts
new file mode 100644
index 0000000000..0fc5eb23d3
--- /dev/null
+++ b/extensions/kusto/src/appContext.ts
@@ -0,0 +1,34 @@
+/*---------------------------------------------------------------------------------------------
+ * 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 vscode from 'vscode';
+
+/**
+ * Global context for the application
+ */
+export class AppContext {
+
+ private serviceMap: Map = new Map();
+
+ constructor(public readonly extensionContext: vscode.ExtensionContext) { }
+
+ public getService(serviceName: string): T | undefined {
+ const service = this.serviceMap.get(serviceName) as T;
+ if (!service) {
+ console.warn('Service ', serviceName, ' is not registered');
+ }
+ return service;
+ }
+
+ public registerService(serviceName: string, service: T): void {
+ if (this.serviceMap.has(serviceName)) {
+ console.warn('Multiple services ', serviceName, ' registered!');
+ } else {
+ this.serviceMap.set(serviceName, service);
+ }
+ }
+}
diff --git a/extensions/kusto/src/constants.ts b/extensions/kusto/src/constants.ts
new file mode 100644
index 0000000000..11bc89a901
--- /dev/null
+++ b/extensions/kusto/src/constants.ts
@@ -0,0 +1,48 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+export const serviceName = 'Kusto service';
+export const providerId = 'KUSTO';
+export const serviceCrashLink = 'https://github.com/Microsoft/vscode-kusto/wiki/SqlToolsService-Known-Issues';
+export const extensionConfigSectionName = 'kusto';
+
+// DATA PROTOCOL VALUES ///////////////////////////////////////////////////////////
+export const kustoClusterProviderName = 'kustoCluster';
+export const protocolVersion = '1.0';
+export const authenticationTypePropName = 'authenticationType';
+export const integratedAuth = 'integrated';
+export const serverPropName = 'server';
+export const userPropName = 'user';
+export const passwordPropName = 'password';
+export const groupIdPropName = 'groupId';
+export const groupIdName = 'groupId';
+export const kustoProviderName = 'KUSTO';
+
+export const UNTITLED_SCHEMA = 'untitled';
+
+export const clusterEndpointsProperty = 'clusterEndpoints';
+export const hdfsRootPath = '/';
+
+// SERVICE NAMES //////////////////////////////////////////////////////////
+export const ObjectExplorerService = 'objectexplorer';
+export const objectExplorerPrefix: string = 'objectexplorer://';
+export const ViewType = 'view';
+
+export enum BuiltInCommands {
+ SetContext = 'setContext'
+}
+
+export enum CommandContext {
+ WizardServiceEnabled = 'wizardservice:enabled'
+}
+
+export enum KustoClusterItems {
+ Connection = 'kustoCluster:connection',
+ Folder = 'kustoCluster:folder',
+ File = 'kustoCluster:file',
+ Error = 'kustoCluster:error'
+}
+
+export const kustoClusterNewNotebookTask = 'kustoCluster.task.newNotebook';
+export const kustoClusterOpenNotebookTask = 'kustoCluster.task.openNotebook';
diff --git a/extensions/kusto/src/contextProvider.ts b/extensions/kusto/src/contextProvider.ts
new file mode 100644
index 0000000000..b0ec4f6a3b
--- /dev/null
+++ b/extensions/kusto/src/contextProvider.ts
@@ -0,0 +1,75 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+import * as vscode from 'vscode';
+import * as azdata from 'azdata';
+
+import * as types from './types';
+
+export enum BuiltInCommands {
+ SetContext = 'setContext',
+}
+
+export enum ContextKeys {
+ ISCLOUD = 'kusto:iscloud',
+ EDITIONID = 'kusto:engineedition',
+ ISCLUSTER = 'kusto:iscluster',
+ SERVERMAJORVERSION = 'kusto:servermajorversion'
+}
+
+const isCloudEditions = [
+ 5,
+ 6
+];
+
+export function setCommandContext(key: ContextKeys | string, value: any) {
+ return vscode.commands.executeCommand(BuiltInCommands.SetContext, key, value);
+}
+
+export default class ContextProvider {
+ private _disposables = new Array();
+
+ constructor() {
+ this._disposables.push(azdata.workspace.onDidOpenDashboard(this.onDashboardOpen, this));
+ this._disposables.push(azdata.workspace.onDidChangeToDashboard(this.onDashboardOpen, this));
+ }
+
+ public onDashboardOpen(e: azdata.DashboardDocument): void {
+ let iscloud: boolean = false;
+ let edition: number | undefined;
+ let isCluster: boolean = false; // TODO: Do we even need this for Kusto
+ let serverMajorVersion: number | undefined;
+ if (e.profile.providerName.toLowerCase() === 'kusto' && !types.isUndefinedOrNull(e.serverInfo) && !types.isUndefinedOrNull(e.serverInfo.engineEditionId)) {
+ if (isCloudEditions.some(i => i === e.serverInfo.engineEditionId)) {
+ iscloud = true;
+ } else {
+ iscloud = false;
+ }
+
+ edition = e.serverInfo.engineEditionId;
+
+ serverMajorVersion = e.serverInfo.serverMajorVersion;
+ }
+
+ if (iscloud === true || iscloud === false) {
+ setCommandContext(ContextKeys.ISCLOUD, iscloud);
+ }
+
+ if (!types.isUndefinedOrNull(edition)) {
+ setCommandContext(ContextKeys.EDITIONID, edition);
+ }
+
+ if (!types.isUndefinedOrNull(isCluster)) {
+ setCommandContext(ContextKeys.ISCLUSTER, isCluster);
+ }
+
+ if (!types.isUndefinedOrNull(serverMajorVersion)) {
+ setCommandContext(ContextKeys.SERVERMAJORVERSION, serverMajorVersion);
+ }
+ }
+
+ dispose(): void {
+ this._disposables = this._disposables.map(i => i.dispose());
+ }
+}
diff --git a/extensions/kusto/src/contracts.ts b/extensions/kusto/src/contracts.ts
new file mode 100644
index 0000000000..491c6cfb02
--- /dev/null
+++ b/extensions/kusto/src/contracts.ts
@@ -0,0 +1,63 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { NotificationType, RequestType } from 'vscode-languageclient';
+import { ITelemetryEventProperties, ITelemetryEventMeasures } from './telemetry';
+import * as azdata from 'azdata';
+
+// ------------------------------- < Telemetry Sent Event > ------------------------------------
+
+/**
+ * Event sent when the language service send a telemetry event
+ */
+export namespace TelemetryNotification {
+ export const type = new NotificationType('telemetry/sqlevent');
+}
+
+/**
+ * Update event parameters
+ */
+export class TelemetryParams {
+ public params!: {
+ eventName: string;
+ properties: ITelemetryEventProperties;
+ measures: ITelemetryEventMeasures;
+ };
+}
+
+// ------------------------------- Telemetry Sent Event > ----------------------------------
+
+// ------------------------------- -----------------------------
+export namespace SerializeDataStartRequest {
+ export const type = new RequestType('serialize/start');
+}
+
+export namespace SerializeDataContinueRequest {
+ export const type = new RequestType('serialize/continue');
+}
+// ------------------------------- -----------------------------
+
+// ------------------------------- < Load Completion Extension Request > ------------------------------------
+/**
+ * Completion extension load parameters
+ */
+export class CompletionExtensionParams {
+ ///
+ /// Absolute path for the assembly containing the completion extension
+ ///
+ public assemblyPath?: string;
+ ///
+ /// The type name for the completion extension
+ ///
+ public typeName?: string;
+ ///
+ /// Property bag for initializing the completion extension
+ ///
+ public properties?: {};
+}
+
+export namespace CompletionExtLoadRequest {
+ export const type = new RequestType('completion/extLoad');
+}
diff --git a/extensions/kusto/src/features.ts b/extensions/kusto/src/features.ts
new file mode 100644
index 0000000000..3404224873
--- /dev/null
+++ b/extensions/kusto/src/features.ts
@@ -0,0 +1,89 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { SqlOpsDataClient, SqlOpsFeature } from 'dataprotocol-client';
+import { ClientCapabilities, StaticFeature, RPCMessageType, ServerCapabilities } from 'vscode-languageclient';
+import { Disposable } from 'vscode';
+import { Telemetry } from './telemetry';
+import * as contracts from './contracts';
+import * as azdata from 'azdata';
+import * as Utils from './utils';
+import * as UUID from 'vscode-languageclient/lib/utils/uuid';
+
+export class TelemetryFeature implements StaticFeature {
+
+ constructor(private _client: SqlOpsDataClient) { }
+
+ fillClientCapabilities(capabilities: ClientCapabilities): void {
+ Utils.ensure(capabilities, 'telemetry')!.telemetry = true;
+ }
+
+ initialize(): void {
+ this._client.onNotification(contracts.TelemetryNotification.type, e => {
+ Telemetry.sendTelemetryEvent(e.params.eventName, e.params.properties, e.params.measures);
+ });
+ }
+}
+
+export class SerializationFeature extends SqlOpsFeature {
+ private static readonly messageTypes: RPCMessageType[] = [
+ contracts.SerializeDataStartRequest.type,
+ contracts.SerializeDataContinueRequest.type,
+ ];
+
+ constructor(client: SqlOpsDataClient) {
+ super(client, SerializationFeature.messageTypes);
+ }
+
+ public fillClientCapabilities(capabilities: ClientCapabilities): void {
+ }
+
+ public initialize(capabilities: ServerCapabilities): void {
+ this.register(this.messages, {
+ id: UUID.generateUuid(),
+ registerOptions: undefined
+ });
+ }
+
+ protected registerProvider(options: undefined): Disposable {
+ const client = this._client;
+
+ let startSerialization = (requestParams: azdata.SerializeDataStartRequestParams): Thenable => {
+ return client.sendRequest(contracts.SerializeDataStartRequest.type, requestParams).then(
+ r => {
+ return r;
+ },
+ e => {
+ client.logFailedRequest(contracts.SerializeDataStartRequest.type, e);
+ return Promise.resolve({
+ succeeded: false,
+ messages: Utils.getErrorMessage(e)
+ });
+ }
+ );
+ };
+
+ let continueSerialization = (requestParams: azdata.SerializeDataContinueRequestParams): Thenable => {
+ return client.sendRequest(contracts.SerializeDataContinueRequest.type, requestParams).then(
+ r => {
+ return r;
+ },
+ e => {
+ client.logFailedRequest(contracts.SerializeDataContinueRequest.type, e);
+ return Promise.resolve({
+ succeeded: false,
+ messages: Utils.getErrorMessage(e)
+ });
+ }
+ );
+ };
+
+ return azdata.dataprotocol.registerSerializationProvider({
+ providerId: client.providerId,
+ startSerialization,
+ continueSerialization
+ });
+ }
+}
diff --git a/extensions/kusto/src/iconProvider.ts b/extensions/kusto/src/iconProvider.ts
new file mode 100644
index 0000000000..61cc953c5a
--- /dev/null
+++ b/extensions/kusto/src/iconProvider.ts
@@ -0,0 +1,21 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as azdata from 'azdata';
+import * as constants from './constants';
+
+const cloudIcon = 'kusto:cloud';
+
+export class KustoIconProvider implements azdata.IconProvider {
+ public readonly providerId: string = constants.kustoProviderName;
+ public handle?: number;
+ getConnectionIconId(connection: azdata.IConnectionProfile, serverInfo: azdata.ServerInfo): Thenable {
+ let iconName: string | undefined;
+ if (connection.providerName === this.providerId) {
+ iconName = cloudIcon;
+ }
+ return Promise.resolve(iconName);
+ }
+}
diff --git a/extensions/kusto/src/kusto.d.ts b/extensions/kusto/src/kusto.d.ts
new file mode 100644
index 0000000000..04f8f3b20a
--- /dev/null
+++ b/extensions/kusto/src/kusto.d.ts
@@ -0,0 +1,38 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+// This is the place for extensions to expose APIs.
+
+import * as azdata from 'azdata';
+
+/**
+* The APIs provided by Kusto extension
+*/
+export interface IExtension {
+ /**
+ * Gets the object explorer API that supports querying over the connections supported by this extension
+ *
+ */
+ getKustoObjectExplorerBrowser(): KustoObjectExplorerBrowser;
+}
+
+/**
+ * A browser supporting actions over the object explorer connections provided by this extension.
+ * Currently this is the
+ */
+export interface KustoObjectExplorerBrowser {
+ /**
+ * Gets the matching node given a context object, e.g. one from a right-click on a node in Object Explorer
+ */
+ getNode(objectExplorerContext: azdata.ObjectExplorerContext): Thenable;
+}
+
+/**
+ * A tree node in the object explorer tree
+ */
+export interface ITreeNode {
+ getNodeInfo(): azdata.NodeInfo;
+ getChildren(refreshChildren: boolean): ITreeNode[] | Thenable;
+}
diff --git a/extensions/kusto/src/kustoApiFactory.ts b/extensions/kusto/src/kustoApiFactory.ts
new file mode 100644
index 0000000000..baf0006b15
--- /dev/null
+++ b/extensions/kusto/src/kustoApiFactory.ts
@@ -0,0 +1,23 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { AppContext } from './appContext';
+import { IExtension, KustoObjectExplorerBrowser } from './kusto';
+import * as constants from './constants';
+import { KustoObjectExplorerNodeProvider } from './objectExplorerNodeProvider/objectExplorerNodeProvider';
+import * as azdata from 'azdata';
+
+export function createKustoApi(context: AppContext): IExtension {
+ return {
+ getKustoObjectExplorerBrowser(): KustoObjectExplorerBrowser {
+ return {
+ getNode: (explorerContext: azdata.ObjectExplorerContext) => {
+ let oeProvider = context.getService(constants.ObjectExplorerService);
+ return oeProvider?.findSqlClusterNodeByContext(explorerContext);
+ }
+ };
+ }
+ };
+}
diff --git a/extensions/kusto/src/kustoServer.ts b/extensions/kusto/src/kustoServer.ts
new file mode 100644
index 0000000000..5f05cb38cf
--- /dev/null
+++ b/extensions/kusto/src/kustoServer.ts
@@ -0,0 +1,163 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ServerProvider, IConfig, Events } from 'service-downloader';
+import { ServerOptions, TransportKind } from 'vscode-languageclient';
+import * as Constants from './constants';
+import * as vscode from 'vscode';
+import * as path from 'path';
+import { getCommonLaunchArgsAndCleanupOldLogFiles } from './utils';
+import { localize } from './localize';
+import { Telemetry, LanguageClientErrorHandler } from './telemetry';
+import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
+import { TelemetryFeature, SerializationFeature } from './features';
+import { AppContext } from './appContext';
+import { CompletionExtensionParams, CompletionExtLoadRequest } from './contracts';
+import { promises as fs } from 'fs';
+
+const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
+const statusView = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
+
+export class KustoServer {
+
+ private client!: SqlOpsDataClient;
+ private config!: IConfig;
+ private disposables: vscode.Disposable[] = [];
+
+ public async start(context: AppContext): Promise {
+ try {
+ const installationStart = Date.now();
+ const path = await this.download(context); // TodoKusto: Remove this commented line once the Kusto service layer has been published to Github. Until then copy manually for debugging.
+ // const path = "e:\\repos\\azuredatastudio\\extensions\\kusto\\sqltoolsservice\\Windows\\2.0.0-release.15\\MicrosoftKustoServiceLayer.exe";
+ const installationComplete = Date.now();
+ let serverOptions = generateServerOptions(context.extensionContext.logPath, path);
+ let clientOptions = getClientOptions(context);
+ this.client = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions); // TodoKusto: Update constant
+ const processStart = Date.now();
+ const clientReadyPromise = this.client.onReady().then(() => {
+ const processEnd = Date.now();
+ statusView.text = localize('serviceStartedStatusMsg', "{0} Started", Constants.serviceName);
+ setTimeout(() => {
+ statusView.hide();
+ }, 1500);
+ vscode.commands.registerCommand('kusto.loadCompletionExtension', (params: CompletionExtensionParams) => {
+ this.client.sendRequest(CompletionExtLoadRequest.type, params);
+ });
+ Telemetry.sendTelemetryEvent('startup/LanguageClientStarted', {
+ installationTime: String(installationComplete - installationStart),
+ processStartupTime: String(processEnd - processStart),
+ totalTime: String(processEnd - installationStart),
+ beginningTimestamp: String(installationStart)
+ });
+ });
+ statusView.show();
+ statusView.text = localize('startingServiceStatusMsg', "Starting {0}", Constants.serviceName);
+ this.client.start();
+ await Promise.all([clientReadyPromise]);
+ return this.client;
+ } catch (e) {
+ Telemetry.sendTelemetryEvent('ServiceInitializingFailed');
+ vscode.window.showErrorMessage(localize('failedToStartServiceErrorMsg', "Failed to start {0}", Constants.serviceName));
+ throw e;
+ }
+ }
+
+ private async download(context: AppContext): Promise {
+ const rawConfig = await fs.readFile(path.join(context.extensionContext.extensionPath, 'config.json')); // TodoKusto: Update config.json to refer to the right exe
+ this.config = JSON.parse(rawConfig.toString())!;
+ this.config.installDirectory = path.join(__dirname, this.config.installDirectory);
+ this.config.proxy = vscode.workspace.getConfiguration('http').get('proxy')!;
+ this.config.strictSSL = vscode.workspace.getConfiguration('http').get('proxyStrictSSL') || true;
+
+ const serverdownloader = new ServerProvider(this.config);
+ serverdownloader.eventEmitter.onAny(() => generateHandleServerProviderEvent());
+ return serverdownloader.getOrDownloadServer();
+ }
+
+ dispose() {
+ this.disposables.forEach(d => d.dispose());
+ if (this.client) {
+ this.client.stop();
+ }
+ }
+}
+
+function generateServerOptions(logPath: string, executablePath: string): ServerOptions {
+ const launchArgs = getCommonLaunchArgsAndCleanupOldLogFiles(logPath, 'kustoService.log', executablePath);
+ return { command: executablePath, args: launchArgs, transport: TransportKind.stdio };
+}
+
+function generateHandleServerProviderEvent() {
+ let dots = 0;
+ return (e: string, ...args: any[]) => {
+ switch (e) {
+ case Events.INSTALL_START:
+ outputChannel.show(true);
+ statusView.show();
+ outputChannel.appendLine(localize('installingServiceChannelMsg', "Installing {0} to {1}", Constants.serviceName, args[0]));
+ statusView.text = localize('installingServiceStatusMsg', "Installing {0}", Constants.serviceName);
+ break;
+ case Events.INSTALL_END:
+ outputChannel.appendLine(localize('installedServiceChannelMsg', "Installed {0}", Constants.serviceName));
+ break;
+ case Events.DOWNLOAD_START:
+ outputChannel.appendLine(localize('downloadingServiceChannelMsg', "Downloading {0}", args[0]));
+ outputChannel.append(localize('downloadingServiceSizeChannelMsg', "({0} KB)", Math.ceil(args[1] / 1024).toLocaleString(vscode.env.language)));
+ statusView.text = localize('downloadingServiceStatusMsg', "Downloading {0}", Constants.serviceName);
+ break;
+ case Events.DOWNLOAD_PROGRESS:
+ let newDots = Math.ceil(args[0] / 5);
+ if (newDots > dots) {
+ outputChannel.append('.'.repeat(newDots - dots));
+ dots = newDots;
+ }
+ break;
+ case Events.DOWNLOAD_END:
+ outputChannel.appendLine(localize('downloadServiceDoneChannelMsg', "Done installing {0}", Constants.serviceName));
+ break;
+ default:
+ console.error(`Unknown event from Server Provider ${e}`);
+ break;
+ }
+ };
+}
+
+function getClientOptions(context: AppContext): ClientOptions {
+ return {
+ documentSelector: ['kusto'], // TodoKusto: This should be same as the language id in package.json. See if we can surface that better later.
+ synchronize: {
+ configurationSection: Constants.extensionConfigSectionName
+ },
+ providerId: Constants.providerId,
+ errorHandler: new LanguageClientErrorHandler(),
+ features: [
+ // we only want to add new features
+ ...SqlOpsDataClient.defaultFeatures,
+ TelemetryFeature,
+ SerializationFeature
+ ],
+ outputChannel: new CustomOutputChannel()
+ };
+}
+
+class CustomOutputChannel implements vscode.OutputChannel {
+ name!: string;
+ append(value: string): void {
+ console.log(value);
+ }
+ appendLine(value: string): void {
+ console.log(value);
+ }
+ clear(): void {
+ }
+ show(preserveFocus?: boolean): void;
+ show(column?: vscode.ViewColumn, preserveFocus?: boolean): void;
+ show(column?: any, preserveFocus?: any) {
+ }
+ hide(): void {
+ }
+ dispose(): void {
+ }
+}
diff --git a/extensions/kusto/src/localize.ts b/extensions/kusto/src/localize.ts
new file mode 100644
index 0000000000..51c7ffb2a4
--- /dev/null
+++ b/extensions/kusto/src/localize.ts
@@ -0,0 +1,8 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as nls from 'vscode-nls';
+
+export const localize = nls.loadMessageBundle();
diff --git a/extensions/kusto/src/main.ts b/extensions/kusto/src/main.ts
new file mode 100644
index 0000000000..450764a498
--- /dev/null
+++ b/extensions/kusto/src/main.ts
@@ -0,0 +1,135 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import * as azdata from 'azdata';
+import * as path from 'path';
+
+import * as Constants from './constants';
+import ContextProvider from './contextProvider';
+import * as Utils from './utils';
+import { AppContext } from './appContext';
+import { IExtension } from './kusto';
+import { KustoObjectExplorerNodeProvider } from './objectExplorerNodeProvider/objectExplorerNodeProvider';
+import { registerSearchServerCommand } from './objectExplorerNodeProvider/command';
+import { KustoIconProvider } from './iconProvider';
+import { createKustoApi } from './kustoApiFactory';
+import { localize } from './localize';
+import { KustoServer } from './kustoServer';
+import { promises as fs } from 'fs';
+
+export async function activate(context: vscode.ExtensionContext): Promise {
+ // lets make sure we support this platform first
+ let supported = await Utils.verifyPlatform();
+
+ if (!supported) {
+ vscode.window.showErrorMessage(localize('kusto.unsupportedPlatform', 'Unsupported platform'));
+ return undefined;
+ }
+
+ // ensure our log path exists
+ if (!(await Utils.exists(context.logPath))) {
+ await fs.mkdir(context.logPath);
+ }
+
+ let appContext = new AppContext(context);
+
+ let nodeProvider = new KustoObjectExplorerNodeProvider(appContext);
+ azdata.dataprotocol.registerObjectExplorerNodeProvider(nodeProvider);
+ let iconProvider = new KustoIconProvider();
+ azdata.dataprotocol.registerIconProvider(iconProvider);
+
+ activateNotebookTask();
+
+ registerSearchServerCommand();
+ context.subscriptions.push(new ContextProvider());
+
+ registerLogCommand(context);
+
+ // initialize client last so we don't have features stuck behind it
+ const server = new KustoServer();
+ context.subscriptions.push(server);
+ await server.start(appContext);
+
+ return createKustoApi(appContext);
+}
+
+const logFiles = ['resourceprovider.log', 'kustoservice.log', 'credentialstore.log'];
+function registerLogCommand(context: vscode.ExtensionContext) {
+ context.subscriptions.push(vscode.commands.registerCommand('kusto.showLogFile', async () => {
+ const choice = await vscode.window.showQuickPick(logFiles);
+ if (choice) {
+ const document = await vscode.workspace.openTextDocument(vscode.Uri.file(path.join(context.logPath, choice)));
+ if (document) {
+ vscode.window.showTextDocument(document);
+ }
+ }
+ }));
+}
+
+function activateNotebookTask(): void {
+ azdata.tasks.registerTask(Constants.kustoClusterNewNotebookTask, (profile: azdata.IConnectionProfile) => {
+ return saveProfileAndCreateNotebook(profile);
+ });
+ azdata.tasks.registerTask(Constants.kustoClusterOpenNotebookTask, (profile: azdata.IConnectionProfile) => {
+ return handleOpenNotebookTask(profile);
+ });
+}
+
+function saveProfileAndCreateNotebook(profile: azdata.IConnectionProfile): Promise {
+ return handleNewNotebookTask(undefined, profile);
+}
+
+function findNextUntitledEditorName(): string {
+ let nextVal = 0;
+ // Note: this will go forever if it's coded wrong, or you have inifinite Untitled notebooks!
+ while (true) {
+ let title = `Notebook-${nextVal}`;
+ let hasNotebookDoc = azdata.nb.notebookDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1;
+ if (!hasNotebookDoc) {
+ return title;
+ }
+ nextVal++;
+ }
+}
+
+async function handleNewNotebookTask(oeContext?: azdata.ObjectExplorerContext, profile?: azdata.IConnectionProfile): Promise {
+ // Ensure we get a unique ID for the notebook. For now we're using a different prefix to the built-in untitled files
+ // to handle this. We should look into improving this in the future
+ let title = findNextUntitledEditorName();
+ let untitledUri = vscode.Uri.parse(`untitled:${title}`);
+ await azdata.nb.showNotebookDocument(untitledUri, {
+ connectionProfile: profile,
+ preview: false
+ });
+}
+
+async function handleOpenNotebookTask(profile: azdata.IConnectionProfile): Promise {
+ let notebookFileTypeName = localize('notebookFileType', "Notebooks");
+ let filter = {};
+ filter[notebookFileTypeName] = 'ipynb';
+ let uris = await vscode.window.showOpenDialog({
+ filters: filter,
+ canSelectFiles: true,
+ canSelectMany: false
+ });
+ if (uris && uris.length > 0) {
+ let fileUri = uris[0];
+ // Verify this is a .ipynb file since this isn't actually filtered on Mac/Linux
+ if (path.extname(fileUri.fsPath) !== '.ipynb') {
+ // in the future might want additional supported types
+ vscode.window.showErrorMessage(localize('unsupportedFileType', "Only .ipynb Notebooks are supported"));
+ } else {
+ await azdata.nb.showNotebookDocument(fileUri, {
+ connectionProfile: profile,
+ preview: false
+ });
+ }
+ }
+}
+
+// this method is called when your extension is deactivated
+export function deactivate(): void {
+}
diff --git a/extensions/kusto/src/objectExplorerNodeProvider/cancelableStream.ts b/extensions/kusto/src/objectExplorerNodeProvider/cancelableStream.ts
new file mode 100644
index 0000000000..f1ef49a591
--- /dev/null
+++ b/extensions/kusto/src/objectExplorerNodeProvider/cancelableStream.ts
@@ -0,0 +1,27 @@
+/*---------------------------------------------------------------------------------------------
+ * 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 { Transform } from 'stream';
+import * as vscode from 'vscode';
+import * as nls from 'vscode-nls';
+
+const localize = nls.loadMessageBundle();
+
+export class CancelableStream extends Transform {
+ constructor(private cancelationToken: vscode.CancellationTokenSource) {
+ super();
+ }
+
+ public _transform(chunk: any, encoding: string, callback: Function): void {
+ if (this.cancelationToken && this.cancelationToken.token.isCancellationRequested) {
+ callback(new Error(localize('streamCanceled', 'Stream operation canceled by the user')));
+ } else {
+ this.push(chunk);
+ callback();
+ }
+ }
+}
diff --git a/extensions/kusto/src/objectExplorerNodeProvider/command.ts b/extensions/kusto/src/objectExplorerNodeProvider/command.ts
new file mode 100644
index 0000000000..4d6bd2228a
--- /dev/null
+++ b/extensions/kusto/src/objectExplorerNodeProvider/command.ts
@@ -0,0 +1,187 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import * as azdata from 'azdata';
+import * as nls from 'vscode-nls';
+const localize = nls.loadMessageBundle();
+
+import { TreeNode } from './treeNodes';
+import { QuestionTypes, IPrompter, IQuestion } from '../prompts/question';
+import * as utils from '../utils';
+import * as constants from '../constants';
+import { AppContext } from '../appContext';
+
+export interface ICommandContextParsingOptions {
+ editor: boolean;
+ uri: boolean;
+}
+
+export interface ICommandBaseContext {
+ command: string;
+ editor?: vscode.TextEditor;
+ uri?: vscode.Uri;
+}
+
+export interface ICommandUnknownContext extends ICommandBaseContext {
+ type: 'unknown';
+}
+
+export interface ICommandUriContext extends ICommandBaseContext {
+ type: 'uri';
+}
+
+export interface ICommandViewContext extends ICommandBaseContext {
+ type: 'view';
+ node: TreeNode;
+}
+
+export interface ICommandObjectExplorerContext extends ICommandBaseContext {
+ type: 'objectexplorer';
+ explorerContext: azdata.ObjectExplorerContext;
+}
+
+export type CommandContext = ICommandObjectExplorerContext | ICommandViewContext | ICommandUriContext | ICommandUnknownContext;
+
+function isTextEditor(editor: any): editor is vscode.TextEditor {
+ if (editor === undefined) { return false; }
+
+ return editor.id !== undefined && ((editor as vscode.TextEditor).edit !== undefined || (editor as vscode.TextEditor).document !== undefined);
+}
+
+export abstract class Command extends vscode.Disposable {
+
+
+ protected readonly contextParsingOptions: ICommandContextParsingOptions = { editor: false, uri: false };
+
+ private disposable: vscode.Disposable;
+
+ constructor(command: string | string[], protected appContext: AppContext) {
+ super(() => this.dispose());
+
+ if (typeof command === 'string') {
+ this.disposable = vscode.commands.registerCommand(command, (...args: any[]) => this._execute(command, ...args), this);
+
+ return;
+ }
+
+ const subscriptions = command.map(cmd => vscode.commands.registerCommand(cmd, (...args: any[]) => this._execute(cmd, ...args), this));
+ this.disposable = vscode.Disposable.from(...subscriptions);
+ }
+
+ dispose(): void {
+ if (this.disposable) {
+ this.disposable.dispose();
+ }
+ }
+
+ protected async preExecute(...args: any[]): Promise {
+ return this.execute(...args);
+ }
+
+ abstract execute(...args: any[]): any;
+
+ protected _execute(command: string, ...args: any[]): any {
+ // TODO consider using Telemetry.trackEvent(command);
+
+ const [context, rest] = Command.parseContext(command, this.contextParsingOptions, ...args);
+ return this.preExecute(context, ...rest);
+ }
+
+ private static parseContext(command: string, options: ICommandContextParsingOptions, ...args: any[]): [CommandContext, any[]] {
+ let editor: vscode.TextEditor | undefined = undefined;
+
+ let firstArg = args[0];
+ if (options.editor && (firstArg === undefined || isTextEditor(firstArg))) {
+ editor = firstArg;
+ args = args.slice(1);
+ firstArg = args[0];
+ }
+
+ if (options.uri && (firstArg === undefined || firstArg instanceof vscode.Uri)) {
+ const [uri, ...rest] = args as [vscode.Uri, any];
+ return [{ command: command, type: 'uri', editor: editor, uri: uri }, rest];
+ }
+
+ if (firstArg instanceof TreeNode) {
+ const [node, ...rest] = args as [TreeNode, any];
+ return [{ command: command, type: constants.ViewType, node: node }, rest];
+ }
+
+ if (firstArg && utils.isObjectExplorerContext(firstArg)) {
+ const [explorerContext, ...rest] = args as [azdata.ObjectExplorerContext, any];
+ return [{ command: command, type: constants.ObjectExplorerService, explorerContext: explorerContext }, rest];
+ }
+
+ return [{ command: command, type: 'unknown', editor: editor }, args];
+ }
+}
+
+export abstract class ProgressCommand extends Command {
+ static progressId = 0;
+ constructor(command: string, protected prompter: IPrompter, appContext: AppContext) {
+ super(command, appContext);
+ }
+
+ protected async executeWithProgress(
+ execution: (cancelToken: vscode.CancellationTokenSource) => Promise,
+ label: string,
+ isCancelable: boolean = false,
+ onCanceled?: () => void
+ ): Promise {
+ let disposables: vscode.Disposable[] = [];
+ const tokenSource = new vscode.CancellationTokenSource();
+ const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
+ disposables.push(vscode.Disposable.from(statusBarItem));
+ statusBarItem.text = localize('progress', '$(sync~spin) {0}...', label);
+ if (isCancelable) {
+ const cancelCommandId = `cancelProgress${ProgressCommand.progressId++}`;
+ disposables.push(vscode.commands.registerCommand(cancelCommandId, async () => {
+ if (await this.confirmCancel()) {
+ tokenSource.cancel();
+ }
+ }));
+ statusBarItem.tooltip = localize('cancelTooltip', 'Cancel');
+ statusBarItem.command = cancelCommandId;
+ }
+ statusBarItem.show();
+
+ try {
+ await execution(tokenSource);
+ } catch (error) {
+ if (isCancelable && onCanceled && tokenSource.token.isCancellationRequested) {
+ // The error can be assumed to be due to cancelation occurring. Do the callback
+ onCanceled();
+ } else {
+ throw error;
+ }
+ } finally {
+ disposables.forEach(d => d.dispose());
+ }
+ }
+
+ private async confirmCancel(): Promise {
+ return (await this.prompter.promptSingle({
+ type: QuestionTypes.confirm,
+ message: localize('cancel', 'Cancel operation?'),
+ default: true
+ }))!;
+ }
+}
+
+export function registerSearchServerCommand(): void {
+ vscode.commands.registerCommand('kusto.searchServers', () => {
+ vscode.window.showInputBox({
+ placeHolder: localize('kusto.searchServers', 'Search Server Names')
+ }).then((stringSearch) => {
+ if (stringSearch) {
+ vscode.commands.executeCommand('registeredServers.searchServer', (stringSearch));
+ }
+ });
+ });
+ vscode.commands.registerCommand('kusto.clearSearchServerResult', () => {
+ vscode.commands.executeCommand('registeredServers.clearSearchServerResult');
+ });
+}
diff --git a/extensions/kusto/src/objectExplorerNodeProvider/connection.ts b/extensions/kusto/src/objectExplorerNodeProvider/connection.ts
new file mode 100644
index 0000000000..cb04c20e78
--- /dev/null
+++ b/extensions/kusto/src/objectExplorerNodeProvider/connection.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.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as azdata from 'azdata';
+import * as nls from 'vscode-nls';
+const localize = nls.loadMessageBundle();
+
+import * as constants from '../constants';
+
+export class KustoClusterConnection {
+ private _connection: azdata.connection.Connection;
+ private _profile!: azdata.IConnectionProfile;
+ private _user: string;
+ private _password: string;
+
+ constructor(connectionInfo: azdata.connection.Connection | azdata.IConnectionProfile) {
+ this.validate(connectionInfo);
+
+ if ('id' in connectionInfo) {
+ this._profile = connectionInfo;
+ this._connection = this.toConnection(this._profile);
+ } else {
+ this._connection = connectionInfo;
+ }
+ this._user = this._connection.options[constants.userPropName];
+ this._password = this._connection.options[constants.passwordPropName];
+ }
+
+ public get connection(): azdata.connection.Connection { return this._connection; }
+ public get user(): string { return this._user; }
+ public get password(): string { return this._password; }
+
+ public isMatch(connection: KustoClusterConnection | azdata.ConnectionInfo): boolean {
+ if (!connection) { return false; }
+ let options1 = connection instanceof KustoClusterConnection ?
+ connection._connection.options : connection.options;
+ let options2 = this._connection.options;
+ return [constants.serverPropName, constants.userPropName]
+ .every(e => options1[e] === options2[e]);
+ }
+
+ public isIntegratedAuth(): boolean {
+ let authType: string = this._connection.options[constants.authenticationTypePropName];
+ return authType?.toLowerCase() === constants.integratedAuth;
+ }
+
+ public updatePassword(password: string): void {
+ if (password) {
+ this._password = password;
+ }
+ }
+
+ private validate(connectionInfo: azdata.ConnectionInfo): void {
+ if (!connectionInfo) {
+ throw new Error(localize('connectionInfoUndefined', 'ConnectionInfo is undefined.'));
+ }
+ if (!connectionInfo.options) {
+ throw new Error(localize('connectionInfoOptionsUndefined', 'ConnectionInfo.options is undefined.'));
+ }
+ let missingProperties: string[] = this.getMissingProperties(connectionInfo)!;
+ if (missingProperties && missingProperties.length > 0) {
+ throw new Error(localize('connectionInfoOptionsMissingProperties',
+ 'Some missing properties in connectionInfo.options: {0}',
+ missingProperties.join(', ')));
+ }
+ }
+
+ private getMissingProperties(connectionInfo: azdata.ConnectionInfo): string[] | undefined {
+ if (!connectionInfo || !connectionInfo.options) { return undefined; }
+ let requiredProps = [constants.serverPropName];
+ requiredProps.push(constants.userPropName);
+ return requiredProps.filter(e => connectionInfo.options[e] === undefined);
+ }
+
+ private toConnection(connProfile: azdata.IConnectionProfile): azdata.connection.Connection {
+ let connection: azdata.connection.Connection = Object.assign(connProfile,
+ { connectionId: this._profile.id });
+ return connection;
+ }
+}
diff --git a/extensions/kusto/src/objectExplorerNodeProvider/objectExplorerNodeProvider.ts b/extensions/kusto/src/objectExplorerNodeProvider/objectExplorerNodeProvider.ts
new file mode 100644
index 0000000000..bf5f7e7bcd
--- /dev/null
+++ b/extensions/kusto/src/objectExplorerNodeProvider/objectExplorerNodeProvider.ts
@@ -0,0 +1,220 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as azdata from 'azdata';
+import * as vscode from 'vscode';
+import * as nls from 'vscode-nls';
+const localize = nls.loadMessageBundle();
+
+import { ProviderBase } from './providerBase';
+import { KustoClusterConnection } from './connection';
+import { TreeNode } from './treeNodes';
+import { AppContext } from '../appContext';
+import * as constants from '../constants';
+import { ICommandObjectExplorerContext } from './command';
+
+export const kustoOutputChannel = vscode.window.createOutputChannel(constants.providerId);
+
+export interface ITreeChangeHandler {
+ notifyNodeChanged(node: TreeNode): void;
+}
+
+export class KustoObjectExplorerNodeProvider extends ProviderBase implements azdata.ObjectExplorerNodeProvider, ITreeChangeHandler {
+ public readonly supportedProviderId: string = constants.providerId;
+ private expandCompleteEmitter = new vscode.EventEmitter();
+
+ constructor(private appContext: AppContext) {
+ super();
+ this.appContext.registerService(constants.ObjectExplorerService, this);
+ }
+
+ handleSessionOpen(session: azdata.ObjectExplorerSession): Thenable {
+ return new Promise((resolve, reject) => {
+ if (!session) {
+ reject('handleSessionOpen requires a session object to be passed');
+ } else {
+ resolve(this.doSessionOpen(session));
+ }
+ });
+ }
+
+ private async doSessionOpen(session: azdata.ObjectExplorerSession): Promise {
+ if (!session || !session.sessionId) { return false; }
+
+ let connProfile = await azdata.objectexplorer.getSessionConnectionProfile(session.sessionId);
+ if (!connProfile) { return false; }
+
+ return true;
+ }
+
+ expandNode(nodeInfo: azdata.ExpandNodeInfo, isRefresh: boolean = false): Thenable {
+ return new Promise((resolve, reject) => {
+ if (!nodeInfo) {
+ reject('expandNode requires a nodeInfo object to be passed');
+ } else {
+ resolve(this.doExpandNode(nodeInfo, isRefresh));
+ }
+ });
+ }
+
+ private async doExpandNode(nodeInfo: azdata.ExpandNodeInfo, isRefresh: boolean = false): Promise {
+ let response = {
+ sessionId: nodeInfo.sessionId!,
+ nodePath: nodeInfo.nodePath!,
+ errorMessage: undefined,
+ nodes: []
+ };
+
+ this.expandCompleteEmitter.fire(response);
+
+ return true;
+ }
+
+ refreshNode(nodeInfo: azdata.ExpandNodeInfo): Thenable {
+ return this.expandNode(nodeInfo, true);
+ }
+
+ handleSessionClose(closeSessionInfo: azdata.ObjectExplorerCloseSessionInfo): void {
+ }
+
+ findNodes(findNodesInfo: azdata.FindNodesInfo): Thenable {
+ let response: azdata.ObjectExplorerFindNodesResponse = {
+ nodes: []
+ };
+ return Promise.resolve(response);
+ }
+
+ registerOnExpandCompleted(handler: (response: azdata.ObjectExplorerExpandInfo) => any): void {
+ this.expandCompleteEmitter.event(handler);
+ }
+
+ notifyNodeChanged(node: TreeNode): void {
+ this.notifyNodeChangesAsync(node);
+ }
+
+ private async notifyNodeChangesAsync(node: TreeNode): Promise {
+ try {
+ let session = this.getSqlClusterSessionForNode(node);
+ if (!session) {
+ vscode.window.showErrorMessage(localize('sessionNotFound', "Session for node {0} does not exist", node.nodePathValue));
+ } else {
+ let nodeInfo = node.getNodeInfo();
+ let expandInfo: azdata.ExpandNodeInfo = {
+ nodePath: nodeInfo.nodePath,
+ sessionId: session.sessionId
+ };
+ await this.refreshNode(expandInfo);
+ }
+ } catch (err) {
+ kustoOutputChannel.appendLine(localize('notifyError', "Error notifying of node change: {0}", err));
+ }
+ }
+
+ private getSqlClusterSessionForNode(node?: TreeNode): SqlClusterSession | undefined {
+ let sqlClusterSession: SqlClusterSession | undefined = undefined;
+ while (node !== undefined) {
+ if (node instanceof SqlClusterRootNode) {
+ sqlClusterSession = node.session;
+ break;
+ } else {
+ node = node.parent;
+ }
+ }
+ return sqlClusterSession;
+ }
+
+ async findSqlClusterNodeByContext(context: ICommandObjectExplorerContext | azdata.ObjectExplorerContext): Promise {
+ let node: T | undefined = undefined;
+ let explorerContext = 'explorerContext' in context ? context.explorerContext : context;
+ let sqlConnProfile = explorerContext.connectionProfile;
+ let session = this.findSqlClusterSessionBySqlConnProfile(sqlConnProfile!);
+ if (session) {
+ if (explorerContext.isConnectionNode) {
+ // Note: ideally fix so we verify T matches RootNode and go from there
+ node = session.rootNode;
+ } else {
+ // Find the node under the session
+ node = await session.rootNode.findNodeByPath(explorerContext?.nodeInfo?.nodePath!, true);
+ }
+ }
+ return node;
+ }
+
+ public findSqlClusterSessionBySqlConnProfile(connectionProfile: azdata.IConnectionProfile): SqlClusterSession | undefined {
+ return undefined;
+ }
+}
+
+export class SqlClusterSession {
+ private _rootNode: SqlClusterRootNode;
+
+ constructor(
+ private _sqlClusterConnection: KustoClusterConnection,
+ private _sqlSession: azdata.ObjectExplorerSession,
+ private _sqlConnectionProfile: azdata.IConnectionProfile
+ ) {
+ this._rootNode = new SqlClusterRootNode(this,
+ this._sqlSession.rootNode.nodePath!);
+ }
+
+ public get sqlClusterConnection(): KustoClusterConnection { return this._sqlClusterConnection; }
+ public get sqlSession(): azdata.ObjectExplorerSession { return this._sqlSession; }
+ public get sqlConnectionProfile(): azdata.IConnectionProfile { return this._sqlConnectionProfile; }
+ public get sessionId(): string { return this._sqlSession.sessionId!; }
+ public get rootNode(): SqlClusterRootNode { return this._rootNode; }
+
+ public isMatchedSqlConnection(sqlConnProfile: azdata.IConnectionProfile): boolean {
+ return this._sqlConnectionProfile.id === sqlConnProfile.id;
+ }
+}
+
+class SqlClusterRootNode extends TreeNode {
+ private _children: TreeNode[] = [];
+ constructor(
+ private _session: SqlClusterSession,
+ private _nodePathValue: string
+ ) {
+ super();
+ }
+
+ public get session(): SqlClusterSession {
+ return this._session;
+ }
+
+ public get nodePathValue(): string {
+ return this._nodePathValue;
+ }
+
+ public getChildren(refreshChildren: boolean): TreeNode[] | Promise {
+ if (refreshChildren || !this._children) {
+ return this.refreshChildren();
+ }
+ return this._children;
+ }
+
+ private async refreshChildren(): Promise {
+ this._children = [];
+ return this._children;
+ }
+
+ getTreeItem(): vscode.TreeItem | Promise {
+ throw new Error('Not intended for use in a file explorer view.');
+ }
+
+ getNodeInfo(): azdata.NodeInfo {
+ let nodeInfo: azdata.NodeInfo = {
+ label: localize('rootLabel', "Root")!,
+ isLeaf: false,
+ errorMessage: undefined,
+ metadata: undefined,
+ nodePath: this.generateNodePath()!,
+ nodeStatus: undefined,
+ nodeType: 'sqlCluster:root',
+ nodeSubType: undefined,
+ iconType: 'folder'
+ };
+ return nodeInfo;
+ }
+}
diff --git a/extensions/kusto/src/objectExplorerNodeProvider/providerBase.ts b/extensions/kusto/src/objectExplorerNodeProvider/providerBase.ts
new file mode 100644
index 0000000000..ab8d9f7f76
--- /dev/null
+++ b/extensions/kusto/src/objectExplorerNodeProvider/providerBase.ts
@@ -0,0 +1,13 @@
+/*---------------------------------------------------------------------------------------------
+ * 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 constants from '../constants';
+
+export abstract class ProviderBase {
+ public readonly providerId: string = constants.kustoClusterProviderName;
+ public handle?: number;
+}
diff --git a/extensions/kusto/src/objectExplorerNodeProvider/treeNodes.ts b/extensions/kusto/src/objectExplorerNodeProvider/treeNodes.ts
new file mode 100644
index 0000000000..675af2fe3d
--- /dev/null
+++ b/extensions/kusto/src/objectExplorerNodeProvider/treeNodes.ts
@@ -0,0 +1,87 @@
+/*---------------------------------------------------------------------------------------------
+ * 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 azdata from 'azdata';
+import * as vscode from 'vscode';
+import { ITreeNode } from './types';
+
+type TreeNodePredicate = (node: TreeNode) => boolean;
+
+export abstract class TreeNode implements ITreeNode {
+ private _parent?: TreeNode;
+ private _errorStatusCode?: number;
+
+ public get parent(): TreeNode | undefined {
+ return this._parent;
+ }
+
+ public set parent(node: TreeNode | undefined) {
+ this._parent = node;
+ }
+
+ public get errorStatusCode(): number | undefined {
+ return this._errorStatusCode;
+ }
+
+ public set errorStatusCode(error: number | undefined) {
+ this._errorStatusCode = error;
+ }
+
+ public generateNodePath(): string | undefined {
+ let path: string | undefined;
+ if (this.parent) {
+ path = this.parent.generateNodePath();
+ }
+ path = path ? `${path}/${this.nodePathValue}` : this.nodePathValue;
+ return path;
+ }
+
+ public findNodeByPath(path: string, expandIfNeeded: boolean = false): Promise {
+ let condition: TreeNodePredicate = (node: TreeNode) => node.getNodeInfo().nodePath === path || node.getNodeInfo().nodePath.startsWith(path);
+ let filter: TreeNodePredicate = (node: TreeNode) => path.startsWith(node.getNodeInfo().nodePath);
+ return TreeNode.findNode(this, condition, filter, true);
+ }
+
+ public static async findNode(node: TreeNode, condition: TreeNodePredicate, filter: TreeNodePredicate, expandIfNeeded: boolean): Promise {
+ if (!node) {
+ return undefined;
+ }
+
+ if (condition(node)) {
+ return node;
+ }
+
+ let nodeInfo = node.getNodeInfo();
+ if (nodeInfo.isLeaf) {
+ return undefined;
+ }
+
+ // TODO #3813 support filtering by already expanded / not yet expanded
+ let children = await node.getChildren(false);
+ if (children) {
+ for (let child of children) {
+ if (filter && filter(child)) {
+ let childNode = await this.findNode(child, condition, filter, expandIfNeeded);
+ if (childNode) {
+ return childNode;
+ }
+ }
+ }
+ }
+ return undefined;
+ }
+
+ /**
+ * The value to use for this node in the node path
+ */
+ public abstract get nodePathValue(): string;
+
+ abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise;
+ abstract getTreeItem(): vscode.TreeItem | Promise;
+
+ abstract getNodeInfo(): azdata.NodeInfo;
+}
diff --git a/extensions/kusto/src/objectExplorerNodeProvider/types.d.ts b/extensions/kusto/src/objectExplorerNodeProvider/types.d.ts
new file mode 100644
index 0000000000..4b82aeaae8
--- /dev/null
+++ b/extensions/kusto/src/objectExplorerNodeProvider/types.d.ts
@@ -0,0 +1,17 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import * as azdata from 'azdata';
+
+/**
+ * A tree node in the object explorer tree
+ *
+ * @export
+ */
+export interface ITreeNode {
+ getNodeInfo(): azdata.NodeInfo;
+ getChildren(refreshChildren: boolean): ITreeNode[] | Promise;
+}
diff --git a/extensions/kusto/src/prompts/adapter.ts b/extensions/kusto/src/prompts/adapter.ts
new file mode 100644
index 0000000000..42ecc5d839
--- /dev/null
+++ b/extensions/kusto/src/prompts/adapter.ts
@@ -0,0 +1,77 @@
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import { window } from 'vscode';
+import PromptFactory from './factory';
+import EscapeException from './escapeException';
+import { IQuestion, IPrompter, IPromptCallback, Answers } from './question';
+
+// Supports simple pattern for prompting for user input and acting on this
+export default class CodeAdapter implements IPrompter {
+
+ // TODO define question interface
+ private fixQuestion(question: IQuestion): any {
+ if (question.type === 'checkbox' && Array.isArray(question.choices)) {
+ // For some reason when there's a choice of checkboxes, they aren't formatted properly
+ // Not sure where the issue is
+ question.choices = question.choices.map(item => {
+ if (typeof (item) === 'string') {
+ return { checked: false, name: item, value: item };
+ } else {
+ return item;
+ }
+ });
+ }
+ }
+
+ public async promptSingle(question: IQuestion, ignoreFocusOut?: boolean): Promise {
+ let questions: IQuestion[] = [question];
+ const answers = this.prompt(questions, ignoreFocusOut);
+ if (answers) {
+ let response: T = answers[question.name];
+ return response || undefined;
+ }
+ return undefined;
+ }
+
+ public async prompt(questions: IQuestion[], ignoreFocusOut?: boolean): Promise | undefined> {
+ // Collapse multiple questions into a set of prompt steps
+ const promptResult = new Promise>((resolve) => {
+ let answers: Answers = {};
+ questions.forEach(async (question: IQuestion) => {
+ this.fixQuestion(question);
+ const prompt = await PromptFactory.createPrompt(question, ignoreFocusOut);
+ if (!question.shouldPrompt || question.shouldPrompt(answers) === true) {
+ const result = await prompt.render();
+ answers[question.name] = result;
+
+ if (question.onAnswered) {
+ question.onAnswered(result);
+ }
+ return;
+ }
+ });
+
+ resolve(answers);
+ });
+
+ try {
+ return await promptResult;
+ } catch (err) {
+ if (err instanceof EscapeException || err instanceof TypeError) {
+ window.showErrorMessage(err.message);
+ }
+ return undefined;
+ }
+ }
+
+ // Helper to make it possible to prompt using callback pattern. Generally Promise is a preferred flow
+ public promptCallback(questions: IQuestion[], callback: IPromptCallback): void {
+ // Collapse multiple questions into a set of prompt steps
+ this.prompt(questions).then(answers => {
+ if (callback && answers) {
+ callback(answers);
+ }
+ });
+ }
+}
diff --git a/extensions/kusto/src/prompts/checkbox.ts b/extensions/kusto/src/prompts/checkbox.ts
new file mode 100644
index 0000000000..2b89438058
--- /dev/null
+++ b/extensions/kusto/src/prompts/checkbox.ts
@@ -0,0 +1,52 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import { window } from 'vscode';
+import Prompt from './prompt';
+import EscapeException from './escapeException';
+
+const figures = require('figures');
+
+export default class CheckboxPrompt extends Prompt {
+
+ constructor(question: any, ignoreFocusOut?: boolean) {
+ super(question, ignoreFocusOut);
+ }
+
+ public render(): any {
+ let choices = this._question.choices.reduce((result, choice) => {
+ let choiceName = choice.name || choice;
+ result[`${choice.checked === true ? figures.radioOn : figures.radioOff} ${choiceName}`] = choice;
+ return result;
+ }, {});
+
+ let options = this.defaultQuickPickOptions;
+ options.placeHolder = this._question.message;
+
+ let quickPickOptions = Object.keys(choices);
+ quickPickOptions.push(figures.tick);
+
+ return window.showQuickPick(quickPickOptions, options)
+ .then(result => {
+ if (result === undefined) {
+ throw new EscapeException();
+ }
+
+ if (result !== figures.tick) {
+ choices[result].checked = !choices[result].checked;
+
+ return this.render();
+ }
+
+ return this._question.choices.reduce((result2, choice) => {
+ if (choice.checked === true) {
+ result2.push(choice.value);
+ }
+
+ return result2;
+ }, []);
+ });
+ }
+}
diff --git a/extensions/kusto/src/prompts/confirm.ts b/extensions/kusto/src/prompts/confirm.ts
new file mode 100644
index 0000000000..055d95a4e7
--- /dev/null
+++ b/extensions/kusto/src/prompts/confirm.ts
@@ -0,0 +1,36 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import * as nls from 'vscode-nls';
+const localize = nls.loadMessageBundle();
+
+import { window } from 'vscode';
+import Prompt from './prompt';
+import EscapeException from './escapeException';
+
+export default class ConfirmPrompt extends Prompt {
+
+ constructor(question: any, ignoreFocusOut?: boolean) {
+ super(question, ignoreFocusOut);
+ }
+
+ public render(): any {
+ let choices: { [id: string]: boolean } = {};
+ choices[localize('msgYes', 'Yes')] = true;
+ choices[localize('msgNo', 'No')] = false;
+
+ let options = this.defaultQuickPickOptions;
+ options.placeHolder = this._question.message;
+
+ return window.showQuickPick(Object.keys(choices), options)
+ .then(result => {
+ if (result === undefined) {
+ throw new EscapeException();
+ }
+
+ return choices[result] || false;
+ });
+ }
+}
diff --git a/extensions/kusto/src/prompts/escapeException.ts b/extensions/kusto/src/prompts/escapeException.ts
new file mode 100644
index 0000000000..11bcfc4360
--- /dev/null
+++ b/extensions/kusto/src/prompts/escapeException.ts
@@ -0,0 +1,8 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+export default class EscapeException extends Error {
+
+}
diff --git a/extensions/kusto/src/prompts/expand.ts b/extensions/kusto/src/prompts/expand.ts
new file mode 100644
index 0000000000..9a4a57f52e
--- /dev/null
+++ b/extensions/kusto/src/prompts/expand.ts
@@ -0,0 +1,78 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import vscode = require('vscode');
+import Prompt from './prompt';
+import EscapeException from './escapeException';
+import { INameValueChoice } from './question';
+
+const figures = require('figures');
+
+export default class ExpandPrompt extends Prompt {
+
+ constructor(question: any, ignoreFocusOut?: boolean) {
+ super(question, ignoreFocusOut);
+ }
+
+ public render(): any {
+ // label indicates this is a quickpick item. Otherwise it's a name-value pair
+ if (this._question.choices[0].label) {
+ return this.renderQuickPick(this._question.choices);
+ } else {
+ return this.renderNameValueChoice(this._question.choices);
+ }
+ }
+
+ private renderQuickPick(choices: vscode.QuickPickItem[]): any {
+ let options = this.defaultQuickPickOptions;
+ options.placeHolder = this._question.message;
+
+ return vscode.window.showQuickPick(choices, options)
+ .then(result => {
+ if (result === undefined) {
+ throw new EscapeException();
+ }
+
+ return this.validateAndReturn(result || false);
+ });
+ }
+ private renderNameValueChoice(choices: INameValueChoice[]): any {
+ const choiceMap = this._question.choices.reduce((result, choice) => {
+ result[choice.name] = choice.value;
+ return result;
+ }, {});
+
+ let options = this.defaultQuickPickOptions;
+ options.placeHolder = this._question.message;
+
+ return vscode.window.showQuickPick(Object.keys(choiceMap), options)
+ .then(result => {
+ if (result === undefined) {
+ throw new EscapeException();
+ }
+
+ // Note: cannot be used with 0 or false responses
+ let returnVal = choiceMap[result] || false;
+ return this.validateAndReturn(returnVal);
+ });
+ }
+
+ private validateAndReturn(value: any): any {
+ if (!this.validate(value)) {
+ return this.render();
+ }
+ return value;
+ }
+
+ private validate(value: any): boolean {
+ const validationError = this._question.validate ? this._question.validate(value || '') : undefined;
+
+ if (validationError) {
+ this._question.message = `${figures.warning} ${validationError}`;
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/extensions/kusto/src/prompts/factory.ts b/extensions/kusto/src/prompts/factory.ts
new file mode 100644
index 0000000000..1b9db3dda5
--- /dev/null
+++ b/extensions/kusto/src/prompts/factory.ts
@@ -0,0 +1,35 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import Prompt from './prompt';
+import InputPrompt from './input';
+import PasswordPrompt from './password';
+import ListPrompt from './list';
+import ConfirmPrompt from './confirm';
+import CheckboxPrompt from './checkbox';
+import ExpandPrompt from './expand';
+
+export default class PromptFactory {
+
+ public static createPrompt(question: any, ignoreFocusOut?: boolean): Prompt {
+ switch (question.type || 'input') {
+ case 'string':
+ case 'input':
+ return new InputPrompt(question, ignoreFocusOut);
+ case 'password':
+ return new PasswordPrompt(question, ignoreFocusOut);
+ case 'list':
+ return new ListPrompt(question, ignoreFocusOut);
+ case 'confirm':
+ return new ConfirmPrompt(question, ignoreFocusOut);
+ case 'checkbox':
+ return new CheckboxPrompt(question, ignoreFocusOut);
+ case 'expand':
+ return new ExpandPrompt(question, ignoreFocusOut);
+ default:
+ throw new Error(`Could not find a prompt for question type ${question.type}`);
+ }
+ }
+}
diff --git a/extensions/kusto/src/prompts/input.ts b/extensions/kusto/src/prompts/input.ts
new file mode 100644
index 0000000000..eb57f3faee
--- /dev/null
+++ b/extensions/kusto/src/prompts/input.ts
@@ -0,0 +1,59 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import { window, InputBoxOptions } from 'vscode';
+import Prompt from './prompt';
+import EscapeException from './escapeException';
+
+const figures = require('figures');
+
+export default class InputPrompt extends Prompt {
+
+ protected _options: InputBoxOptions;
+
+ constructor(question: any, ignoreFocusOut?: boolean) {
+ super(question, ignoreFocusOut);
+
+ this._options = this.defaultInputBoxOptions;
+ this._options.prompt = this._question.message;
+ }
+
+ // Helper for callers to know the right type to get from the type factory
+ public static get promptType(): string { return 'input'; }
+
+ public render(): any {
+ // Prefer default over the placeHolder, if specified
+ let placeHolder = this._question.default ? this._question.default : this._question.placeHolder;
+
+ if (this._question.default instanceof Error) {
+ placeHolder = this._question.default.message;
+ this._question.default = undefined;
+ }
+
+ this._options.placeHolder = placeHolder;
+
+ return window.showInputBox(this._options)
+ .then(result => {
+ if (result === undefined) {
+ throw new EscapeException();
+ }
+
+ if (result === '') {
+ // Use the default value, if defined
+ result = this._question.default || '';
+ }
+
+ const validationError = this._question.validate ? this._question.validate(result || '') : undefined;
+
+ if (validationError) {
+ this._question.default = new Error(`${figures.warning} ${validationError}`);
+
+ return this.render();
+ }
+
+ return result;
+ });
+ }
+}
diff --git a/extensions/kusto/src/prompts/list.ts b/extensions/kusto/src/prompts/list.ts
new file mode 100644
index 0000000000..d8254d1b9d
--- /dev/null
+++ b/extensions/kusto/src/prompts/list.ts
@@ -0,0 +1,33 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import { window } from 'vscode';
+import Prompt from './prompt';
+import EscapeException from './escapeException';
+
+export default class ListPrompt extends Prompt {
+ constructor(question: any, ignoreFocusOut?: boolean) {
+ super(question, ignoreFocusOut);
+ }
+
+ public render(): any {
+ const choices = this._question.choices.reduce((result, choice) => {
+ result[choice.name] = choice.value;
+ return result;
+ }, {});
+
+ let options = this.defaultQuickPickOptions;
+ options.placeHolder = this._question.message;
+
+ return window.showQuickPick(Object.keys(choices), options)
+ .then(result => {
+ if (result === undefined) {
+ throw new EscapeException();
+ }
+
+ return choices[result];
+ });
+ }
+}
diff --git a/extensions/kusto/src/prompts/password.ts b/extensions/kusto/src/prompts/password.ts
new file mode 100644
index 0000000000..ef6de55886
--- /dev/null
+++ b/extensions/kusto/src/prompts/password.ts
@@ -0,0 +1,15 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import InputPrompt from './input';
+
+export default class PasswordPrompt extends InputPrompt {
+
+ constructor(question: any, ignoreFocusOut?: boolean) {
+ super(question, ignoreFocusOut);
+
+ this._options.password = true;
+ }
+}
diff --git a/extensions/kusto/src/prompts/progressIndicator.ts b/extensions/kusto/src/prompts/progressIndicator.ts
new file mode 100644
index 0000000000..d63b830a08
--- /dev/null
+++ b/extensions/kusto/src/prompts/progressIndicator.ts
@@ -0,0 +1,70 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import { window, StatusBarItem, StatusBarAlignment } from 'vscode';
+
+export default class ProgressIndicator {
+
+ private _statusBarItem: StatusBarItem;
+
+ constructor() {
+ this._statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left);
+ }
+
+ private _tasks: string[] = [];
+ public beginTask(task: string): void {
+ this._tasks.push(task);
+ this.displayProgressIndicator();
+ }
+
+ public endTask(task: string): void {
+ if (this._tasks.length > 0) {
+ this._tasks.pop();
+ }
+
+ this.setMessage();
+ }
+
+ private setMessage(): void {
+ if (this._tasks.length === 0) {
+ this._statusBarItem.text = '';
+ this.hideProgressIndicator();
+ return;
+ }
+
+ this._statusBarItem.text = this._tasks[this._tasks.length - 1];
+ this._statusBarItem.show();
+ }
+
+ private _interval: any;
+ private displayProgressIndicator(): void {
+ this.setMessage();
+ this.hideProgressIndicator();
+ this._interval = setInterval(() => this.onDisplayProgressIndicator(), 100);
+ }
+ private hideProgressIndicator(): void {
+ if (this._interval) {
+ clearInterval(this._interval);
+ this._interval = undefined;
+ }
+ this.ProgressCounter = 0;
+ }
+
+ private ProgressText = ['|', '/', '-', '\\', '|', '/', '-', '\\'];
+ private ProgressCounter = 0;
+ private onDisplayProgressIndicator(): void {
+ if (this._tasks.length === 0) {
+ return;
+ }
+
+ let txt = this.ProgressText[this.ProgressCounter];
+ this._statusBarItem.text = this._tasks[this._tasks.length - 1] + ' ' + txt;
+ this.ProgressCounter++;
+
+ if (this.ProgressCounter >= this.ProgressText.length - 1) {
+ this.ProgressCounter = 0;
+ }
+ }
+}
diff --git a/extensions/kusto/src/prompts/prompt.ts b/extensions/kusto/src/prompts/prompt.ts
new file mode 100644
index 0000000000..700f577d66
--- /dev/null
+++ b/extensions/kusto/src/prompts/prompt.ts
@@ -0,0 +1,33 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import { InputBoxOptions, QuickPickOptions } from 'vscode';
+
+abstract class Prompt {
+
+ protected _question: any;
+ protected _ignoreFocusOut?: boolean;
+
+ constructor(question: any, ignoreFocusOut?: boolean) {
+ this._question = question;
+ this._ignoreFocusOut = ignoreFocusOut ? ignoreFocusOut : false;
+ }
+
+ public abstract render(): any;
+
+ protected get defaultQuickPickOptions(): QuickPickOptions {
+ return {
+ ignoreFocusOut: this._ignoreFocusOut
+ };
+ }
+
+ protected get defaultInputBoxOptions(): InputBoxOptions {
+ return {
+ ignoreFocusOut: this._ignoreFocusOut
+ };
+ }
+}
+
+export default Prompt;
diff --git a/extensions/kusto/src/prompts/question.ts b/extensions/kusto/src/prompts/question.ts
new file mode 100644
index 0000000000..b0678c668a
--- /dev/null
+++ b/extensions/kusto/src/prompts/question.ts
@@ -0,0 +1,73 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+// TODO: Common file with mssql
+import vscode = require('vscode');
+
+export class QuestionTypes {
+ public static get input(): string { return 'input'; }
+ public static get password(): string { return 'password'; }
+ public static get list(): string { return 'list'; }
+ public static get confirm(): string { return 'confirm'; }
+ public static get checkbox(): string { return 'checkbox'; }
+ public static get expand(): string { return 'expand'; }
+}
+
+// Question interface to clarify how to use the prompt feature
+// based on Bower Question format: https://github.com/bower/bower/blob/89069784bb46bfd6639b4a75e98a0d7399a8c2cb/packages/bower-logger/README.md
+export interface IQuestion {
+ // Type of question (see QuestionTypes)
+ type: string;
+ // Name of the question for disambiguation
+ name: string;
+ // Message to display to the user
+ message: string;
+ // Optional placeHolder to give more detailed information to the user
+ placeHolder?: any;
+ // Optional default value - this will be used instead of placeHolder
+ default?: any;
+ // optional set of choices to be used. Can be QuickPickItems or a simple name-value pair
+ choices?: Array;
+ // Optional validation function that returns an error string if validation fails
+ validate?: (value: any) => string;
+ // Optional pre-prompt function. Takes in set of answers so far, and returns true if prompt should occur
+ shouldPrompt?: (answers: { [id: string]: any }) => boolean;
+ // Optional action to take on the question being answered
+ onAnswered?: (value: any) => void;
+ // Optional set of options to support matching choices.
+ matchOptions?: vscode.QuickPickOptions;
+}
+
+// Pair used to display simple choices to the user
+export interface INameValueChoice {
+ name: string;
+ value: any;
+}
+
+// Generic object that can be used to define a set of questions and handle the result
+export interface IQuestionHandler {
+ // Set of questions to be answered
+ questions: IQuestion[];
+ // Optional callback, since questions may handle themselves
+ callback?: IPromptCallback;
+}
+
+export type Answers = { [key: string]: T };
+
+export interface IPrompter {
+ promptSingle(question: IQuestion, ignoreFocusOut?: boolean): Promise;
+ /**
+ * Prompts for multiple questions
+ *
+ * @returns Map of question IDs to results, or undefined if
+ * the user canceled the question session
+ */
+ prompt(questions: IQuestion[], ignoreFocusOut?: boolean): Promise | undefined>;
+ promptCallback(questions: IQuestion[], callback: IPromptCallback): void;
+}
+
+export interface IPromptCallback {
+ (answers: { [id: string]: any }): void;
+}
diff --git a/extensions/kusto/src/telemetry.ts b/extensions/kusto/src/telemetry.ts
new file mode 100644
index 0000000000..92ec9dfdf8
--- /dev/null
+++ b/extensions/kusto/src/telemetry.ts
@@ -0,0 +1,165 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import TelemetryReporter from 'vscode-extension-telemetry';
+import { ErrorAction, ErrorHandler, Message, CloseAction } from 'vscode-languageclient';
+
+import * as Utils from './utils';
+import * as Constants from './constants';
+import { localize } from './localize';
+
+const packageJson = require('../package.json');
+const viewKnownIssuesAction = localize('viewKnownIssuesText', "View Known Issues");
+
+export interface ITelemetryEventProperties {
+ [key: string]: string;
+}
+
+export interface ITelemetryEventMeasures {
+ [key: string]: number;
+}
+
+/**
+ * Filters error paths to only include source files. Exported to support testing
+ */
+export function FilterErrorPath(line: string): string | undefined {
+ if (line) {
+ let values: string[] = line.split('/out/');
+ if (values.length <= 1) {
+ // Didn't match expected format
+ return line;
+ } else {
+ return values[1];
+ }
+ }
+ return undefined;
+}
+
+export class Telemetry {
+ private static reporter: TelemetryReporter;
+ private static disabled: boolean;
+
+ /**
+ * Disable telemetry reporting
+ */
+ public static disable(): void {
+ this.disabled = true;
+ }
+
+ /**
+ * Initialize the telemetry reporter for use.
+ */
+ public static initialize(): void {
+ if (typeof this.reporter === 'undefined') {
+ // Check if the user has opted out of telemetry
+ if (!vscode.workspace.getConfiguration('telemetry').get('enableTelemetry', true)) {
+ this.disable();
+ return;
+ }
+
+ let packageInfo = Utils.getPackageInfo(packageJson)!;
+ this.reporter = new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
+ }
+ }
+
+ /**
+ * Send a telemetry event for an exception
+ */
+ public static sendTelemetryEventForException(
+ err: any, methodName: string, extensionConfigName: string): void {
+ let stackArray: string[];
+ let firstLine: string = '';
+ if (err !== undefined && err.stack !== undefined) {
+ stackArray = err.stack.split('\n');
+ if (stackArray !== undefined && stackArray.length >= 2) {
+ firstLine = stackArray[1]; // The fist line is the error message and we don't want to send that telemetry event
+ firstLine = FilterErrorPath(firstLine)!;
+ }
+ }
+
+ // Only adding the method name and the fist line of the stack trace. We don't add the error message because it might have PII
+ this.sendTelemetryEvent('Exception', { methodName: methodName, errorLine: firstLine });
+ }
+
+ /**
+ * Send a telemetry event using application insights
+ */
+ public static sendTelemetryEvent(
+ eventName: string,
+ properties?: ITelemetryEventProperties,
+ measures?: ITelemetryEventMeasures): void {
+
+ if (typeof this.disabled === 'undefined') {
+ this.disabled = false;
+ }
+
+ if (this.disabled || typeof (this.reporter) === 'undefined') {
+ // Don't do anything if telemetry is disabled
+ return;
+ }
+
+ if (!properties || typeof properties === 'undefined') {
+ properties = {};
+ }
+
+ try {
+ this.reporter.sendTelemetryEvent(eventName, properties, measures);
+ } catch (telemetryErr) {
+ // If sending telemetry event fails ignore it so it won't break the extension
+ console.error('Failed to send telemetry event. error: ' + telemetryErr);
+ }
+
+ }
+}
+
+/**
+ * Handle Language Service client errors
+ */
+export class LanguageClientErrorHandler implements ErrorHandler {
+
+ /**
+ * Show an error message prompt with a link to known issues wiki page
+ * @memberOf LanguageClientErrorHandler
+ */
+ showOnErrorPrompt(): void {
+ Telemetry.sendTelemetryEvent(Constants.serviceName + 'Crash');
+ vscode.window.showErrorMessage(
+ localize('serviceCrashMessage', "{0} component exited unexpectedly. Please restart Azure Data Studio.", Constants.serviceName),
+ viewKnownIssuesAction).then(action => {
+ if (action && action === viewKnownIssuesAction) {
+ vscode.env.openExternal(vscode.Uri.parse(Constants.serviceCrashLink));
+ }
+ });
+ }
+
+ /**
+ * Callback for language service client error
+ *
+ * @memberOf LanguageClientErrorHandler
+ */
+ error(error: Error, message: Message, count: number): ErrorAction {
+ this.showOnErrorPrompt();
+
+ // we don't retry running the service since crashes leave the extension
+ // in a bad, unrecovered state
+ return ErrorAction.Shutdown;
+ }
+
+ /**
+ * Callback for language service client closed
+ *
+ * @memberOf LanguageClientErrorHandler
+ */
+ closed(): CloseAction {
+ this.showOnErrorPrompt();
+
+ // we don't retry running the service since crashes leave the extension
+ // in a bad, unrecovered state
+ return CloseAction.DoNotRestart;
+ }
+}
+
+Telemetry.initialize();
diff --git a/extensions/kusto/src/types.ts b/extensions/kusto/src/types.ts
new file mode 100644
index 0000000000..b09b7d25f4
--- /dev/null
+++ b/extensions/kusto/src/types.ts
@@ -0,0 +1,14 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+'use strict';
+
+/**
+ * @returns whether the provided parameter is undefined or null.
+ */
+export function isUndefinedOrNull(obj: any): boolean {
+ // Intentional ==
+ // eslint-disable-next-line eqeqeq
+ return obj == undefined;
+}
diff --git a/extensions/kusto/src/typings/refs.d.ts b/extensions/kusto/src/typings/refs.d.ts
new file mode 100644
index 0000000000..dad0d96412
--- /dev/null
+++ b/extensions/kusto/src/typings/refs.d.ts
@@ -0,0 +1,8 @@
+/*---------------------------------------------------------------------------------------------
+ * 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/extensions/kusto/src/utils.ts b/extensions/kusto/src/utils.ts
new file mode 100644
index 0000000000..8f48991d93
--- /dev/null
+++ b/extensions/kusto/src/utils.ts
@@ -0,0 +1,141 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+//TODO: This is the same file from mssql. Move this into a common place.
+import * as azdata from 'azdata';
+import * as vscode from 'vscode';
+import * as path from 'path';
+import * as os from 'os';
+import * as findRemoveSync from 'find-remove';
+import { promises as fs } from 'fs';
+
+const configTracingLevel = 'tracingLevel';
+const configLogRetentionMinutes = 'logRetentionMinutes';
+const configLogFilesRemovalLimit = 'logFilesRemovalLimit';
+const extensionConfigSectionName = 'kusto';
+
+export function removeOldLogFiles(logPath: string, prefix: string): JSON {
+ return findRemoveSync(logPath, { age: { seconds: getConfigLogRetentionSeconds() }, limit: getConfigLogFilesRemovalLimit() });
+}
+
+export function getConfiguration(config: string = extensionConfigSectionName): vscode.WorkspaceConfiguration {
+ return vscode.workspace.getConfiguration(extensionConfigSectionName);
+}
+
+export function getConfigLogFilesRemovalLimit(): number | undefined {
+ let config = getConfiguration();
+ if (config && config[configLogFilesRemovalLimit]) {
+ return Number((config[configLogFilesRemovalLimit]).toFixed(0));
+ }
+ else {
+ return undefined;
+ }
+}
+
+export function getConfigLogRetentionSeconds(): number | undefined {
+ let config = getConfiguration();
+ if (config && config[configLogRetentionMinutes]) {
+ return Number((config[configLogRetentionMinutes] * 60).toFixed(0));
+ }
+ else {
+ return undefined;
+ }
+}
+
+export function getConfigTracingLevel(): string | undefined {
+ let config = getConfiguration();
+ if (config) {
+ return config[configTracingLevel];
+ }
+ else {
+ return undefined;
+ }
+}
+
+export function getLogFileName(prefix: string, pid: number): string {
+ return `${prefix}_${pid}.log`;
+}
+
+export function getCommonLaunchArgsAndCleanupOldLogFiles(logPath: string, fileName: string, executablePath: string): string[] {
+ let launchArgs: string[] = [];
+ launchArgs.push('--log-file');
+ let logFile = path.join(logPath, fileName);
+ launchArgs.push(logFile);
+
+ console.log(`logFile for ${path.basename(executablePath)} is ${logFile}`);
+ console.log(`This process (ui Extenstion Host) is pid: ${process.pid}`);
+ // Delete old log files
+ let deletedLogFiles = removeOldLogFiles(logPath, fileName);
+ console.log(`Old log files deletion report: ${JSON.stringify(deletedLogFiles)}`);
+ let config = getConfigTracingLevel();
+ if (config) {
+ launchArgs.push('--tracing-level');
+ launchArgs.push(config);
+ }
+ return launchArgs;
+}
+
+export function ensure(target: object, key: string): any {
+ if (target[key] === void 0) {
+ target[key] = {} as any;
+ }
+ return target[key];
+}
+
+export interface IPackageInfo {
+ name: string;
+ version: string;
+ aiKey: string;
+}
+
+export function getPackageInfo(packageJson: IPackageInfo): IPackageInfo | undefined {
+ if (packageJson) {
+ return {
+ name: packageJson.name,
+ version: packageJson.version,
+ aiKey: packageJson.aiKey
+ };
+ }
+ return undefined;
+}
+
+export function verifyPlatform(): Thenable {
+ if (os.platform() === 'darwin' && parseFloat(os.release()) < 16) {
+ return Promise.resolve(false);
+ } else {
+ return Promise.resolve(true);
+ }
+}
+
+export function getErrorMessage(error: Error | any, removeHeader: boolean = false): string {
+ let errorMessage: string = (error instanceof Error) ? error.message : error.toString();
+ if (removeHeader) {
+ errorMessage = removeErrorHeader(errorMessage);
+ }
+ return errorMessage;
+}
+
+export function removeErrorHeader(errorMessage: string): string {
+ if (errorMessage && errorMessage !== '') {
+ let header: string = 'Error:';
+ if (errorMessage.startsWith(header)) {
+ errorMessage = errorMessage.substring(header.length);
+ }
+ }
+ return errorMessage;
+}
+
+export function isObjectExplorerContext(object: any): object is azdata.ObjectExplorerContext {
+ return 'connectionProfile' in object && 'isConnectionNode' in object;
+}
+
+export async function exists(path: string): Promise {
+ try {
+ await fs.access(path);
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
diff --git a/extensions/kusto/syntaxes/kusto.tmLanguage b/extensions/kusto/syntaxes/kusto.tmLanguage
new file mode 100644
index 0000000000..4bc8063091
--- /dev/null
+++ b/extensions/kusto/syntaxes/kusto.tmLanguage
@@ -0,0 +1,448 @@
+
+
+
+
+ scopeName
+ source.kusto
+
+ fileTypes
+
+ csl
+ kusto
+
+
+ name
+ kusto file
+
+ patterns
+
+
+
+
+ match
+ \b(let|set|alias|declare|pattern|restrict|access|to|set)\b
+ name
+ keyword.control.kusto
+
+
+
+ match
+ \b(cluster|database|materialize|table|toscalar)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(and|or|!in|has|has_cs|hasprefix|hasprefix_cs|hassuffix|hassuffix_cs|contains|contains_cs|startswith|startswith_cs|endswith|endswith_cs|matches|regex|in|between)\b
+ name
+ keyword.operator.kusto
+
+
+
+
+ match
+ \b(binary_and|binary_not|binary_or|binary_shift_left|binary_shift_right|binary_xor)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(tobool|todatetime|todecimal|todouble|toguid|tohex|toreal|toint|tolong|tolower|toreal|tostring|totimespan|toupper|to_utf8|translate|treepath|trim|trim_end|trim_start|url_decode|url_encode|weekofyear|welch_test|zip)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ (?<=\.\d|\d|\d\W)(d|h|m|s|ms|microsecond|tick|seconds)\b
+ name
+ variable.language
+
+
+
+ match
+ \b(ago|datetime_add|datetime_part|datetime_diff|dayofmonth|dayofweek|dayofyear|endofday|endofmonth|endofweek|endofyear|format_datetime|format_timespan|getmonth|getyear|hourofday|make_datetime|make_timespan|monthofyear|now|startofday|startofmonth|startofweek|startofyear|todatetime|totimespan|weekofyear)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(array_concat|array_length|array_slice|array_split|array_strcat|bag_keys|pack|pack_all|pack_array|repeat|treepath|zip)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(next|prev|row_cumsum|row_number)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(toscalar)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(abs|acos|asin|atan|atan2|beta_cdf|beta_inv|beta_pdf|cos|cot|degrees|exp|exp100|exp2|gamma|hash|isfinite|isinf|isnan|log|log10|log2|loggamma|not|pi|pow|radians|rand|range|round|sign|sin|sqrt|tan|welch_test)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(column_ifexists|current_principal|cursor_after|extent_id|extent_tags|ingestion_time)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(bin|bin_at|ceiling|floor)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(case|coalesce|iif|iff|max_of|min_of)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(series_add|series_divide|series_equals|series_greater|series_greater_equals|series_less|series_less_equals|series_multiply|series_not_equals|series_subtract)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(series_decompose|series_decompose_anomalies|series_decompose_forecast|series_fill_backward|series_fill_const|series_fill_forward|series_fill_linear|series_fir|series_fit_2lines|series_fit_2lines_dynamic|series_fit_line|series_fit_line_dynamic|series_iir|series_outliers|series_periods_detect|series_periods_validate|series_seasonal|series_stats|series_stats_dynamic)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(base64_decodestring|base64_encodestring|countof|extract|extract_all|extractjson|indexof|isempty|isnotempty|isnotnull|isnull|parse_ipv4|parse_json|parse_url|parse_urlquery|parse_version|replace|reverse|split|strcat|strcat_delim|strcmp|strlen|strrep|substring|toupper|translate|trim|trim_end|trim_start|url_decode|url_encode)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(gettype)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(dcount_hll|hll_merge|percentile_tdigest|percentrank_tdigest|rank_tdigest|tdigest_merge)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(any|arg_max|arg_min|avg|avgif|buildschema|count|countif|dcount|dcountif|hll|hll_merge|make_bag|make_list|make_set|max|min|percentiles|stdev|stdevif|stdevp|sum|sumif|tdigest|tdigest_merge|variance|varianceif|variancep)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(next|prev|row_cumsum|row_number)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(activity_counts_metrics|sliding_window_counts|activity_metrics|new_activity_metrics|activity_engagement|active_users_count|session_count|funnel_sequence|funnel_sequence_completion)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \.create-or-alter
+ name
+ keyword.control.kusto
+
+
+
+ match
+ (?<=let ).+(?=\W*=)
+ name
+ entity.function.name.lambda.kusto
+
+
+
+ match
+ \b(with|folder|docstring|skipvalidation)\b
+ name
+ keyword.operator.kusto
+
+
+ match
+ \b(function)\b
+ name
+ variable.language
+
+
+
+
+ match
+ \b(bool|datetime|decimal|dynamic|guid|int|long|real|string|timespan)\b
+ name
+ storage.type
+
+
+
+ match
+ \b(datatable)(?=\W*\()
+ name
+ support.function
+
+
+
+
+ match
+ \b(as|consume|count|datatable|distinct|evaluate|extend|externaldata|facet|find|fork|getschema|invoke|join|limit|take|lookup|make-series|mv-expand|order|sort|project-away|project-rename|project|parse|partition|print|range|reduce|render|sample|sample-distinct|search|serialize|sort|summarize|take|top-nested|top|top-hitters|union|where)\b
+ name
+ keyword.operator.special.kusto
+
+
+
+ match
+ \b(autocluster|bag_unpack|basket|dcount_intersect|diffpatterns|narrow|pivot|preview|rolling_percentile|sql_request)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(on|kind|hint\.remote|hint\.strategy)\b
+ name
+ keyword.operator.kusto
+
+
+
+ match
+ (\$left|\$right)\b
+ name
+ keyword.other.kusto
+
+
+
+ match
+ \b(innerunique|inner|leftouter|rightouter|fullouter|leftanti|anti|leftantisemi|rightanti|rightantisemi|leftsemi|rightsemi|shuffle|broadcast)\b
+ name
+ keyword.other.kusto
+
+
+
+ match
+ \b(series_fir|series_iir|series_fit_line|series_fit_line_dynamic|series_fit_2lines|series_fit_2lines_dynamic|series_outliers|series_periods_detect|series_periods_validate|series_stats_dynamic|series_stats)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(series_fill_backward|series_fill_const|series_fill_forward|series_fill_linear)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(bag|array)\b
+ name
+ keyword.operator.kusto
+
+
+
+ match
+ \b(asc|desc|nulls first|nulls last)\b
+ name
+ keyword.other.kusto
+
+
+
+ match
+ \b(regex|simple|relaxed)\b
+ name
+ keyword.other.kusto
+
+
+
+ match
+ \b(anomalychart|areachart|barchart|columnchart|ladderchart|linechart|piechart|pivotchart|scatterchart|stackedareachart|table|timechart|timepivot)\b
+ name
+ support.function
+
+
+
+ match
+ \b(by|from|in|of|to|step|with)\b
+ name
+ keyword.operator.word
+
+
+
+
+ match
+ ".*?"
+ name
+ string.quoted.double
+
+
+ match
+ \{.*?\}
+ name
+ string.variable
+
+
+ match
+ '.*?'
+ name
+ string.quoted.single
+
+
+
+ match
+ //.*
+ name
+ comment.line
+
+
+ match
+ \b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*+)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?(?=\b|\w)
+ name
+ constant.numeric
+
+
+ match
+ \b(true|false)\b
+ name
+ constant.language
+
+
+
+ match
+ \b(array_strcat|make_dictionary|makelist|makeset|mvexpand|todynamic)(?=\W*\(|\b)
+ name
+ invalid.deprecated
+
+
+
+ match
+
+ name
+ invalid.illegal
+
+
+ uuid
+ FF0550E0-3A29-11E3-AA6E-0800200C9B77
+
+
diff --git a/extensions/kusto/themes/Kuskus.Dark.tmTheme b/extensions/kusto/themes/Kuskus.Dark.tmTheme
new file mode 100644
index 0000000000..7ec9df294a
--- /dev/null
+++ b/extensions/kusto/themes/Kuskus.Dark.tmTheme
@@ -0,0 +1,404 @@
+
+
+
+
+ name
+ [Kuskus] Kusto (Dark)
+ settings
+
+
+ settings
+
+ background
+ #272822
+ caret
+ #F8F8F0
+ foreground
+ #F8F8F2
+ invisibles
+ #3B3A32
+ lineHighlight
+ #3E3D32
+ selection
+ #49483E
+ findHighlight
+ #FFE792
+ findHighlightForeground
+ #000000
+ selectionBorder
+ #222218
+ activeGuide
+ #9D550FB0
+
+ bracketsForeground
+ #F8F8F2A5
+ bracketsOptions
+ underline
+
+ bracketContentsForeground
+ #F8F8F2A5
+ bracketContentsOptions
+ underline
+
+ tagsOptions
+ stippled_underline
+
+
+
+ name
+ Comment
+ scope
+ comment
+ settings
+
+ foreground
+ #75715E
+
+
+
+ name
+ String
+ scope
+ string
+ settings
+
+ foreground
+ #E6DB74
+
+
+
+ name
+ Number
+ scope
+ constant.numeric
+ settings
+
+ foreground
+ #AE81FF
+
+
+
+
+ name
+ Built-in constant
+ scope
+ constant.language
+ settings
+
+ foreground
+ #AE81FF
+
+
+
+ name
+ User-defined constant
+ scope
+ constant.character, constant.other
+ settings
+
+ foreground
+ #AE81FF
+
+
+
+ name
+ Variable
+ scope
+ variable
+ settings
+
+ fontStyle
+
+
+
+
+ name
+ Keyword
+ scope
+ keyword
+ settings
+
+ foreground
+ #F92672
+
+
+
+ name
+ Keyword operator words
+ scope
+ keyword.operator.word
+ settings
+
+ foreground
+ #FD971F
+
+
+
+ name
+ Storage
+ scope
+ storage
+ settings
+
+ fontStyle
+
+ foreground
+ #F92672
+
+
+
+ name
+ Storage type
+ scope
+ storage.type
+ settings
+
+ foreground
+ #66D9EF
+
+
+
+ name
+ Class name
+ scope
+ entity.name.class
+ settings
+
+ fontStyle
+ underline
+ foreground
+ #A6E22E
+
+
+
+ name
+ Inherited class
+ scope
+ entity.other.inherited-class
+ settings
+
+ fontStyle
+ italic underline
+ foreground
+ #A6E22E
+
+
+
+ name
+ Function name
+ scope
+ entity.name.function
+ settings
+
+ fontStyle
+
+ foreground
+ #A6E22E
+
+
+
+ name
+ Function argument
+ scope
+ variable.parameter
+ settings
+
+ fontStyle
+ italic
+ foreground
+ #FD971F
+
+ name
+ TODO better semantic scope
+ scope
+ variable.language
+ settings
+
+ foreground
+ #FD971F
+
+
+
+ name
+ Tag name
+ scope
+ entity.name.tag
+ settings
+
+ fontStyle
+
+ foreground
+ #F92672
+
+
+
+ name
+ Tag attribute
+ scope
+ entity.other.attribute-name
+ settings
+
+ fontStyle
+
+ foreground
+ #A6E22E
+
+
+
+ name
+ Library function
+ scope
+ support.function
+ settings
+
+ fontStyle
+
+ foreground
+ #A6E22E
+
+
+
+ name
+ Library constant
+ scope
+ support.constant
+ settings
+
+ fontStyle
+
+ foreground
+ #66D9EF
+
+
+
+ name
+ Library class/type
+ scope
+ support.type, support.class
+ settings
+
+ fontStyle
+ italic
+ foreground
+ #66D9EF
+
+
+
+ name
+ Library variable
+ scope
+ support.other.variable
+ settings
+
+ fontStyle
+
+
+
+
+ name
+ Invalid
+ scope
+ invalid
+ settings
+
+ background
+
+ fontStyle
+
+ foreground
+ #F8F8F0
+
+
+
+ name
+ Invalid deprecated
+ scope
+ invalid.deprecated
+ settings
+
+ fontStyle
+ underline
+ foreground
+ #FF0000
+
+
+
+ name
+ JSON String
+ scope
+ meta.structure.dictionary.json string.quoted.double.json
+ settings
+
+ foreground
+ #CFCFC2
+
+
+
+
+ name
+ diff.header
+ scope
+ meta.diff, meta.diff.header
+ settings
+
+ foreground
+ #75715E
+
+
+
+ name
+ diff.deleted
+ scope
+ markup.deleted
+ settings
+
+ foreground
+ #F92672
+
+
+
+ name
+ diff.inserted
+ scope
+ markup.inserted
+ settings
+
+ foreground
+ #A6E22E
+
+
+
+ name
+ diff.changed
+ scope
+ markup.changed
+ settings
+
+ foreground
+ #E6DB74
+
+
+
+
+ scope
+ constant.numeric.line-number.find-in-files - match
+ settings
+
+ foreground
+ #AE81FFA0
+
+
+
+ scope
+ entity.name.filename.find-in-files
+ settings
+
+ foreground
+ #E6DB74
+
+
+
+ uuid
+ D8D5E82E-3D5B-46B5-B38E-8C841C21347D
+
+
diff --git a/extensions/kusto/themes/Kuskus.KustoExplorer.Dark.tmTheme b/extensions/kusto/themes/Kuskus.KustoExplorer.Dark.tmTheme
new file mode 100644
index 0000000000..e9d2ffc411
--- /dev/null
+++ b/extensions/kusto/themes/Kuskus.KustoExplorer.Dark.tmTheme
@@ -0,0 +1,223 @@
+
+
+
+
+ name
+ [Kuskus] Kusto Explorer (Dark)
+ settings
+
+
+ settings
+
+ background
+ #272822
+ caret
+ #F8F8F0
+ foreground
+ #F8F8F2
+ invisibles
+ #3B3A32
+ lineHighlight
+ #3E3D32
+ selection
+ #49483E
+ findHighlight
+ #FFE792
+ findHighlightForeground
+ #000000
+ selectionBorder
+ #222218
+ activeGuide
+ #9D550FB0
+
+ bracketsForeground
+ #F8F8F2A5
+ bracketsOptions
+ underline
+
+ bracketContentsForeground
+ #F8F8F2A5
+ bracketContentsOptions
+ underline
+
+ tagsOptions
+ stippled_underline
+
+
+
+ name
+ Comment
+ scope
+ comment
+ settings
+
+ foreground
+ #60804B
+
+
+
+ name
+ String
+ scope
+ string
+ settings
+
+ foreground
+ #C16E40
+
+
+
+ name
+ Number
+ scope
+ constant.numeric
+ settings
+
+ foreground
+
+
+
+
+ name
+ Keyword operators
+ scope
+ keyword.operator.special.kusto
+ settings
+
+ foreground
+ #44C89E
+
+
+
+ name
+ Keyword operators
+ scope
+ keyword.operator
+ settings
+
+ foreground
+ #4E84D4
+
+
+
+ name
+ Keyword control
+ scope
+ keyword.control
+ settings
+
+ foreground
+ #4E84D4
+
+
+
+ name
+ Storage type
+ scope
+ storage.type
+ settings
+
+ foreground
+ #4E84D4
+
+
+
+
+ name
+ Function arguments and parameters
+ scope
+ variable.parameter
+ settings
+
+ foreground
+ #92CAEA
+
+
+
+ name
+ Lambda definitions
+ scope
+ entity.function.name.lambda.kusto
+ settings
+
+ foreground
+ #92CAEA
+
+
+
+ name
+ Library function
+ scope
+ support.function
+ settings
+
+ fontStyle
+
+ foreground
+ #4E84D4
+
+
+
+ name
+ Invalid (deprecated, illegal)
+ scope
+ invalid
+ settings
+
+ fontStyle
+ underline
+ foreground
+ #FF0000
+
+
+
+
+
+ name
+ diff.header
+ scope
+ meta.diff, meta.diff.header
+ settings
+
+ foreground
+ #75715E
+
+
+
+ name
+ diff.deleted
+ scope
+ markup.deleted
+ settings
+
+ foreground
+ #F92672
+
+
+
+ name
+ diff.inserted
+ scope
+ markup.inserted
+ settings
+
+ foreground
+ #A6E22E
+
+
+
+ name
+ diff.changed
+ scope
+ markup.changed
+ settings
+
+ foreground
+ #E6DB74
+
+
+
+ uuid
+ D8D5E82E-3D5B-46B5-B38E-8C841C21347D
+
+
diff --git a/extensions/kusto/themes/kuskus-kusto-dark.json b/extensions/kusto/themes/kuskus-kusto-dark.json
new file mode 100644
index 0000000000..065ed78d59
--- /dev/null
+++ b/extensions/kusto/themes/kuskus-kusto-dark.json
@@ -0,0 +1,12 @@
+{
+ "tokenColors": "./Kuskus.Dark.tmTheme",
+ "colors": {
+ "editor.background": "#272822",
+ "editorCursor.foreground": "#F8F8F0",
+ "editor.foreground": "#F8F8F2",
+ "editorWhitespace.foreground": "#3B3A32",
+ "editor.lineHighlightBackground": "#3E3D32",
+ "editor.selectionBackground": "#49483E"
+ },
+ "name": "[Kuskus] Kusto (Dark)"
+}
\ No newline at end of file
diff --git a/extensions/kusto/themes/kuskus-kusto-explorer-dark.json b/extensions/kusto/themes/kuskus-kusto-explorer-dark.json
new file mode 100644
index 0000000000..6e57537959
--- /dev/null
+++ b/extensions/kusto/themes/kuskus-kusto-explorer-dark.json
@@ -0,0 +1,12 @@
+{
+ "tokenColors": "./Kuskus.KustoExplorer.Dark.tmTheme",
+ "colors": {
+ "editor.background": "#222224",
+ "editorCursor.foreground": "#F8F8F0",
+ "editor.foreground": "#F8F8F2",
+ "editorWhitespace.foreground": "#3B3A32",
+ "editor.lineHighlightBackground": "#3D4247",
+ "editor.selectionBackground": "#1268AC"
+ },
+ "name": "[Kuskus] Kusto Explorer (Dark)"
+}
\ No newline at end of file
diff --git a/extensions/kusto/tsconfig.json b/extensions/kusto/tsconfig.json
new file mode 100644
index 0000000000..111371f94b
--- /dev/null
+++ b/extensions/kusto/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../shared.tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out",
+ "strict": true,
+ "noUnusedParameters": false,
+ "noImplicitAny": false
+ },
+ "include": [
+ "src/**/*"
+ ]
+}
diff --git a/extensions/kusto/yarn.lock b/extensions/kusto/yarn.lock
new file mode 100644
index 0000000000..e47873d96f
--- /dev/null
+++ b/extensions/kusto/yarn.lock
@@ -0,0 +1,677 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@types/caseless@*":
+ version "0.12.2"
+ resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8"
+ integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==
+
+"@types/kerberos@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@types/kerberos/-/kerberos-1.1.0.tgz#fb1e5bc4f7272d152f67714deb100d5de7cb3e48"
+ integrity sha512-ixpV6PSSMnIVpMNCLQ0gWguC2+pBxc0LeUCv9Ugj54opVSVFXfPNYP6sMa7UHvicYGDXAyHQSAzQC8VYEIgdFQ==
+
+"@types/node@*":
+ version "12.7.2"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.2.tgz#c4e63af5e8823ce9cc3f0b34f7b998c2171f0c44"
+ integrity sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==
+
+"@types/request@^2.48.2":
+ version "2.48.2"
+ resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.2.tgz#936374cbe1179d7ed529fc02543deb4597450fed"
+ integrity sha512-gP+PSFXAXMrd5PcD7SqHeUjdGshAI8vKQ3+AvpQr3ht9iQea+59LOKvKITcQI+Lg+1EIkDP6AFSBUJPWG8GDyA==
+ dependencies:
+ "@types/caseless" "*"
+ "@types/node" "*"
+ "@types/tough-cookie" "*"
+ form-data "^2.5.0"
+
+"@types/through2@^2.0.34":
+ version "2.0.34"
+ resolved "https://registry.yarnpkg.com/@types/through2/-/through2-2.0.34.tgz#9c2a259a238dace2a05a2f8e94b786961bc27ac4"
+ integrity sha512-nhRG8+RuG/L+0fAZBQYaRflXKjTrHOKH8MFTChnf+dNVMxA3wHYYrfj0tztK0W51ABXjGfRCDc0vRkecCOrsow==
+ dependencies:
+ "@types/node" "*"
+
+"@types/tough-cookie@*":
+ version "2.3.5"
+ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.5.tgz#9da44ed75571999b65c37b60c9b2b88db54c585d"
+ integrity sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==
+
+agent-base@4, agent-base@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
+ integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
+ dependencies:
+ es6-promisify "^5.0.0"
+
+applicationinsights@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.6.tgz#bc201810de91cea910dab34e8ad35ecde488edeb"
+ integrity sha512-VQT3kBpJVPw5fCO5n+WUeSx0VHjxFtD7znYbILBlVgOS9/cMDuGFmV2Br3ObzFyZUDGNbEfW36fD1y2/vAiCKw==
+ dependencies:
+ diagnostic-channel "0.2.0"
+ diagnostic-channel-publishers "0.2.1"
+ zone.js "0.7.6"
+
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+ integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
+
+balanced-match@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+ integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+
+base64-js@^1.0.2:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
+ integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
+
+bl@^1.0.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
+ integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==
+ dependencies:
+ readable-stream "^2.3.5"
+ safe-buffer "^5.1.1"
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+buffer-alloc-unsafe@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
+ integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
+
+buffer-alloc@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
+ integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
+ dependencies:
+ buffer-alloc-unsafe "^1.1.0"
+ buffer-fill "^1.0.0"
+
+buffer-crc32@~0.2.3:
+ version "0.2.13"
+ resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
+ integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
+
+buffer-fill@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
+ integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
+
+buffer@^5.2.1:
+ version "5.6.0"
+ resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
+ integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
+ dependencies:
+ base64-js "^1.0.2"
+ ieee754 "^1.1.4"
+
+combined-stream@^1.0.6:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828"
+ integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==
+ dependencies:
+ delayed-stream "~1.0.0"
+
+commander@^2.8.1:
+ version "2.20.3"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
+ integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+
+core-util-is@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+ integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
+
+"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#1.1.0":
+ version "1.1.0"
+ resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/61a6e4dd4662a225259b6ba8a7eca215fab0cfdc"
+ dependencies:
+ vscode-languageclient "5.2.1"
+
+debug@3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+ integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+ dependencies:
+ ms "2.0.0"
+
+debug@^3.1.0:
+ version "3.2.6"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
+ integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
+ dependencies:
+ ms "^2.1.1"
+
+decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
+ integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==
+ dependencies:
+ file-type "^5.2.0"
+ is-stream "^1.1.0"
+ tar-stream "^1.5.2"
+
+decompress-tarbz2@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
+ integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==
+ dependencies:
+ decompress-tar "^4.1.0"
+ file-type "^6.1.0"
+ is-stream "^1.1.0"
+ seek-bzip "^1.0.5"
+ unbzip2-stream "^1.0.9"
+
+decompress-targz@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
+ integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==
+ dependencies:
+ decompress-tar "^4.1.1"
+ file-type "^5.2.0"
+ is-stream "^1.1.0"
+
+decompress-unzip@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
+ integrity sha1-3qrM39FK6vhVePczroIQ+bSEj2k=
+ dependencies:
+ file-type "^3.8.0"
+ get-stream "^2.2.0"
+ pify "^2.3.0"
+ yauzl "^2.4.2"
+
+decompress@^4.2.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.1.tgz#007f55cc6a62c055afa37c07eb6a4ee1b773f118"
+ integrity sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==
+ dependencies:
+ decompress-tar "^4.0.0"
+ decompress-tarbz2 "^4.0.0"
+ decompress-targz "^4.0.0"
+ decompress-unzip "^4.0.1"
+ graceful-fs "^4.1.10"
+ make-dir "^1.0.0"
+ pify "^2.3.0"
+ strip-dirs "^2.0.0"
+
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+ integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
+
+diagnostic-channel-publishers@0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
+ integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM=
+
+diagnostic-channel@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
+ integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
+ dependencies:
+ semver "^5.3.0"
+
+end-of-stream@^1.0.0:
+ version "1.4.4"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
+ integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
+ dependencies:
+ once "^1.4.0"
+
+es6-promise@^4.0.3:
+ version "4.2.8"
+ resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
+ integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
+
+es6-promisify@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
+ integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
+ dependencies:
+ es6-promise "^4.0.3"
+
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+
+eventemitter2@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
+ integrity sha1-YZegldX7a1folC9v1+qtY6CclFI=
+
+fd-slicer@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
+ integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
+ dependencies:
+ pend "~1.2.0"
+
+figures@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
+ integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=
+ dependencies:
+ escape-string-regexp "^1.0.5"
+
+file-type@^3.8.0:
+ version "3.9.0"
+ resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
+ integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek=
+
+file-type@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
+ integrity sha1-LdvqfHP/42No365J3DOMBYwritY=
+
+file-type@^6.1.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
+ integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==
+
+find-remove@1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/find-remove/-/find-remove-1.2.1.tgz#afd93400d23890e018ea197591e9d850d3d049a2"
+ integrity sha512-zcspBi9mWAyM9YTcVJLkI/x6rbjSDqHijjPa0vTwEmVZnYSmvYMtixDkUnSnuv2xAAkc9fblpkCg91paBIJaLw==
+ dependencies:
+ fmerge "1.2.0"
+ rimraf "2.6.2"
+
+fmerge@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/fmerge/-/fmerge-1.2.0.tgz#36e99d2ae255e3ee1af666b4df780553671cf692"
+ integrity sha1-NumdKuJV4+4a9ma033gFU2cc9pI=
+
+form-data@^2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.0.tgz#094ec359dc4b55e7d62e0db4acd76e89fe874d37"
+ integrity sha512-WXieX3G/8side6VIqx44ablyULoGruSde5PNTxoUyo5CeyAMX6nVWUd0rgist/EuX655cjhUhTo1Fo3tRYqbcA==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.6"
+ mime-types "^2.1.12"
+
+fs-constants@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
+ integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+
+get-stream@^2.2.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
+ integrity sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=
+ dependencies:
+ object-assign "^4.0.1"
+ pinkie-promise "^2.0.0"
+
+glob@^7.0.5:
+ version "7.1.3"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
+ integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+graceful-fs@^4.1.10:
+ version "4.2.4"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
+ integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
+
+http-proxy-agent@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
+ integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
+ dependencies:
+ agent-base "4"
+ debug "3.1.0"
+
+https-proxy-agent@^2.2.1:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
+ integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
+ dependencies:
+ agent-base "^4.3.0"
+ debug "^3.1.0"
+
+ieee754@^1.1.4:
+ version "1.1.13"
+ resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
+ integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+ integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
+
+inherits@~2.0.3:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+is-natural-number@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
+ integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=
+
+is-stream@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
+ integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
+
+isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+ integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+
+make-dir@^1.0.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
+ integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
+ dependencies:
+ pify "^3.0.0"
+
+mime-db@~1.38.0:
+ version "1.38.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad"
+ integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==
+
+mime-types@^2.1.12:
+ version "2.1.22"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.22.tgz#fe6b355a190926ab7698c9a0556a11199b2199bd"
+ integrity sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==
+ dependencies:
+ mime-db "~1.38.0"
+
+minimatch@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+ integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+ integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
+
+mkdirp@^0.5.1:
+ version "0.5.5"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
+ integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
+ dependencies:
+ minimist "^1.2.5"
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+
+ms@^2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+object-assign@^4.0.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
+
+once@^1.3.0, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+ dependencies:
+ wrappy "1"
+
+os-tmpdir@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+ integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+
+pend@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
+ integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
+
+pify@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
+ integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
+
+pify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
+ integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
+
+pinkie-promise@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
+ integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
+ dependencies:
+ pinkie "^2.0.0"
+
+pinkie@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
+ integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
+
+process-nextick-args@~2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+ integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
+readable-stream@^2.3.0, readable-stream@^2.3.5:
+ version "2.3.7"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
+ integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
+
+rimraf@2.6.2:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
+ integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==
+ dependencies:
+ glob "^7.0.5"
+
+safe-buffer@^5.1.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+seek-bzip@^1.0.5:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.6.tgz#35c4171f55a680916b52a07859ecf3b5857f21c4"
+ integrity sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==
+ dependencies:
+ commander "^2.8.1"
+
+semver@^5.3.0:
+ version "5.6.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
+ integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
+
+semver@^5.5.0:
+ version "5.7.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
+ integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
+
+"service-downloader@github:anthonydresser/service-downloader#0.1.6":
+ version "0.1.6"
+ resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/fd4114b145ee2b4f1f7950c23f10f4b1b28a2bfc"
+ dependencies:
+ decompress "^4.2.0"
+ eventemitter2 "^5.0.1"
+ http-proxy-agent "^2.1.0"
+ https-proxy-agent "^2.2.1"
+ mkdirp "^0.5.1"
+ tmp "^0.0.33"
+
+string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+ integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+ dependencies:
+ safe-buffer "~5.1.0"
+
+strip-dirs@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
+ integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==
+ dependencies:
+ is-natural-number "^4.0.1"
+
+tar-stream@^1.5.2:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
+ integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
+ dependencies:
+ bl "^1.0.0"
+ buffer-alloc "^1.2.0"
+ end-of-stream "^1.0.0"
+ fs-constants "^1.0.0"
+ readable-stream "^2.3.0"
+ to-buffer "^1.1.1"
+ xtend "^4.0.0"
+
+through@^2.3.8:
+ version "2.3.8"
+ resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
+ integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
+
+tmp@^0.0.33:
+ version "0.0.33"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
+ integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
+ dependencies:
+ os-tmpdir "~1.0.2"
+
+to-buffer@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
+ integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==
+
+unbzip2-stream@^1.0.9:
+ version "1.4.3"
+ resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7"
+ integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==
+ dependencies:
+ buffer "^5.2.1"
+ through "^2.3.8"
+
+util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+ integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
+
+vscode-extension-telemetry@0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.0.tgz#3cdcb61d03829966bd04b5f11471a1e40d6abaad"
+ integrity sha512-WVCnP+uLxlqB6UD98yQNV47mR5Rf79LFxpuZhSPhEf0Sb4tPZed3a63n003/dchhOwyCTCBuNN4n8XKJkLEI1Q==
+ dependencies:
+ applicationinsights "1.0.6"
+
+vscode-jsonrpc@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz#a7bf74ef3254d0a0c272fab15c82128e378b3be9"
+ integrity sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg==
+
+vscode-languageclient@5.2.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-5.2.1.tgz#7cfc83a294c409f58cfa2b910a8cfeaad0397193"
+ integrity sha512-7jrS/9WnV0ruqPamN1nE7qCxn0phkH5LjSgSp9h6qoJGoeAKzwKz/PF6M+iGA/aklx4GLZg1prddhEPQtuXI1Q==
+ dependencies:
+ semver "^5.5.0"
+ vscode-languageserver-protocol "3.14.1"
+
+vscode-languageserver-protocol@3.14.1:
+ version "3.14.1"
+ resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.14.1.tgz#b8aab6afae2849c84a8983d39a1cf742417afe2f"
+ integrity sha512-IL66BLb2g20uIKog5Y2dQ0IiigW0XKrvmWiOvc0yXw80z3tMEzEnHjaGAb3ENuU7MnQqgnYJ1Cl2l9RvNgDi4g==
+ dependencies:
+ vscode-jsonrpc "^4.0.0"
+ vscode-languageserver-types "3.14.0"
+
+vscode-languageserver-types@3.14.0:
+ version "3.14.0"
+ resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz#d3b5952246d30e5241592b6dde8280e03942e743"
+ integrity sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==
+
+vscode-nls@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002"
+ integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw==
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+
+xtend@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
+ integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
+
+yauzl@^2.4.2:
+ version "2.10.0"
+ resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
+ integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
+ dependencies:
+ buffer-crc32 "~0.2.3"
+ fd-slicer "~1.1.0"
+
+zone.js@0.7.6:
+ version "0.7.6"
+ resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
+ integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk=
diff --git a/src/sql/azdata.d.ts b/src/sql/azdata.d.ts
index 8f2034cce5..db036ad541 100644
--- a/src/sql/azdata.d.ts
+++ b/src/sql/azdata.d.ts
@@ -284,9 +284,9 @@ declare module 'azdata' {
// Object Explorer interfaces -----------------------------------------------------------------------
export interface ObjectExplorerSession {
success: boolean;
- sessionId: string;
+ sessionId?: string;
rootNode: NodeInfo;
- errorMessage: string;
+ errorMessage?: string;
}
/**
@@ -296,12 +296,12 @@ declare module 'azdata' {
export interface NodeInfo {
nodePath: string;
nodeType: string;
- nodeSubType: string;
+ nodeSubType?: string;
nodeStatus?: string;
label: string;
isLeaf: boolean;
metadata?: ObjectMetadata;
- errorMessage: string;
+ errorMessage?: string;
/**
* Optional iconType for the object in the tree. Currently this only supports
* an icon name or SqlThemeIcon name, rather than a path to an icon.
@@ -1228,7 +1228,7 @@ declare module 'azdata' {
}
export interface ObjectExplorerExpandInfo {
- sessionId: string;
+ sessionId?: string;
nodePath: string;
nodes: NodeInfo[];
errorMessage?: string;
@@ -1236,7 +1236,7 @@ declare module 'azdata' {
export interface ExpandNodeInfo {
sessionId: string;
- nodePath: string;
+ nodePath: string | undefined;
}
export interface FindNodesInfo {
@@ -1299,7 +1299,7 @@ declare module 'azdata' {
}
export interface IconProvider extends DataProvider {
- getConnectionIconId(connection: IConnectionProfile, serverInfo: ServerInfo): Thenable;
+ getConnectionIconId(connection: IConnectionProfile, serverInfo: ServerInfo): Thenable;
}
// Admin Services interfaces -----------------------------------------------------------------------
diff --git a/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts b/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts
index 7df2f5e8d7..5eed28dfb4 100644
--- a/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts
+++ b/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts
@@ -219,7 +219,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
}
await this.closeSession(connection.providerName, session);
delete this._activeObjectExplorerNodes[connectionUri];
- delete this._sessions[session.sessionId];
+ delete this._sessions[session.sessionId!];
}
}
@@ -232,10 +232,10 @@ export class ObjectExplorerService implements IObjectExplorerService {
this.logService.error(expandResponse.errorMessage);
}
- let sessionStatus = this._sessions[expandResponse.sessionId];
+ let sessionStatus = this._sessions[expandResponse.sessionId!];
let foundSession = false;
if (sessionStatus) {
- let nodeStatus = this._sessions[expandResponse.sessionId].nodes[expandResponse.nodePath];
+ let nodeStatus = this._sessions[expandResponse.sessionId!].nodes[expandResponse.nodePath];
foundSession = !!nodeStatus;
if (foundSession && nodeStatus.expandEmitter) {
nodeStatus.expandEmitter.fire(expandResponse);
@@ -261,22 +261,22 @@ export class ObjectExplorerService implements IObjectExplorerService {
private async handleSessionCreated(session: azdata.ObjectExplorerSession): Promise {
let connection: ConnectionProfile | undefined = undefined;
let errorMessage: string | undefined = undefined;
- if (this._sessions[session.sessionId]) {
- connection = this._sessions[session.sessionId].connection;
+ if (this._sessions[session.sessionId!]) {
+ connection = this._sessions[session.sessionId!].connection;
try {
if (session.success && session.rootNode) {
let server = this.toTreeNode(session.rootNode, undefined);
server.connection = connection;
server.session = session;
- this._activeObjectExplorerNodes[connection.id] = server;
+ this._activeObjectExplorerNodes[connection!.id] = server;
}
else {
errorMessage = session && session.errorMessage ? session.errorMessage : errSessionCreateFailed;
this.logService.error(errorMessage);
}
// Send on session created about the session to all node providers so they can prepare for node expansion
- let nodeProviders = this._nodeProviders[connection.providerName];
+ let nodeProviders = this._nodeProviders[connection!.providerName];
if (nodeProviders) {
const promises = nodeProviders.map(p => p.handleSessionOpen(session));
await Promise.all(promises);
@@ -284,7 +284,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
} catch (error) {
this.logService.warn(`cannot handle the session ${session.sessionId} in all nodeProviders`);
} finally {
- this.sendUpdateNodeEvent(connection, errorMessage);
+ this.sendUpdateNodeEvent(connection!, errorMessage);
}
}
else {
@@ -296,8 +296,8 @@ export class ObjectExplorerService implements IObjectExplorerService {
* Gets called when session is disconnected
*/
public onSessionDisconnected(handle: number, session: azdata.ObjectExplorerSession): void {
- if (this._sessions[session.sessionId]) {
- let connection: ConnectionProfile = this._sessions[session.sessionId].connection;
+ if (this._sessions[session.sessionId!]) {
+ let connection: ConnectionProfile = this._sessions[session.sessionId!].connection;
if (connection && this._connectionManagementService.isProfileConnected(connection)) {
let uri: string = Utils.generateUri(connection);
if (this._serverTreeView?.isObjectExplorerConnectionUri(uri)) {
@@ -383,10 +383,10 @@ export class ObjectExplorerService implements IObjectExplorerService {
refresh: boolean = false): Promise {
let self = this;
return new Promise((resolve, reject) => {
- if (session.sessionId in self._sessions && self._sessions[session.sessionId]) {
+ if (session.sessionId! in self._sessions && self._sessions[session.sessionId!]) {
let newRequest = false;
- if (!self._sessions[session.sessionId].nodes[nodePath]) {
- self._sessions[session.sessionId].nodes[nodePath] = {
+ if (!self._sessions[session.sessionId!].nodes[nodePath]) {
+ self._sessions[session.sessionId!].nodes[nodePath] = {
expandEmitter: new Emitter()
};
newRequest = true;
@@ -402,7 +402,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
allProviders.push(...nodeProviders);
}
- self._sessions[session.sessionId].nodes[nodePath].expandEmitter.event((expandResult) => {
+ self._sessions[session.sessionId!].nodes[nodePath].expandEmitter.event((expandResult: NodeExpandInfoWithProviderId) => {
if (expandResult && expandResult.providerId) {
resultMap.set(expandResult.providerId, expandResult);
} else {
@@ -415,14 +415,14 @@ export class ObjectExplorerService implements IObjectExplorerService {
// Have to delete it after get all reponses otherwise couldn't find session for not the first response
if (newRequest) {
- delete self._sessions[session.sessionId].nodes[nodePath];
+ delete self._sessions[session.sessionId!].nodes[nodePath];
}
}
});
if (newRequest) {
allProviders.forEach(provider => {
self.callExpandOrRefreshFromProvider(provider, {
- sessionId: session.sessionId,
+ sessionId: session.sessionId!,
nodePath: nodePath
}, refresh).then(isExpanding => {
if (!isExpanding) {
@@ -506,9 +506,12 @@ export class ObjectExplorerService implements IObjectExplorerService {
public closeSession(providerId: string, session: azdata.ObjectExplorerSession): Promise {
// Complete any requests that are still open for the session
- let sessionStatus = this._sessions[session.sessionId];
+ let sessionStatus = this._sessions[session.sessionId!];
if (sessionStatus && sessionStatus.nodes) {
- entries(sessionStatus.nodes).forEach(([nodePath, nodeStatus]: [string, NodeStatus]) => {
+ entries(sessionStatus.nodes).forEach((entry) => {
+ const nodePath: string = entry[0];
+ const nodeStatus: NodeStatus = entry[1] as NodeStatus;
+
if (nodeStatus.expandEmitter) {
nodeStatus.expandEmitter.fire({
sessionId: session.sessionId,
@@ -606,7 +609,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
}
let node = new TreeNode(nodeInfo.nodeType, nodeInfo.label, isLeaf, nodeInfo.nodePath,
- nodeInfo.nodeSubType, nodeInfo.nodeStatus, parent, nodeInfo.metadata, nodeInfo.iconType, {
+ nodeInfo.nodeSubType!, nodeInfo.nodeStatus, parent, nodeInfo.metadata, nodeInfo.iconType, {
getChildren: (treeNode?: TreeNode) => this.getChildren(treeNode),
isExpanded: treeNode => this.isExpanded(treeNode),
setNodeExpandedState: async (treeNode, expandedState) => await this.setNodeExpandedState(treeNode, expandedState),