Initial LiveShare extension scaffolding (#7170)

* LiveShare initial shared connection

* Various cleanups

* Fix type

* Fix hygiene
This commit is contained in:
Karl Burtram
2019-09-11 15:24:08 -07:00
committed by GitHub
parent 9765b0ed8e
commit 9df66deb81
33 changed files with 3126 additions and 36 deletions

View File

@@ -0,0 +1,5 @@
src/**
out/**
tsconfig.json
extension.webpack.config.js
yarn.lock

View File

@@ -0,0 +1,30 @@
{
"name": "liveshare",
"version": "0.1.0",
"publisher": "Microsoft",
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
"activationEvents": [
"*"
],
"engines": {
"vscode": "*"
},
"main": "./out/main",
"extensionDependencies": [
"vscode.sql"
],
"scripts": {
"compile": "gulp compile-extension:liveshare"
},
"devDependencies": {
"@types/node": "12.0.9",
"ts-loader": "^5.3.3",
"tslint": "^5.12.1",
"typescript": "^3.3.1",
"vscode": "^1.1.33"
},
"dependencies": {
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.3.0",
"vsls": "^0.3.1291"
}
}

View File

@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Provider Constants
*/
export const LiveShareProviderId: string = 'ads-liveshare';
export const LiveShareServiceName: string = 'ads-liveshare';
export const VslsSchema: string = 'vsls';
/**
* Connection Provider Constants
*/
export const connectRequest = 'connect';
export const disconnectRequest = 'disconnect';
export const cancelConnectRequest = 'cancelConnect';
export const changeDatabaseRequest = 'changeDatabase';
export const listDatabasesRequest = 'listDatabases';
export const getConnectionStringRequest = 'getConnectionString';
export const buildConnectionInfoRequest = 'buildConnectionInfo';
export const rebuildIntellisenseCacheRequest = 'rebuildIntelliSenseCache';
/**
* Query Provider Constants
*/
export const cancelQueryRequest = 'cancelQuery';
export const runQueryRequest = 'runQuery';
export const runQueryStatementRequest = 'runQueryStatement';
export const runQueryStringRequest = 'runQueryString';
export const runQueryAndReturnRequest = 'runQueryAndReturn';
export const parseSyntaxRequest = 'parseSyntax';
export const getQueryRowsRequest = 'getQueryRows';
export const disposeQueryRequest = 'disposeQuery';
export const setQueryExecutionOptionsRequest = 'setQueryExecutionOptions';
export const saveResultsRequest = 'saveResultsRequest';

View File

@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* 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 { LiveShare, SharedServiceProxy } from './liveshare';
import { LiveShareProviderId, LiveShareServiceName, VslsSchema } from './constants';
import { ConnectionProvider } from './providers/connectionProvider';
import { StatusProvider, LiveShareDocumentState } from './providers/statusProvider';
import { QueryProvider } from './providers/queryProvider';
declare var require: any;
let vsls = require('vsls');
export class GuestSessionManager {
private _statusProvider: StatusProvider;
constructor(
context: vscode.ExtensionContext,
vslsApi: LiveShare
) {
let self = this;
vscode.workspace.onDidOpenTextDocument(params => this.onDidOpenTextDocument(params));
vslsApi!.onDidChangeSession(async function onLiveShareSessionCHange(e: any) {
const isHost = e.session.role === vsls.Role.Host;
if (!e.session.id && isHost) {
return;
}
const sharedServiceProxy: SharedServiceProxy = await vslsApi.getSharedService(LiveShareServiceName);
if (!sharedServiceProxy) {
vscode.window.showErrorMessage('Could not access a shared service. You have to set "liveshare.features" to "experimental" in your user settings in order to use this extension.');
return;
}
const connectionProvider = new ConnectionProvider(isHost, vslsApi, sharedServiceProxy);
const queryProvider = new QueryProvider(false);
queryProvider.initialize(false, sharedServiceProxy);
self._statusProvider = new StatusProvider(
isHost,
vslsApi,
connectionProvider,
sharedServiceProxy);
});
}
private async onDidOpenTextDocument(doc: vscode.TextDocument): Promise<void> {
if (this._statusProvider && this.isLiveShareDocument(doc)) {
let documentState: LiveShareDocumentState = await this._statusProvider.getDocumentState(doc);
if (documentState) {
let queryDocument = await azdata.queryeditor.getQueryDocument(doc.uri.toString());
if (queryDocument) {
let connectionOptions: Map<string, any> = new Map<string, any>();
connectionOptions['providerName'] = LiveShareProviderId;
connectionOptions['serverName'] = documentState.serverName;
connectionOptions['databaseName'] = documentState.databaseName;
connectionOptions['userName'] = 'liveshare';
connectionOptions['password'] = 'liveshare';
connectionOptions['authenticationType'] = 'liveshare';
connectionOptions['savePassword'] = false;
connectionOptions['saveProfile'] = false;
let profile = azdata.connection.ConnectionProfile.createFrom(connectionOptions);
queryDocument.connect(profile);
}
}
}
}
private isLiveShareDocument(doc: vscode.TextDocument): boolean {
return doc && doc.uri.scheme === VslsSchema;
}
}

View File

@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { LiveShare, SharedService } from './liveshare';
import { ConnectionProvider } from './providers/connectionProvider';
import { QueryProvider } from './providers/queryProvider';
import { StatusProvider } from './providers/statusProvider';
import { LiveShareServiceName } from './constants';
declare var require: any;
let vsls = require('vsls');
export class HostSessionManager {
constructor(
context: vscode.ExtensionContext,
vslsApi: LiveShare
) {
vslsApi!.onDidChangeSession(async function onLiveShareSessionCHange(e: any) {
const isHost = e.session.role === vsls.Role.Host;
if (!isHost) {
return;
}
const sharedService: SharedService = await vslsApi.shareService(LiveShareServiceName);
if (!sharedService) {
vscode.window.showErrorMessage('Could not create a shared service. You have to set "liveshare.features" to "experimental" in your user settings in order to use this extension.');
return;
}
const connectionProvider = new ConnectionProvider(isHost, vslsApi, sharedService);
const queryProvider = new QueryProvider(true);
queryProvider.initialize(true, sharedService);
/* tslint:disable:no-unused-expression */
new StatusProvider(
isHost,
vslsApi,
connectionProvider,
sharedService);
/* tslint:enable:no-unused-expression */
});
}
}

View File

@@ -0,0 +1,559 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Entrypoint and type definitions for Live Share for VS Code extension API
*/
import * as vscode from 'vscode';
/**
* Root API that is used to acquire access to the main Live Share API.
*
* An implementation of this interface is returned by the Live Share extension's
* activation function. Ordinarily this interface is not used directly; use the
* `getApiAsync()` helper function above instead.
*/
export interface LiveShareExtension {
/**
* Requests a specific version of the Live Share API for use by another extension.
*
* @returns a promise that resolves to the requested API, or `null` if the requested
* API is not available
*/
getApi(requestedApiVersion: string): Promise<LiveShare | null>;
}
/**
* Forward definition of the ContactServiceProvider interface
*/
export interface ContactServiceProvider {
}
/**
* Main API that enables other VS Code extensions to access Live Share capabilities.
*/
export interface LiveShare {
/**
* Status of participation in a sharing session, if any.
* Also includes the Live Share user info, if signed in.
*/
readonly session: Session;
/**
* Event that notifies listeners when participation in a sharing session
* starts or stops.
*/
readonly onDidChangeSession: vscode.Event<SessionChangeEvent>;
/** List of peers connected to the current sharing session, NOT including oneself. */
readonly peers: Peer[];
/** Event that notifies listeners when peers join or leave the session. */
readonly onDidChangePeers: vscode.Event<PeersChangeEvent>;
/**
* Starts a new session, sharing the currenly opened workspace.
* Or if sharing was already started, retrieves the join link.
*
* Not valid when joined to a session as a guest.
*
* @returns Join link for the new or existing session, or `null` if sharing failed.
*/
share(options?: ShareOptions): Promise<vscode.Uri | null>;
/**
* Joins a shared session using a link acquired from the host.
*
* Note joining another session requires either reloading the current window
* (and all extensions) or opening a new window.
*
* @param link Join link for a shared session.
*/
join(link: vscode.Uri, options?: JoinOptions): Promise<void>;
/**
* When called as a Host, ends the current sharing session and disconnects all guests,
* (without reloading the window or extensions).
*
* When called as a Guest, disconnects from the current sharing session
* and closes the workspace (causing extensions to be reloaded).
*/
end(): Promise<void>;
/**
* Provides a named service to guests. The service is made available only
* while a Live Share session is active in the Host role.
*
* The caller must add request and/or notification handlers to the returned
* `SharedService` instance in order to receive messages from guests.
*
* A `SharedService` instance is returned even if the service is not
* currently made available because there is no hosted sharing session.
* The service will be automatically made available when a hosted sharing
* session begins.
*
* NOTE: Access to shared services may be restricted.
* If the caller is not permitted, this method returns `null`.
*/
shareService(name: string): Promise<SharedService | null>;
/**
* Stops providing a named service to guests.
*
* NOTE: Access to shared services may be restricted.
*/
unshareService(name: string): Promise<void>;
/**
* Gets a proxy for a named service provided by a Host. The service is
* available only while a Live Share session is active in the Guest role
* AND the session Host has shared the named service.
*
* The caller must add a notification handler to the returned `SharedService`
* instance in order to receive notifications from hosts. (Service proxies
* cannot receive requests, only send them.)
*
* A `SharedServiceProxy` instance is returned even if the service is not
* currently available (either because there is no active sharing session or
* because the Host has not shared the service). Listen to the event on the
* instance to be notified when the service becomes available or unavailable.
*
* NOTE: Access to shared services may be restricted.
* If the caller is not permitted, this method returns `null`.
*/
getSharedService(name: string): Promise<SharedServiceProxy | null>;
/**
* Converts a local `file:` URI to a `vsls:` URI. Only available in host role.
*/
convertLocalUriToShared(localUri: vscode.Uri): vscode.Uri;
/**
* Converts a `vsls:` URI to a local `file:` URI. Only available in host role.
*/
convertSharedUriToLocal(sharedUri: vscode.Uri): vscode.Uri;
/**
* Registers a command to be added to the Live Share contextual command palette.
*
* @param command command identifier, as declared in the calling extension's manifest
* @param isEnabled optional callback to check if the command is available
* @param thisArg optional `this` for the callback
* @returns Disposable that can be used to unregister the command, or null if the command
* could not be registered.
*
* The command must be declared in the `contributes.commands` section of the calling
* extension manifest, including extended VSLS label and detail properties, for example:
* "contributes": {
* "commands": [
* {
* "command": "myextension.mycommand",
* "title": "Live Share: Do Something",
* "vsls-label": "$(star) Do Something",
* "vsls-detail": "Do some VSLS-related command provided by this extension"
* }
* ]
* }
*
* Extensions should use this capability judiciously, to avoid cluttering the Live Share
* command palette. If contributing a group of related commands, put them in a separate
* quick-pick menu that is brought up by a single command registered here.
*
* NOTE: Ability to contribute commands to the Live Share command palette may be restricted.
* If the caller is not permitted, this method returns `null`.
*/
registerCommand(
command: string,
isEnabled?: () => boolean,
thisArg?: any): vscode.Disposable | null;
/**
* Registers a provider that can extend a Live Share tree view by providing additional items.
*
* @param viewId One of the Live Share tree view IDs. Not all Live Share tree views support
* data providers; currently only the session and session explorer views do.
* @param treeDataProvider A provider that provides additional data for the Live Share view.
* @returns Disposable that can be used to unregister the provider, or null if the provider
* could not be registered.
*
* NOTE: Ability to contribute commands to Live Share tree views may be restricted. If the
* caller is not permitted, this method returns `null`.
*/
registerTreeDataProvider<T>(
viewId: View,
treeDataProvider: vscode.TreeDataProvider<T>,
): vscode.Disposable | null;
/**
* Registers a contact service provider.
*
* @param name Name of the provider ('skype', 'teams',..)
* @param contactServiceProvider implementation of the ContactServiceProvider interface
* @returns Disposable that can be used to unregister the provider, or null if the provider
* could not be registered.
*/
registerContactServiceProvider(
name: string,
contactServiceProvider: ContactServiceProvider,
): vscode.Disposable | null;
/**
* Sends a request to share a local server in the active collaboration session.
*
* @param server Contains properties pertaining to the local server.
* @returns A registration object that will un-share the server when disposed.
*/
shareServer(server: Server): Promise<vscode.Disposable>;
/**
* Request contacts to our presence providers
* @param emails Request contacts emails
*/
getContacts(emails: string[]): Promise<ContactsCollection>;
}
export interface ShareOptions {
/**
* Suppress display of the usual notification that indicates that sharing
* started. Also suppresses copying the join link to the clipboard. When
* setting this option, the caller should take care of showing or
* communicating the link somehow.
*/
suppressNotification?: boolean;
/**
* (NOT IMPLEMENTED) Default access level for incoming guests. The host may
* override this setting on a per-guest basis.
*/
access?: Access;
}
export interface JoinOptions {
/**
* Open the joined workspace in a new window, instead of re-using the current window.
*/
newWindow?: boolean;
correlationId?: string;
}
/**
* Represents a local TCP server listening on the given port.
*/
export interface Server {
/**
* Local TCP port the server is listening on.
*/
port: number;
/**
* User-friendly name of the server.
*/
displayName?: string;
/**
* Default URL users will be redirected to when accessing the server.
*/
browseUrl?: string;
}
export enum Role {
None = 0,
Host = 1,
Guest = 2,
}
/** This is just a placeholder for a richer access control model to be added later. */
export enum Access {
None = 0,
ReadOnly = 1,
ReadWrite = 3,
Owner = 0xFF,
}
/**
* Authenticated Live Share user information.
*
* NOTE: Access to user information may be restricted.
* If the caller is not permitted, the `Peer.user` property returns 'null'.
*/
export interface UserInfo {
/**
* User display name.
*/
readonly displayName: string;
/**
* Validated email address.
*/
readonly emailAddress: string | null;
/**
* The username that the provider (e.g. GitHub) makes available.
*/
readonly userName: string | null;
/**
* User id. This is persistent ID that stays the same for the same user
* if the user re-joins the session and even between sessions for some time.
*/
readonly id: string;
}
/**
* Represents one participant in a sharing session.
*/
export interface Peer {
/** Integer that uniquely identifies a peer within the scope of a session. */
readonly peerNumber: number;
/**
* Authenticated Live Share user information.
*
* NOTE: Access to user information may be restricted.
* If the caller is not permitted, this property returns 'null'.
*/
readonly user: UserInfo | null;
/**
* Role within the session. Each session has exactly one host; the rest of
* the peers are guests.
*/
readonly role: Role;
/**
* Access level within the session. The host has full "owner" access to the
* session. Guests may have their access limited by the host.
*/
readonly access: Access;
}
/**
* Information about the current session, including user information (in the base class).
*/
export interface Session extends Peer {
/**
* Globally unique identifier for the current session, or null if there is no active session.
*/
readonly id: string | null;
}
export interface SessionChangeEvent {
readonly session: Session;
}
export interface PeersChangeEvent {
readonly added: Peer[];
readonly removed: Peer[];
}
export interface RequestHandler {
(args: any[], cancellation: vscode.CancellationToken): any | Promise<any>;
}
export interface NotifyHandler {
(args: object): void;
}
/**
* A service that is provided by the host for use by guests.
*/
export interface SharedService {
/** A shared service is available when a sharing session is active as a Host. */
readonly isServiceAvailable: boolean;
readonly onDidChangeIsServiceAvailable: vscode.Event<boolean>;
/**
* Registers a callback to be invoked when a request is sent to the service.
*
* @param name Request method name
*/
onRequest(name: string, handler: RequestHandler): void;
/**
* Registers a callback to be invoked when a notification is sent to the service.
*
* @param name Notify event name
*/
onNotify(name: string, handler: NotifyHandler): void;
/**
* Sends a notification (event) from the service. Does not wait for a response.
*
* If no sharing session is active, this method does nothing.
*
* @param name notify event name
* @param args notify event args object
*/
notify(name: string, args: object): void;
}
/**
* A proxy that allows guests to access a host-provided service.
*/
export interface SharedServiceProxy {
/**
* A shared service proxy is available when a sharing session is active as a
* Guest, and the Host has shared a service with the same name.
*/
readonly isServiceAvailable: boolean;
readonly onDidChangeIsServiceAvailable: vscode.Event<boolean>;
/**
* Registers a callback to be invoked when a notification is sent by the service.
*
* @param name notify event name
*/
onNotify(name: string, handler: NotifyHandler): void;
/**
* Sends a request (method call) to the service and waits for a response.
*
* @param name request method name
*
* @returns a promise that waits asynchronously for a response
*
* @throws SharedServiceProxyError if the service is not currently available
* (because there is no active sharing session or no peer has provided the service)
*
* @throws SharedServiceResponseError (via rejected promise) if the service's
* request handler throws an error
*/
request(name: string, args: any[], cancellation?: vscode.CancellationToken): Promise<any>;
/**
* Sends a notification (event) to the service. (Does not wait for a response.)
*
* If the service is not currently available (either because there is
* no active sharing session or because no peer has provided the service)
* then this method does nothing.
*
* @param name notify event name
* @param args notify event args object
*/
notify(name: string, args: object): void;
}
/**
* Error thrown by a proxy when a request to a shared service cannot be made
* because the service is not available or cannot be reached.
*/
export interface SharedServiceProxyError extends Error {
}
/**
* Error thrown by a proxy when a shared service's request handler threw an error.
* The remote message and remote stack are propagated back to the proxy.
*/
export interface SharedServiceResponseError extends Error {
remoteStack?: string;
}
/**
* Identifiers for Live Share tree views. These identifiers may be used by other extensions
* to extend Live Share tree views with additional nodes via the `registerTreeDataProvider()`
* API.
*/
export enum View {
Session = 'liveshare.session',
ExplorerSession = 'liveshare.session.explorer',
Contacts = 'liveshare.contacts',
Help = 'liveshare.help',
}
/**
* Identifiers for Live Share tree view items. These identifiers may be used by other
* extensions to extend Live Share tree items with additional commands using conditional
* expressions in the `view/item/context` section of their own package.json.
*/
export enum ViewItem {
// session item groups
Participants = 'participants',
Servers = 'servers',
Terminals = 'terminals',
// participants
CurrentUser = 'participants.currentuser', // (not currently shown)
Guest = 'participants.guest',
FollowedGuest = 'participants.guest.followed',
Participant = 'participants.participant',
FollowedParticipant = 'participants.participant.followed',
GuestAnonymous = 'participants.guest.anonymous',
FollowedGuestAnonymous = 'participants.guest.followed.anonymous',
GuestElevated = 'participants.guest.elevated',
FollowedGuestElevated = 'participants.guest.followed.elevated',
// servers
LocalServer = 'servers.local',
RemoteServer = 'servers.remote',
// terminals
LocalTerminalReadOnly = 'terminals.local.readonly',
LocalTerminalReadWrite = 'terminals.local.readwrite',
RemoteTerminal = 'terminals.remote',
// contacts
SuggestedContacts = 'contacts.suggested',
AvailableContacts = 'contacts.available',
ContactsProvider = 'contacts.provider',
SelfContact = 'contacts.selfContact',
Contact = 'contacts.contact',
ContactOffline = 'contacts.contact.offline',
RecentContact = 'contacts.recentContact',
RecentContactOffline = 'contacts.recentContact.offline',
NoContact = 'contacts.noContact',
RecentContacts = 'contacts.RecentContacts',
NoSuggestedContacts = 'contacts.NoSuggestedUsers',
NoRecentContacts = 'contacts.NoRecentContacts',
InvitedContact = 'contacts.invited',
// help
SessionFeedbackQuestion = 'help.sessionFeedback',
ReportAProblem = 'help.reportAProblem',
TweetUsYourFeedback = 'help.tweetUsYourFeedback',
Survey = 'help.survey',
GoodFeedback = 'help.goodFeedback',
BadFeedback = 'help.badFeedback',
DontAskAgain = 'help.dontAskAgain',
Thankyou = 'help.thankyou',
MoreInfo = 'help.moreinfo',
// Shown while session sharing / joining is in progress
Loading = 'loading',
// Other / unspecified item type
Other = 'other',
}
export interface InviteContactOptions {
/**
* This option will force the invite to only use the email channel
*/
useEmail?: boolean;
}
/**
* Represent a contact with live presence support
*/
export interface Contact {
readonly onDidChange: vscode.Event<string[]>;
readonly id: string;
readonly email: string;
readonly displayName?: string;
readonly status?: string;
readonly avatarUri?: string;
invite(options?: InviteContactOptions): Promise<boolean>;
}
/**
* Represent a collection of contacts that can be disposed at once
*/
export interface ContactsCollection {
readonly contacts: { [email: string]: Contact };
dispose(): Promise<void>;
}

View File

@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* 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 { GuestSessionManager } from './guestSessionManager';
import { HostSessionManager } from './hostSessionManager';
declare var require: any;
let vsls = require('vsls');
export async function activate(context: vscode.ExtensionContext) {
const vslsApi = await vsls.getApi();
if (!vslsApi) {
return;
}
/* tslint:disable:no-unused-expression */
new HostSessionManager(context, vslsApi);
new GuestSessionManager(context, vslsApi);
/* tslint:enable:no-unused-expression */
}
export function deactivate(): void {
}

View File

@@ -0,0 +1,215 @@
/*---------------------------------------------------------------------------------------------
* 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 constants from '../constants';
import { LiveShare, SharedService, SharedServiceProxy } from '../liveshare';
export class ConnectionProvider {
private _sharedService: SharedService;
private _sharedServiceProxy: SharedServiceProxy;
protected _onConnect: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
public readonly onConnect: vscode.Event<any> = this._onConnect.event;
protected _onDisconnect: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
public readonly onDisconnect: vscode.Event<any> = this._onDisconnect.event;
protected _onConnectionChanged: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
public readonly onConnectionChanged: vscode.Event<any> = this._onConnectionChanged.event;
private _onConnectionCompleteHandler: (connSummary: azdata.ConnectionInfoSummary) => any;
public constructor(
private _isHost: boolean,
private _vslsApi: LiveShare,
service: SharedService | SharedServiceProxy) {
if (this._isHost) {
this._sharedService = <SharedService>service;
this.registerProviderListener();
} else {
this._sharedServiceProxy = <SharedServiceProxy>service;
this.registerProvider();
}
}
public registerProviderListener(): void {
let self = this;
azdata.connection.registerConnectionEventListener({
onConnectionEvent(type: azdata.connection.ConnectionEventType, ownerUri: string, profile: azdata.IConnectionProfile) {
try {
let localUri: vscode.Uri = self._vslsApi.convertLocalUriToShared(vscode.Uri.parse(ownerUri));
ownerUri = localUri.toString();
} catch {
}
self._sharedService.notify(<string>type, {
ownerUri: ownerUri,
profile: profile
});
}
});
this._sharedService.onRequest(constants.connectRequest, (args: any) => {
return;
});
this._sharedService.onRequest(constants.disconnectRequest, (args: any) => {
return true;
});
this._sharedService.onRequest(constants.cancelConnectRequest, (args: any) => {
return true;
});
this._sharedService.onRequest(constants.changeDatabaseRequest, (args: any) => {
return true;
});
this._sharedService.onRequest(constants.listDatabasesRequest, (args: any) => {
return true;
});
this._sharedService.onRequest(constants.getConnectionStringRequest, (args: any) => {
return true;
});
this._sharedService.onRequest(constants.buildConnectionInfoRequest, (args: any) => {
return true;
});
this._sharedService.onRequest(constants.rebuildIntellisenseCacheRequest, (args: any) => {
return true;
});
}
public registerProvider(): vscode.Disposable {
const self = this;
this._sharedServiceProxy.onNotify('onConnect', (args: any) => {
this._onConnect.fire(args);
return args;
});
this._sharedServiceProxy.onNotify('onDisconnect', (args: any) => {
this._onDisconnect.fire(args);
return args;
});
this._sharedServiceProxy.onNotify('onConnectionChanged', (args: any) => {
this._onConnectionChanged.fire(args);
return args;
});
let connect = (ownerUri: string, connInfo: azdata.ConnectionInfo): Thenable<boolean> => {
if (self._onConnectionCompleteHandler) {
// "test" liveshare connection details to be filled out in later iteration
let connSummary: azdata.ConnectionInfoSummary = {
ownerUri: ownerUri,
connectionId: ownerUri,
messages: undefined,
errorMessage: undefined,
errorNumber: undefined,
connectionSummary: {
serverName: connInfo.options['serverName'],
databaseName: connInfo.options['databaseName'],
userName: 'liveshare'
},
serverInfo: {
serverMajorVersion: 1,
serverMinorVersion: 0,
serverReleaseVersion: 1,
engineEditionId: 1,
serverVersion: '1.0',
serverLevel: '1',
serverEdition: '1',
isCloud: false,
azureVersion: 1,
osVersion: '1',
options: connInfo.options
}
};
self._onConnectionCompleteHandler(connSummary);
}
return self._sharedServiceProxy.request(constants.connectRequest, [{
ownerUri: ownerUri,
connInfo: connInfo
}]);
};
let disconnect = (ownerUri: string): Thenable<boolean> => {
return self._sharedServiceProxy.request(constants.disconnectRequest, [{
ownerUri: ownerUri
}]);
};
let cancelConnect = (ownerUri: string): Thenable<boolean> => {
return self._sharedServiceProxy.request(constants.cancelConnectRequest, [{
ownerUri: ownerUri
}]);
};
let changeDatabase = (ownerUri: string, newDatabase: string): Thenable<boolean> => {
return self._sharedServiceProxy.request(constants.changeDatabaseRequest, [{
ownerUri: ownerUri,
newDatabase: newDatabase
}]);
};
let listDatabases = (ownerUri: string): Thenable<azdata.ListDatabasesResult> => {
return self._sharedServiceProxy.request(constants.listDatabasesRequest, [{
ownerUri: ownerUri
}]);
};
let getConnectionString = (ownerUri: string, includePassword: boolean): Thenable<string> => {
return self._sharedServiceProxy.request(constants.getConnectionStringRequest, [{
ownerUri: ownerUri,
includePassword: includePassword
}]);
};
let buildConnectionInfo = (connectionString: string): Thenable<azdata.ConnectionInfo> => {
return self._sharedServiceProxy.request(constants.buildConnectionInfoRequest, [{
connectionString: connectionString
}]);
};
let rebuildIntelliSenseCache = (ownerUri: string): Thenable<void> => {
return self._sharedServiceProxy.request(constants.rebuildIntellisenseCacheRequest, [{
ownerUri: ownerUri
}]);
};
let registerOnConnectionComplete = (handler: (connSummary: azdata.ConnectionInfoSummary) => any): void => {
self._onConnectionCompleteHandler = handler;
return;
};
let registerOnIntelliSenseCacheComplete = (handler: (connectionUri: string) => any): void => {
return;
};
let registerOnConnectionChanged = (handler: (changedConnInfo: azdata.ChangedConnectionInfo) => any): void => {
return;
};
return azdata.dataprotocol.registerConnectionProvider({
providerId: constants.LiveShareProviderId,
connect,
disconnect,
cancelConnect,
changeDatabase,
listDatabases,
getConnectionString,
buildConnectionInfo,
rebuildIntelliSenseCache,
registerOnConnectionChanged,
registerOnIntelliSenseCacheComplete,
registerOnConnectionComplete
});
}
}

View File

@@ -0,0 +1,233 @@
/*---------------------------------------------------------------------------------------------
* 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 constants from '../constants';
import { SharedService, SharedServiceProxy } from '../liveshare';
export class QueryProvider {
private _sharedService: SharedService;
private _sharedServiceProxy: SharedServiceProxy;
private _onQueryCompleteHandler: (result: azdata.QueryExecuteCompleteNotificationResult) => any;
public constructor(private _isHost: boolean) { }
public initialize(isHost: boolean, service: SharedService | SharedServiceProxy) {
if (this._isHost) {
this._sharedService = <SharedService>service;
this.registerProviderListener();
} else {
this._sharedServiceProxy = <SharedServiceProxy>service;
this.registerProvider();
}
}
public registerProviderListener() {
this._sharedService.onRequest(constants.cancelQueryRequest, (args: any) => {
return;
});
this._sharedService.onRequest(constants.runQueryRequest, (args: any) => {
return true;
});
this._sharedService.onRequest(constants.runQueryStatementRequest, (args: any) => {
return true;
});
this._sharedService.onRequest(constants.runQueryStringRequest, (args: any) => {
return true;
});
this._sharedService.onRequest(constants.runQueryAndReturnRequest, (args: any) => {
return true;
});
this._sharedService.onRequest(constants.parseSyntaxRequest, (args: any) => {
return true;
});
this._sharedService.onRequest(constants.getQueryRowsRequest, (args: any) => {
return true;
});
this._sharedService.onRequest(constants.disposeQueryRequest, (args: any) => {
return true;
});
this._sharedService.onRequest(constants.saveResultsRequest, (args: any) => {
return true;
});
this._sharedService.onRequest(constants.setQueryExecutionOptionsRequest, (args: any) => {
return true;
});
}
public registerProvider(): vscode.Disposable {
const self = this;
let runQuery = (ownerUri: string, querySelection: azdata.ISelectionData, executionPlanOptions?: azdata.ExecutionPlanOptions): Thenable<void> => {
if (self._onQueryCompleteHandler) {
self._onQueryCompleteHandler({
ownerUri: ownerUri,
batchSummaries: []
});
}
return self._sharedServiceProxy.request(constants.runQueryRequest, [{
ownerUri: ownerUri,
querySelection: querySelection,
executionPlanOptions: executionPlanOptions
}]);
};
let cancelQuery = (ownerUri: string): Thenable<azdata.QueryCancelResult> => {
return self._sharedServiceProxy.request(constants.cancelQueryRequest, [{
ownerUri: ownerUri
}]);
};
let runQueryStatement = (ownerUri: string, line: number, column: number): Thenable<void> => {
return self._sharedServiceProxy.request(constants.runQueryStatementRequest, [{
ownerUri: ownerUri,
line: line,
column: column
}]);
};
let runQueryString = (ownerUri: string, query: string): Thenable<void> => {
return self._sharedServiceProxy.request(constants.runQueryStringRequest, [{
ownerUri: ownerUri,
query: query
}]);
};
let runQueryAndReturn = (ownerUri: string, queryString: string): Thenable<azdata.SimpleExecuteResult> => {
return self._sharedServiceProxy.request(constants.runQueryAndReturnRequest, [{
ownerUri: ownerUri,
query: queryString
}]);
};
let parseSyntax = (ownerUri: string, query: string): Thenable<azdata.SyntaxParseResult> => {
return self._sharedServiceProxy.request(constants.parseSyntaxRequest, [{
ownerUri: ownerUri,
query: query
}]);
};
let getQueryRows = (rowData: azdata.QueryExecuteSubsetParams): Thenable<azdata.QueryExecuteSubsetResult> => {
return self._sharedServiceProxy.request(constants.getQueryRowsRequest, [{
rowData: rowData
}]);
};
let disposeQuery = (ownerUri: string): Thenable<void> => {
return self._sharedServiceProxy.request(constants.disposeQueryRequest, [{
ownerUri: ownerUri
}]);
};
let registerOnQueryComplete = (handler: (result: azdata.QueryExecuteCompleteNotificationResult) => any): void => {
self._onQueryCompleteHandler = handler;
};
let registerOnBatchStart = (handler: (batchInfo: azdata.QueryExecuteBatchNotificationParams) => any): void => {
};
let registerOnBatchComplete = (handler: (batchInfo: azdata.QueryExecuteBatchNotificationParams) => any): void => {
};
let registerOnResultSetAvailable = (handler: (resultSetInfo: azdata.QueryExecuteResultSetNotificationParams) => any): void => {
};
let registerOnResultSetUpdated = (handler: (resultSetInfo: azdata.QueryExecuteResultSetNotificationParams) => any): void => {
};
let registerOnMessage = (handler: (message: azdata.QueryExecuteMessageParams) => any): void => {
};
let saveResults = (requestParams: azdata.SaveResultsRequestParams): Thenable<azdata.SaveResultRequestResult> => {
return Promise.resolve(undefined);
};
let setQueryExecutionOptions = (ownerUri: string, options: azdata.QueryExecutionOptions): Thenable<void> => {
return Promise.resolve();
};
// Edit Data Requests
let commitEdit = (ownerUri: string): Thenable<void> => {
return Promise.resolve();
};
let createRow = (ownerUri: string): Thenable<azdata.EditCreateRowResult> => {
return Promise.resolve(undefined);
};
let deleteRow = (ownerUri: string, rowId: number): Thenable<void> => {
return Promise.resolve();
};
let disposeEdit = (ownerUri: string): Thenable<void> => {
return Promise.resolve();
};
let initializeEdit = (ownerUri: string, schemaName: string, objectName: string, objectType: string, LimitResults: number, queryString: string): Thenable<void> => {
return Promise.resolve();
};
let revertCell = (ownerUri: string, rowId: number, columnId: number): Thenable<azdata.EditRevertCellResult> => {
return Promise.resolve(undefined);
};
let revertRow = (ownerUri: string, rowId: number): Thenable<void> => {
return Promise.resolve();
};
let updateCell = (ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<azdata.EditUpdateCellResult> => {
return Promise.resolve(undefined);
};
let getEditRows = (rowData: azdata.EditSubsetParams): Thenable<azdata.EditSubsetResult> => {
return Promise.resolve(undefined);
};
// Edit Data Event Handlers
let registerOnEditSessionReady = (handler: (ownerUri: string, success: boolean, message: string) => any): void => {
};
return azdata.dataprotocol.registerQueryProvider({
providerId: constants.LiveShareProviderId,
cancelQuery,
commitEdit,
createRow,
deleteRow,
disposeEdit,
disposeQuery,
getEditRows,
getQueryRows,
setQueryExecutionOptions,
initializeEdit,
registerOnBatchComplete,
registerOnBatchStart,
registerOnEditSessionReady,
registerOnMessage,
registerOnQueryComplete,
registerOnResultSetAvailable,
registerOnResultSetUpdated,
revertCell,
revertRow,
runQuery,
runQueryAndReturn,
parseSyntax,
runQueryStatement,
runQueryString,
saveResults,
updateCell
}, true);
}
}

View File

@@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* 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 { LiveShare, SharedService, SharedServiceProxy } from '../liveshare';
import { ConnectionProvider } from './connectionProvider';
import { LiveShareProviderId } from '../constants';
export class LiveShareDocumentState {
public isConnected: boolean;
public serverName?: string;
public databaseName?: string;
}
export class StatusProvider {
private _sharedService: SharedService;
private _sharedServiceProxy: SharedServiceProxy;
public constructor(
private _isHost: boolean,
private _vslsApi: LiveShare,
connectionProvider: ConnectionProvider,
service: SharedService | SharedServiceProxy) {
if (this._isHost) {
this._sharedService = <SharedService>service;
this.registerStatusProvider();
} else {
this._sharedServiceProxy = <SharedServiceProxy>service;
connectionProvider.onConnect(async (args: any) => {
if (args && args.ownerUri && args.profile) {
let queryDocument = await azdata.queryeditor.getQueryDocument(args.ownerUri);
if (queryDocument) {
let connectionOptions: Map<string, any> = new Map<string, any>();
connectionOptions['providerName'] = LiveShareProviderId;
connectionOptions['serverName'] = args.profile.options['server'];
connectionOptions['databaseName'] = args.profile.options['database'];
connectionOptions['userName'] = 'liveshare';
connectionOptions['password'] = 'liveshare';
connectionOptions['authenticationType'] = 'liveshare';
connectionOptions['savePassword'] = false;
connectionOptions['saveProfile'] = false;
let profile = azdata.connection.ConnectionProfile.createFrom(connectionOptions);
queryDocument.connect(profile);
}
}
});
}
}
private registerStatusProvider(): void {
let self = this;
// Retrieves the current document state associated with the URI parameter.
// The URI will be in guest Live Share format and needs to be converted back
// to the host file path format.
this._sharedService.onRequest('getDocumentState', async (args: any[]) => {
if (args && args.length > 0) {
let ownerUri = vscode.Uri.parse(args[0].ownerUri);
let localUri: vscode.Uri = self._vslsApi.convertSharedUriToLocal(ownerUri);
let connection = await azdata.connection.getConnection(localUri.toString());
let serverName: string = 'liveshare';
let databaseName: string = 'liveshare';
if (connection) {
serverName = connection.serverName;
databaseName = connection.databaseName;
}
let documentState: LiveShareDocumentState = {
isConnected: true,
serverName: serverName,
databaseName: databaseName
};
return documentState;
}
return undefined;
});
}
public getDocumentState(doc: vscode.TextDocument): Promise<LiveShareDocumentState> {
if (!this._isHost) {
return this._sharedServiceProxy.request('getDocumentState', [{
ownerUri: doc.uri.toString()
}]);
} else {
return Promise.resolve(undefined);
}
}
}

View File

@@ -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.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/sql/azdata.d.ts'/>
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
/// <reference path='../../../../src/vs/vscode.d.ts'/>

View File

@@ -0,0 +1,12 @@
{
"extends": "../shared.tsconfig.json",
"compilerOptions": {
"outDir": "./out",
"strict": false,
"noUnusedParameters": false,
"noImplicitAny": false
},
"include": [
"src/**/*"
]
}

File diff suppressed because it is too large Load Diff