mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Data Explorer Sourcing (#4033)
* added initial data explorer viewlet * added dataexplorer contribution point * removed test view * remove unused imports * inital data source, needs work * add shim for ext host * formatting * making the necessary changes to use OE for tree view; need to look at TreeUpdateUtils.connectAndCreateOeSession * formatting * shimming oe more * update to add correct context * working cross provider; need to fix connection * connection works but it adds the connection to the oe for some reason * formatting * add custom connection dialog code path * hashing between trees * added flag and tests * add id maps to handle multiple nodepaths * add necessary car edit parts * keep current behavior in prodc * fix tests * address comments * update comments to be more informative * finish merge * update comments * fix whitespace
This commit is contained in:
@@ -12,11 +12,13 @@ import { IExtensionPoint, ExtensionsRegistry, ExtensionMessageCollector } from '
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { CustomTreeViewPanel, CustomTreeViewer } from 'vs/workbench/browser/parts/views/customView';
|
||||
import { CustomTreeViewPanel } from 'vs/workbench/browser/parts/views/customView';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { viewsContainersExtensionPoint } from 'vs/workbench/api/browser/viewsContainersExtensionPoint';
|
||||
|
||||
import { CustomTreeViewer } from 'sql/workbench/browser/parts/views/customView';
|
||||
|
||||
export const DataExplorerViewlet = {
|
||||
DataExplorer: 'dataExplorer'
|
||||
};
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ITreeItem } from 'sql/workbench/common/views';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
|
||||
import { IConnectionManagementService, ConnectionType } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
|
||||
import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService';
|
||||
|
||||
import { IConnectionProfile } from 'sqlops';
|
||||
|
||||
import { TreeItemCollapsibleState } from 'vs/workbench/common/views';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
export const SERVICE_ID = 'oeShimService';
|
||||
export const IOEShimService = createDecorator<IOEShimService>(SERVICE_ID);
|
||||
|
||||
export interface IOEShimService {
|
||||
_serviceBrand: any;
|
||||
getChildren(node: ITreeItem, identifier: any): TPromise<ITreeItem[]>;
|
||||
providerExists(providerId: string): boolean;
|
||||
}
|
||||
|
||||
export class OEShimService implements IOEShimService {
|
||||
_serviceBrand: any;
|
||||
|
||||
// maps a datasource to a provider handle to a session
|
||||
private sessionMap = new Map<any, Map<number, string>>();
|
||||
private nodeIdMap = new Map<string, string>();
|
||||
|
||||
constructor(
|
||||
@IObjectExplorerService private oe: IObjectExplorerService,
|
||||
@IConnectionManagementService private cm: IConnectionManagementService,
|
||||
@IConnectionDialogService private cd: IConnectionDialogService,
|
||||
@ICapabilitiesService private capabilities: ICapabilitiesService
|
||||
) {
|
||||
}
|
||||
|
||||
private async createSession(providerId: string, node: ITreeItem): TPromise<string> {
|
||||
let deferred = new Deferred<string>();
|
||||
let connProfile = new ConnectionProfile(this.capabilities, node.payload);
|
||||
connProfile.saveProfile = false;
|
||||
if (this.cm.providerRegistered(providerId)) {
|
||||
connProfile = new ConnectionProfile(this.capabilities, await this.cd.openDialogAndWait(this.cm, { connectionType: ConnectionType.default, showDashboard: false }, connProfile, undefined, false));
|
||||
}
|
||||
let sessionResp = await this.oe.createNewSession(providerId, connProfile);
|
||||
let disp = this.oe.onUpdateObjectExplorerNodes(e => {
|
||||
if (e.connection.id === connProfile.id) {
|
||||
let rootNode = this.oe.getSession(sessionResp.sessionId).rootNode;
|
||||
// this is how we know it was shimmed
|
||||
if (rootNode.nodePath) {
|
||||
node.handle = this.oe.getSession(sessionResp.sessionId).rootNode.nodePath;
|
||||
}
|
||||
}
|
||||
disp.dispose();
|
||||
deferred.resolve(sessionResp.sessionId);
|
||||
});
|
||||
return TPromise.wrap(deferred.promise);
|
||||
}
|
||||
|
||||
public async getChildren(node: ITreeItem, identifier: any): TPromise<ITreeItem[]> {
|
||||
try {
|
||||
if (!this.sessionMap.has(identifier)) {
|
||||
this.sessionMap.set(identifier, new Map<number, string>());
|
||||
}
|
||||
if (!this.sessionMap.get(identifier).has(hash(node.payload || node.childProvider))) {
|
||||
this.sessionMap.get(identifier).set(hash(node.payload || node.childProvider), await this.createSession(node.childProvider, node));
|
||||
}
|
||||
if (this.nodeIdMap.has(node.handle)) {
|
||||
node.handle = this.nodeIdMap.get(node.handle);
|
||||
}
|
||||
let sessionId = this.sessionMap.get(identifier).get(hash(node.payload || node.childProvider));
|
||||
let treeNode = new TreeNode(undefined, undefined, undefined, node.handle, undefined, undefined, undefined, undefined, undefined, undefined);
|
||||
let profile: IConnectionProfile = node.payload || {
|
||||
providerName: node.childProvider,
|
||||
authenticationType: undefined,
|
||||
azureTenantId: undefined,
|
||||
connectionName: undefined,
|
||||
databaseName: undefined,
|
||||
groupFullName: undefined,
|
||||
groupId: undefined,
|
||||
id: undefined,
|
||||
options: undefined,
|
||||
password: undefined,
|
||||
savePassword: undefined,
|
||||
saveProfile: undefined,
|
||||
serverName: undefined,
|
||||
userName: undefined,
|
||||
};
|
||||
treeNode.connection = new ConnectionProfile(this.capabilities, profile);
|
||||
return TPromise.wrap(this.oe.resolveTreeNodeChildren({
|
||||
success: undefined,
|
||||
sessionId,
|
||||
rootNode: undefined,
|
||||
errorMessage: undefined
|
||||
}, treeNode).then(e => e.map(n => this.mapNodeToITreeItem(n, node))));
|
||||
} catch (e) {
|
||||
return TPromise.as([]);
|
||||
}
|
||||
}
|
||||
|
||||
private mapNodeToITreeItem(node: TreeNode, parentNode: ITreeItem): ITreeItem {
|
||||
let icon: string;
|
||||
let iconDark: string;
|
||||
if (equalsIgnoreCase(parentNode.childProvider, 'mssql')) {
|
||||
if (node.iconType) {
|
||||
icon = (typeof node.iconType === 'string') ? node.iconType : node.iconType.id;
|
||||
} else {
|
||||
icon = node.nodeTypeId;
|
||||
if (node.nodeStatus) {
|
||||
icon = node.nodeTypeId + '_' + node.nodeStatus;
|
||||
}
|
||||
if (node.nodeSubType) {
|
||||
icon = node.nodeTypeId + '_' + node.nodeSubType;
|
||||
}
|
||||
}
|
||||
icon = icon.toLowerCase();
|
||||
iconDark = icon;
|
||||
} else {
|
||||
icon = node.iconType as string;
|
||||
// this is just because we need to have some mapping
|
||||
iconDark = node.nodeSubType;
|
||||
}
|
||||
let handle = generateUuid();
|
||||
this.nodeIdMap.set(handle, node.nodePath);
|
||||
return {
|
||||
parentHandle: node.parent.id,
|
||||
handle,
|
||||
collapsibleState: node.isAlwaysLeaf ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed,
|
||||
label: node.label,
|
||||
icon,
|
||||
iconDark,
|
||||
childProvider: node.childProvider || parentNode.childProvider,
|
||||
providerHandle: parentNode.childProvider,
|
||||
payload: node.payload || parentNode.payload,
|
||||
contextValue: node.nodeTypeId
|
||||
};
|
||||
}
|
||||
|
||||
public providerExists(providerId: string): boolean {
|
||||
return this.oe.providerRegistered(providerId);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,14 @@ export interface ObjectExplorerCallbacks {
|
||||
}
|
||||
|
||||
export class TreeNode {
|
||||
/**
|
||||
* Informs who provides the children to a node, used by data explorer tree view api
|
||||
*/
|
||||
public childProvider: string;
|
||||
/**
|
||||
* Holds the connection profile for nodes, used by data explorer tree view api
|
||||
*/
|
||||
public payload: any;
|
||||
/**
|
||||
* id for TreeNode
|
||||
*/
|
||||
|
||||
@@ -273,6 +273,7 @@ export interface IConnectionManagementService {
|
||||
*/
|
||||
buildConnectionInfo(connectionString: string, provider?: string): Thenable<sqlops.ConnectionInfo>;
|
||||
|
||||
providerRegistered(providerId: string): boolean;
|
||||
/**
|
||||
* Get connection profile by id
|
||||
*/
|
||||
|
||||
@@ -143,6 +143,10 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
this.onDisconnect(() => this.refreshEditorTitles());
|
||||
}
|
||||
|
||||
public providerRegistered(providerId: string): boolean {
|
||||
return !!this._providers.get(providerId);
|
||||
}
|
||||
|
||||
// Event Emitters
|
||||
public get onAddConnectionProfile(): Event<IConnectionProfile> {
|
||||
return this._onAddConnectionProfile.event;
|
||||
|
||||
@@ -18,9 +18,6 @@ import { ConnectionProfileGroup, IConnectionProfileGroup } from './connectionPro
|
||||
import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService';
|
||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
const MAX_CONNECTIONS_DEFAULT = 25;
|
||||
|
||||
|
||||
21
src/sql/sqlops.d.ts
vendored
21
src/sql/sqlops.d.ts
vendored
@@ -234,20 +234,20 @@ declare module 'sqlops' {
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the actions that could happen after connecting is complete
|
||||
*/
|
||||
* Options for the actions that could happen after connecting is complete
|
||||
*/
|
||||
export interface IConnectionCompletionOptions {
|
||||
/**
|
||||
* Save the connection to MRU and settings (only save to setting if profile.saveProfile is set to true)
|
||||
* Default is true.
|
||||
*/
|
||||
*/
|
||||
saveConnection: boolean;
|
||||
|
||||
/**
|
||||
* If true, open the dashboard after connection is complete.
|
||||
* If undefined / false, dashboard won't be opened after connection completes.
|
||||
* Default is false.
|
||||
*/
|
||||
*/
|
||||
showDashboard?: boolean;
|
||||
|
||||
/**
|
||||
@@ -1032,6 +1032,14 @@ declare module 'sqlops' {
|
||||
* will be used instead.
|
||||
*/
|
||||
iconType?: string | SqlThemeIcon;
|
||||
/**
|
||||
* Informs who provides the children to a node, used by data explorer tree view api
|
||||
*/
|
||||
childProvider?: string;
|
||||
/**
|
||||
* Holds the connection profile for nodes, used by data explorer tree view api
|
||||
*/
|
||||
payload?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2367,6 +2375,11 @@ declare module 'sqlops' {
|
||||
serverInfo: ServerInfo;
|
||||
}
|
||||
|
||||
export class TreeItem extends vscode.TreeItem {
|
||||
payload?: IConnectionProfile;
|
||||
childProvider?: string;
|
||||
}
|
||||
|
||||
export namespace tasks {
|
||||
|
||||
export interface ITaskHandler {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { nb } from 'sqlops';
|
||||
import { TreeItem } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { nb, IConnectionProfile } from 'sqlops';
|
||||
import * as vsExtTypes from 'vs/workbench/api/node/extHostTypes';
|
||||
|
||||
// SQL added extension host types
|
||||
export enum ServiceOptionType {
|
||||
@@ -315,7 +315,7 @@ export interface ToolbarLayout {
|
||||
orientation: Orientation;
|
||||
}
|
||||
|
||||
export class TreeComponentItem extends TreeItem {
|
||||
export class TreeComponentItem extends vsExtTypes.TreeItem {
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
@@ -324,6 +324,11 @@ export enum AzureResource {
|
||||
Sql = 1
|
||||
}
|
||||
|
||||
export class TreeItem extends vsExtTypes.TreeItem {
|
||||
payload: IConnectionProfile;
|
||||
providerHandle: string;
|
||||
}
|
||||
|
||||
export interface ServerInfoOption {
|
||||
isBigDataCluster: boolean;
|
||||
clusterEndpoints: ClusterEndpoint;
|
||||
|
||||
@@ -547,6 +547,7 @@ export function createApiFactory(
|
||||
nb: nb,
|
||||
AzureResource: sqlExtHostTypes.AzureResource,
|
||||
extensions: extensions,
|
||||
TreeItem: sqlExtHostTypes.TreeItem,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
583
src/sql/workbench/browser/parts/views/customView.ts
Normal file
583
src/sql/workbench/browser/parts/views/customView.ts
Normal file
@@ -0,0 +1,583 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
|
||||
import { ViewContainer, TreeItemCollapsibleState, ITreeViewer, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItem as vsITreeItem } from 'vs/workbench/common/views';
|
||||
import { IProgressService2 } from 'vs/workbench/services/progress/common/progress';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IAction, ActionRunner } from 'vs/base/common/actions';
|
||||
import { MenuItemAction, IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ContextAwareMenuItemActionItem, fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IActionItemProvider, ActionBar, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { LIGHT, FileThemeIcon, FolderThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { basename } from 'vs/base/common/paths';
|
||||
import { ResourceLabel } from 'vs/workbench/browser/labels';
|
||||
import { FileKind } from 'vs/platform/files/common/files';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
import { equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
|
||||
import { IOEShimService } from 'sql/parts/objectExplorer/common/objectExplorerViewTreeShim';
|
||||
import { ITreeItem } from 'sql/workbench/common/views';
|
||||
|
||||
class Root implements ITreeItem {
|
||||
constructor(public readonly childProvider: string) { }
|
||||
label = 'root';
|
||||
handle = '0';
|
||||
parentHandle = null;
|
||||
collapsibleState = TreeItemCollapsibleState.Expanded;
|
||||
children = void 0;
|
||||
}
|
||||
|
||||
export class CustomTreeViewer extends Disposable implements ITreeViewer {
|
||||
|
||||
private isVisible: boolean = false;
|
||||
private activated: boolean = false;
|
||||
private _hasIconForParentNode = false;
|
||||
private _hasIconForLeafNode = false;
|
||||
|
||||
private treeContainer: HTMLElement;
|
||||
private tree: FileIconThemableWorkbenchTree;
|
||||
private root: ITreeItem;
|
||||
private elementsToRefresh: ITreeItem[] = [];
|
||||
|
||||
private _dataProvider: ITreeViewDataProvider;
|
||||
|
||||
private _onDidExpandItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
|
||||
readonly onDidExpandItem: Event<ITreeItem> = this._onDidExpandItem.event;
|
||||
|
||||
private _onDidCollapseItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
|
||||
readonly onDidCollapseItem: Event<ITreeItem> = this._onDidCollapseItem.event;
|
||||
|
||||
private _onDidChangeSelection: Emitter<ITreeItem[]> = this._register(new Emitter<ITreeItem[]>());
|
||||
readonly onDidChangeSelection: Event<ITreeItem[]> = this._onDidChangeSelection.event;
|
||||
|
||||
private _onDidChangeVisibility: Emitter<boolean> = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility.event;
|
||||
|
||||
constructor(
|
||||
private id: string,
|
||||
private container: ViewContainer,
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@IWorkbenchThemeService private themeService: IWorkbenchThemeService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@ICommandService private commandService: ICommandService,
|
||||
@IConfigurationService private configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
this.root = new Root(id);
|
||||
this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
|
||||
this._register(this.themeService.onThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('explorer.decorations')) {
|
||||
this.doRefresh([this.root]); /** soft refresh **/
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
get dataProvider(): ITreeViewDataProvider {
|
||||
return this._dataProvider;
|
||||
}
|
||||
|
||||
set dataProvider(dataProvider: ITreeViewDataProvider) {
|
||||
}
|
||||
|
||||
get hasIconForParentNode(): boolean {
|
||||
return this._hasIconForParentNode;
|
||||
}
|
||||
|
||||
get hasIconForLeafNode(): boolean {
|
||||
return this._hasIconForLeafNode;
|
||||
}
|
||||
|
||||
get visible(): boolean {
|
||||
return this.isVisible;
|
||||
}
|
||||
|
||||
setVisibility(isVisible: boolean): void {
|
||||
isVisible = !!isVisible;
|
||||
if (this.isVisible === isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isVisible = isVisible;
|
||||
if (this.isVisible) {
|
||||
this.activate();
|
||||
}
|
||||
|
||||
if (this.tree) {
|
||||
if (this.isVisible) {
|
||||
DOM.show(this.tree.getHTMLElement());
|
||||
} else {
|
||||
DOM.hide(this.tree.getHTMLElement()); // make sure the tree goes out of the tabindex world by hiding it
|
||||
}
|
||||
|
||||
if (this.isVisible) {
|
||||
this.tree.onVisible();
|
||||
} else {
|
||||
this.tree.onHidden();
|
||||
}
|
||||
|
||||
if (this.isVisible && this.elementsToRefresh.length) {
|
||||
this.doRefresh(this.elementsToRefresh);
|
||||
this.elementsToRefresh = [];
|
||||
}
|
||||
}
|
||||
|
||||
this._onDidChangeVisibility.fire(this.isVisible);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
if (this.tree) {
|
||||
// Make sure the current selected element is revealed
|
||||
const selectedElement = this.tree.getSelection()[0];
|
||||
if (selectedElement) {
|
||||
this.tree.reveal(selectedElement, 0.5).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
// Pass Focus to Viewer
|
||||
this.tree.domFocus();
|
||||
}
|
||||
}
|
||||
|
||||
show(container: HTMLElement): void {
|
||||
if (!this.tree) {
|
||||
this.createTree();
|
||||
}
|
||||
DOM.append(container, this.treeContainer);
|
||||
}
|
||||
|
||||
private createTree() {
|
||||
this.treeContainer = DOM.$('.tree-explorer-viewlet-tree-view');
|
||||
const actionItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuItemActionItem, action) : undefined;
|
||||
const menus = this.instantiationService.createInstance(TreeMenus, this.id);
|
||||
const dataSource = this.instantiationService.createInstance(TreeDataSource, this.container);
|
||||
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, menus, actionItemProvider);
|
||||
const controller = this.instantiationService.createInstance(TreeController, this.id, menus);
|
||||
this.tree = this.instantiationService.createInstance(FileIconThemableWorkbenchTree, this.treeContainer, { dataSource, renderer, controller }, {});
|
||||
this.tree.contextKeyService.createKey<boolean>(this.id, true);
|
||||
this._register(this.tree);
|
||||
this._register(this.tree.onDidChangeSelection(e => this.onSelection(e)));
|
||||
this._register(this.tree.onDidExpandItem(e => this._onDidExpandItem.fire(e.item.getElement())));
|
||||
this._register(this.tree.onDidCollapseItem(e => this._onDidCollapseItem.fire(e.item.getElement())));
|
||||
this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.selection)));
|
||||
this.tree.setInput(this.root);
|
||||
}
|
||||
|
||||
layout(size: number) {
|
||||
if (this.tree) {
|
||||
this.treeContainer.style.height = size + 'px';
|
||||
this.tree.layout(size);
|
||||
}
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
if (this.tree) {
|
||||
const parentNode = this.tree.getHTMLElement();
|
||||
const childNodes = [].slice.call(parentNode.querySelectorAll('.outline-item-label > a'));
|
||||
return DOM.getLargestChildWidth(parentNode, childNodes);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
refresh(elements?: ITreeItem[]): TPromise<void> {
|
||||
if (this.tree) {
|
||||
elements = elements || [this.root];
|
||||
for (const element of elements) {
|
||||
element.children = null; // reset children
|
||||
}
|
||||
if (this.isVisible) {
|
||||
return this.doRefresh(elements);
|
||||
} else {
|
||||
this.elementsToRefresh.push(...elements);
|
||||
}
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
reveal(item: ITreeItem, parentChain: ITreeItem[], options?: { select?: boolean, focus?: boolean }): TPromise<void> {
|
||||
if (this.tree && this.isVisible) {
|
||||
options = options ? options : { select: false, focus: false };
|
||||
const select = isUndefinedOrNull(options.select) ? false : options.select;
|
||||
const focus = isUndefinedOrNull(options.focus) ? false : options.focus;
|
||||
|
||||
const root: Root = this.tree.getInput();
|
||||
const promise = root.children ? TPromise.as(null) : this.refresh(); // Refresh if root is not populated
|
||||
return promise.then(() => {
|
||||
var result = TPromise.as(null);
|
||||
parentChain.forEach((e) => {
|
||||
result = result.then(() => this.tree.expand(e));
|
||||
});
|
||||
return result.then(() => this.tree.reveal(item))
|
||||
.then(() => {
|
||||
if (select) {
|
||||
this.tree.setSelection([item], { source: 'api' });
|
||||
}
|
||||
if (focus) {
|
||||
this.focus();
|
||||
this.tree.setFocus(item);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private activate() {
|
||||
if (!this.activated) {
|
||||
this.extensionService.activateByEvent(`onView:${this.id}`);
|
||||
this.activated = true;
|
||||
}
|
||||
}
|
||||
|
||||
private doRefresh(elements: ITreeItem[]): TPromise<void> {
|
||||
if (this.tree) {
|
||||
return TPromise.join(elements.map(e => this.tree.refresh(e))).then(() => null);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private onSelection({ payload }: any): void {
|
||||
if (payload && (!!payload.didClickOnTwistie || payload.source === 'api')) {
|
||||
return;
|
||||
}
|
||||
const selection: ITreeItem = this.tree.getSelection()[0];
|
||||
if (selection) {
|
||||
if (selection.command) {
|
||||
const originalEvent: KeyboardEvent | MouseEvent = payload && payload.originalEvent;
|
||||
const isMouseEvent = payload && payload.origin === 'mouse';
|
||||
const isDoubleClick = isMouseEvent && originalEvent && originalEvent.detail === 2;
|
||||
|
||||
if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) {
|
||||
this.commandService.executeCommand(selection.command.id, ...(selection.command.arguments || []));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TreeDataSource implements IDataSource {
|
||||
|
||||
constructor(
|
||||
private container: ViewContainer,
|
||||
@IProgressService2 private progressService: IProgressService2,
|
||||
@IOEShimService private objectExplorerService: IOEShimService
|
||||
) {
|
||||
}
|
||||
|
||||
getId(tree: ITree, node: ITreeItem): string {
|
||||
return node.handle;
|
||||
}
|
||||
|
||||
hasChildren(tree: ITree, node: ITreeItem): boolean {
|
||||
return this.objectExplorerService.providerExists(node.childProvider) && node.collapsibleState !== TreeItemCollapsibleState.None;
|
||||
}
|
||||
|
||||
getChildren(tree: ITree, node: ITreeItem): TPromise<any[]> {
|
||||
if (this.objectExplorerService.providerExists(node.childProvider)) {
|
||||
return TPromise.wrap(this.progressService.withProgress({ location: this.container }, () => {
|
||||
// this is replicating what vscode does when calling initial children
|
||||
if (node instanceof Root) {
|
||||
node = deepClone(node);
|
||||
node.handle = undefined;
|
||||
}
|
||||
// we need to pass this as a parameter mainly to maintain sessions
|
||||
// hopefully we don't need anything like this when we remove the shim
|
||||
return this.objectExplorerService.getChildren(node, this);
|
||||
}));
|
||||
}
|
||||
return TPromise.as([]);
|
||||
}
|
||||
|
||||
shouldAutoexpand(tree: ITree, node: ITreeItem): boolean {
|
||||
return node.collapsibleState === TreeItemCollapsibleState.Expanded;
|
||||
}
|
||||
|
||||
getParent(tree: ITree, node: any): TPromise<any> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
interface ITreeExplorerTemplateData {
|
||||
resourceLabel: ResourceLabel;
|
||||
icon: HTMLElement;
|
||||
actionBar: ActionBar;
|
||||
aligner: Aligner;
|
||||
}
|
||||
|
||||
class TreeRenderer implements IRenderer {
|
||||
|
||||
private static readonly ITEM_HEIGHT = 22;
|
||||
private static readonly TREE_TEMPLATE_ID = 'treeExplorer';
|
||||
private static readonly MSSQL_TREE_TEMPLATE_ID = 'mssqltreeExplorer';
|
||||
|
||||
constructor(
|
||||
private treeViewId: string,
|
||||
private menus: TreeMenus,
|
||||
private actionItemProvider: IActionItemProvider,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IWorkbenchThemeService private themeService: IWorkbenchThemeService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
) {
|
||||
}
|
||||
|
||||
getHeight(tree: ITree, element: any): number {
|
||||
return TreeRenderer.ITEM_HEIGHT;
|
||||
}
|
||||
|
||||
getTemplateId(tree: ITree, element: ITreeItem): string {
|
||||
return equalsIgnoreCase(element.providerHandle, 'mssql') ? TreeRenderer.MSSQL_TREE_TEMPLATE_ID : TreeRenderer.TREE_TEMPLATE_ID;
|
||||
}
|
||||
|
||||
renderTemplate(tree: ITree, templateId: string, container: HTMLElement): ITreeExplorerTemplateData {
|
||||
DOM.addClass(container, 'custom-view-tree-node-item');
|
||||
|
||||
const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon'));
|
||||
const resourceLabel = this.instantiationService.createInstance(ResourceLabel, container, {});
|
||||
DOM.addClass(resourceLabel.element, 'custom-view-tree-node-item-resourceLabel');
|
||||
const actionsContainer = DOM.append(resourceLabel.element, DOM.$('.actions'));
|
||||
const actionBar = new ActionBar(actionsContainer, {
|
||||
actionItemProvider: this.actionItemProvider,
|
||||
actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection())
|
||||
});
|
||||
|
||||
return { resourceLabel, icon, actionBar, aligner: new Aligner(container, tree, this.themeService) };
|
||||
}
|
||||
|
||||
renderElement(tree: ITree, node: ITreeItem, templateId: string, templateData: ITreeExplorerTemplateData): void {
|
||||
const resource = node.resourceUri ? URI.revive(node.resourceUri) : null;
|
||||
const label = node.label ? node.label : resource ? basename(resource.path) : '';
|
||||
let icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark;
|
||||
const title = node.tooltip ? node.tooltip : resource ? void 0 : label;
|
||||
|
||||
// reset
|
||||
templateData.resourceLabel.clear();
|
||||
templateData.actionBar.clear();
|
||||
|
||||
if ((resource || node.themeIcon) && !icon) {
|
||||
const fileDecorations = this.configurationService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations');
|
||||
templateData.resourceLabel.setLabel({ name: label, resource: resource ? resource : URI.parse('_icon_resource') }, { fileKind: this.getFileKind(node), title, fileDecorations: fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'] });
|
||||
} else {
|
||||
templateData.resourceLabel.setLabel({ name: label }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'] });
|
||||
}
|
||||
|
||||
if (templateId === TreeRenderer.TREE_TEMPLATE_ID) {
|
||||
templateData.icon.style.backgroundImage = icon ? `url('${icon}')` : '';
|
||||
} else {
|
||||
DOM.addClass(templateData.icon, 'icon');
|
||||
DOM.addClass(templateData.icon, icon);
|
||||
}
|
||||
DOM.toggleClass(templateData.icon, 'custom-view-tree-node-item-icon', !!icon);
|
||||
templateData.actionBar.context = (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle });
|
||||
templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });
|
||||
|
||||
templateData.aligner.treeItem = node;
|
||||
}
|
||||
|
||||
private getFileKind(node: ITreeItem): FileKind {
|
||||
if (node.themeIcon) {
|
||||
switch (node.themeIcon.id) {
|
||||
case FileThemeIcon.id:
|
||||
return FileKind.FILE;
|
||||
case FolderThemeIcon.id:
|
||||
return FileKind.FOLDER;
|
||||
}
|
||||
}
|
||||
return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE;
|
||||
}
|
||||
|
||||
disposeTemplate(tree: ITree, templateId: string, templateData: ITreeExplorerTemplateData): void {
|
||||
templateData.resourceLabel.dispose();
|
||||
templateData.actionBar.dispose();
|
||||
templateData.aligner.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class Aligner extends Disposable {
|
||||
|
||||
private _treeItem: ITreeItem;
|
||||
|
||||
constructor(
|
||||
private container: HTMLElement,
|
||||
private tree: ITree,
|
||||
private themeService: IWorkbenchThemeService
|
||||
) {
|
||||
super();
|
||||
this._register(this.themeService.onDidFileIconThemeChange(() => this.render()));
|
||||
}
|
||||
|
||||
set treeItem(treeItem: ITreeItem) {
|
||||
this._treeItem = treeItem;
|
||||
this.render();
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
if (this._treeItem) {
|
||||
DOM.toggleClass(this.container, 'align-icon-with-twisty', this.hasToAlignIconWithTwisty());
|
||||
}
|
||||
}
|
||||
|
||||
private hasToAlignIconWithTwisty(): boolean {
|
||||
if (this._treeItem.collapsibleState !== TreeItemCollapsibleState.None) {
|
||||
return false;
|
||||
}
|
||||
if (!this.hasIcon(this._treeItem)) {
|
||||
return false;
|
||||
|
||||
}
|
||||
const parent: ITreeItem = this.tree.getNavigator(this._treeItem).parent() || this.tree.getInput();
|
||||
if (this.hasIcon(parent)) {
|
||||
return false;
|
||||
}
|
||||
return parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c));
|
||||
}
|
||||
|
||||
private hasIcon(node: vsITreeItem): boolean {
|
||||
const icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark;
|
||||
if (icon) {
|
||||
return true;
|
||||
}
|
||||
if (node.resourceUri || node.themeIcon) {
|
||||
const fileIconTheme = this.themeService.getFileIconTheme();
|
||||
const isFolder = node.themeIcon ? node.themeIcon.id === FolderThemeIcon.id : node.collapsibleState !== TreeItemCollapsibleState.None;
|
||||
if (isFolder) {
|
||||
return fileIconTheme.hasFileIcons && fileIconTheme.hasFolderIcons;
|
||||
}
|
||||
return fileIconTheme.hasFileIcons;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class TreeController extends WorkbenchTreeController {
|
||||
|
||||
constructor(
|
||||
private treeViewId: string,
|
||||
private menus: TreeMenus,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super({}, configurationService);
|
||||
}
|
||||
|
||||
protected shouldToggleExpansion(element: ITreeItem, event: IMouseEvent, origin: string): boolean {
|
||||
return element.command ? this.isClickOnTwistie(event) : super.shouldToggleExpansion(element, event, origin);
|
||||
}
|
||||
|
||||
onContextMenu(tree: ITree, node: ITreeItem, event: ContextMenuEvent): boolean {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
tree.setFocus(node);
|
||||
const actions = this.menus.getResourceContextActions(node);
|
||||
if (!actions.length) {
|
||||
return true;
|
||||
}
|
||||
const anchor = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
|
||||
getActions: () => {
|
||||
return TPromise.as(actions);
|
||||
},
|
||||
|
||||
getActionItem: (action) => {
|
||||
const keybinding = this._keybindingService.lookupKeybinding(action.id);
|
||||
if (keybinding) {
|
||||
return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel() });
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
onHide: (wasCancelled?: boolean) => {
|
||||
if (wasCancelled) {
|
||||
tree.domFocus();
|
||||
}
|
||||
},
|
||||
|
||||
getActionsContext: () => (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle }),
|
||||
|
||||
actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection())
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class MultipleSelectionActionRunner extends ActionRunner {
|
||||
|
||||
constructor(private getSelectedResources: () => any[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
runAction(action: IAction, context: any): TPromise<any> {
|
||||
if (action instanceof MenuItemAction) {
|
||||
const selection = this.getSelectedResources();
|
||||
const filteredSelection = selection.filter(s => s !== context);
|
||||
|
||||
if (selection.length === filteredSelection.length || selection.length === 1) {
|
||||
return action.run(context);
|
||||
}
|
||||
|
||||
return action.run(context, ...filteredSelection);
|
||||
}
|
||||
|
||||
return super.runAction(action, context);
|
||||
}
|
||||
}
|
||||
|
||||
class TreeMenus extends Disposable implements IDisposable {
|
||||
|
||||
constructor(
|
||||
private id: string,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IMenuService private menuService: IMenuService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getResourceActions(element: ITreeItem): IAction[] {
|
||||
return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).primary;
|
||||
}
|
||||
|
||||
getResourceContextActions(element: ITreeItem): IAction[] {
|
||||
return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).secondary;
|
||||
}
|
||||
|
||||
private getActions(menuId: MenuId, context: { key: string, value: string }): { primary: IAction[]; secondary: IAction[]; } {
|
||||
const contextKeyService = this.contextKeyService.createScoped();
|
||||
contextKeyService.createKey('view', this.id);
|
||||
contextKeyService.createKey(context.key, context.value);
|
||||
|
||||
const menu = this.menuService.createMenu(menuId, contextKeyService);
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
const result = { primary, secondary };
|
||||
fillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g));
|
||||
|
||||
menu.dispose();
|
||||
contextKeyService.dispose();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITreeViewDataProvider, ITreeItem } from 'vs/workbench/common/views';
|
||||
import { ITreeViewDataProvider, ITreeItem as vsITreeItem } from 'vs/workbench/common/views';
|
||||
import { IConnectionProfile } from 'sqlops';
|
||||
|
||||
export interface ITreeComponentItem extends ITreeItem {
|
||||
export interface ITreeComponentItem extends vsITreeItem {
|
||||
checked?: boolean;
|
||||
enabled?: boolean;
|
||||
onCheckedChanged?: (checked: boolean) => void;
|
||||
@@ -15,3 +16,9 @@ export interface ITreeComponentItem extends ITreeItem {
|
||||
export interface IModelViewTreeViewDataProvider extends ITreeViewDataProvider {
|
||||
refresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeComponentItem });
|
||||
}
|
||||
|
||||
export interface ITreeItem extends vsITreeItem {
|
||||
providerHandle?: string;
|
||||
childProvider?: string;
|
||||
payload?: IConnectionProfile; // its possible we will want this to be more generic
|
||||
}
|
||||
|
||||
@@ -76,6 +76,10 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
private _connectionErrorTitle = localize('connectionError', 'Connection error');
|
||||
private _dialogDeferredPromise: Deferred<IConnectionProfile>;
|
||||
|
||||
/**
|
||||
* This is used to work around the interconnectedness of this code
|
||||
*/
|
||||
private ignoreNextConnect = false;
|
||||
private _connectionManagementService: IConnectionManagementService;
|
||||
|
||||
constructor(
|
||||
@@ -87,6 +91,7 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
@IClipboardService private _clipboardService: IClipboardService,
|
||||
@ICommandService private _commandService: ICommandService
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Gets the default provider with the following actions
|
||||
* 1. Checks if master provider(map) has data
|
||||
@@ -161,6 +166,13 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
}
|
||||
|
||||
private handleOnCancel(params: INewConnectionParams): void {
|
||||
if (this.ignoreNextConnect) {
|
||||
this._connectionDialog.resetConnection();
|
||||
this._connectionDialog.close();
|
||||
this.ignoreNextConnect = false;
|
||||
this._dialogDeferredPromise.resolve(undefined);
|
||||
return;
|
||||
}
|
||||
if (this.uiController.databaseDropdownExpanded) {
|
||||
this.uiController.closeDatabaseDropdown();
|
||||
} else {
|
||||
@@ -182,6 +194,14 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
}
|
||||
|
||||
private handleDefaultOnConnect(params: INewConnectionParams, connection: IConnectionProfile): Thenable<void> {
|
||||
if (this.ignoreNextConnect) {
|
||||
this._connectionDialog.resetConnection();
|
||||
this._connectionDialog.close();
|
||||
this.ignoreNextConnect = false;
|
||||
this._connecting = false;
|
||||
this._dialogDeferredPromise.resolve(connection);
|
||||
return Promise.resolve();
|
||||
}
|
||||
let fromEditor = params && params.connectionType === ConnectionType.editor;
|
||||
let uri: string = undefined;
|
||||
if (fromEditor && params && params.input) {
|
||||
@@ -303,7 +323,12 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
connectionManagementService: IConnectionManagementService,
|
||||
params?: INewConnectionParams,
|
||||
model?: IConnectionProfile,
|
||||
connectionResult?: IConnectionResult): Thenable<IConnectionProfile> {
|
||||
connectionResult?: IConnectionResult,
|
||||
doConnect: boolean = true): Thenable<IConnectionProfile> {
|
||||
|
||||
if (!doConnect) {
|
||||
this.ignoreNextConnect = true;
|
||||
}
|
||||
this._dialogDeferredPromise = new Deferred<IConnectionProfile>();
|
||||
|
||||
this.showDialog(connectionManagementService, params,
|
||||
|
||||
@@ -28,6 +28,6 @@ export interface IConnectionDialogService {
|
||||
* @param model
|
||||
* @param connectionResult
|
||||
*/
|
||||
openDialogAndWait(connectionManagementService: IConnectionManagementService, params?: INewConnectionParams, model?: IConnectionProfile, connectionResult?: IConnectionResult): Thenable<IConnectionProfile>;
|
||||
openDialogAndWait(connectionManagementService: IConnectionManagementService, params?: INewConnectionParams, model?: IConnectionProfile, connectionResult?: IConnectionResult, doConnect?: boolean): Thenable<IConnectionProfile>;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,10 @@ export interface IObjectExplorerService {
|
||||
getNodeActions(connectionId: string, nodePath: string): Thenable<string[]>;
|
||||
|
||||
getSessionConnectionProfile(sessionId: string): sqlops.IConnectionProfile;
|
||||
|
||||
getSession(sessionId: string): sqlops.ObjectExplorerSession;
|
||||
|
||||
providerRegistered(providerId: string): boolean;
|
||||
}
|
||||
|
||||
interface SessionStatus {
|
||||
@@ -152,6 +156,19 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
this._onSelectionOrFocusChange = new Emitter<void>();
|
||||
}
|
||||
|
||||
public getSession(sessionId: string): sqlops.ObjectExplorerSession {
|
||||
let session = this._sessions[sessionId];
|
||||
if (!session) {
|
||||
return undefined;
|
||||
}
|
||||
let node = this._activeObjectExplorerNodes[session.connection.id];
|
||||
return node ? node.getSession() : undefined;
|
||||
}
|
||||
|
||||
public providerRegistered(providerId: string): boolean {
|
||||
return !!this._providers[providerId];
|
||||
}
|
||||
|
||||
public get onUpdateObjectExplorerNodes(): Event<ObjectExplorerNodeEventArgs> {
|
||||
return this._onUpdateObjectExplorerNodes.event;
|
||||
}
|
||||
@@ -576,13 +593,16 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
}
|
||||
}
|
||||
|
||||
return new TreeNode(nodeInfo.nodeType, nodeInfo.label, isLeaf, nodeInfo.nodePath,
|
||||
let node = new TreeNode(nodeInfo.nodeType, nodeInfo.label, isLeaf, nodeInfo.nodePath,
|
||||
nodeInfo.nodeSubType, nodeInfo.nodeStatus, parent, nodeInfo.metadata, nodeInfo.iconType, {
|
||||
getChildren: treeNode => this.getChildren(treeNode),
|
||||
isExpanded: treeNode => this.isExpanded(treeNode),
|
||||
setNodeExpandedState: (treeNode, expandedState) => this.setNodeExpandedState(treeNode, expandedState),
|
||||
setNodeSelected: (treeNode, selected, clearOtherSelections: boolean = undefined) => this.setNodeSelected(treeNode, selected, clearOtherSelections)
|
||||
});
|
||||
node.childProvider = nodeInfo.childProvider;
|
||||
node.payload = nodeInfo.payload;
|
||||
return node;
|
||||
}
|
||||
|
||||
public registerServerTreeView(view: ServerTreeView): void {
|
||||
@@ -683,6 +703,9 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
|
||||
private async setNodeExpandedState(treeNode: TreeNode, expandedState: TreeItemCollapsibleState): Promise<void> {
|
||||
treeNode = await this.getUpdatedTreeNode(treeNode);
|
||||
if (!treeNode) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
let expandNode = this.getTreeItem(treeNode);
|
||||
if (expandedState === TreeItemCollapsibleState.Expanded) {
|
||||
await this._serverTreeView.reveal(expandNode);
|
||||
@@ -692,6 +715,9 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
|
||||
private async setNodeSelected(treeNode: TreeNode, selected: boolean, clearOtherSelections: boolean = undefined): Promise<void> {
|
||||
treeNode = await this.getUpdatedTreeNode(treeNode);
|
||||
if (!treeNode) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
let selectNode = this.getTreeItem(treeNode);
|
||||
if (selected) {
|
||||
await this._serverTreeView.reveal(selectNode);
|
||||
@@ -701,6 +727,9 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
|
||||
private async getChildren(treeNode: TreeNode): Promise<TreeNode[]> {
|
||||
treeNode = await this.getUpdatedTreeNode(treeNode);
|
||||
if (!treeNode) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
if (treeNode.isAlwaysLeaf) {
|
||||
return [];
|
||||
}
|
||||
@@ -712,6 +741,9 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
|
||||
private async isExpanded(treeNode: TreeNode): Promise<boolean> {
|
||||
treeNode = await this.getUpdatedTreeNode(treeNode);
|
||||
if (!treeNode) {
|
||||
return false;
|
||||
}
|
||||
do {
|
||||
let expandNode = this.getTreeItem(treeNode);
|
||||
if (!this._serverTreeView.isExpanded(expandNode)) {
|
||||
@@ -734,7 +766,8 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
private getUpdatedTreeNode(treeNode: TreeNode): Promise<TreeNode> {
|
||||
return this.getTreeNode(treeNode.getConnectionProfile().id, treeNode.nodePath).then(treeNode => {
|
||||
if (!treeNode) {
|
||||
throw new Error(nls.localize('treeNodeNoLongerExists', 'The given tree node no longer exists'));
|
||||
// throw new Error(nls.localize('treeNodeNoLongerExists', 'The given tree node no longer exists'));
|
||||
return undefined;
|
||||
}
|
||||
return treeNode;
|
||||
});
|
||||
|
||||
@@ -24,4 +24,9 @@ export class ConnectionDialogTestService implements IConnectionDialogService {
|
||||
params?: INewConnectionParams, model?: IConnectionProfile, connectionResult?: IConnectionResult): TPromise<IConnectionProfile> {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
public openDialogAndWaitButDontConnect(connectionManagementService: IConnectionManagementService,
|
||||
params?: INewConnectionParams, model?: IConnectionProfile, connectionResult?: IConnectionResult): TPromise<IConnectionProfile> {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,6 +266,10 @@ export class TestConnectionManagementService implements IConnectionManagementSer
|
||||
return undefined;
|
||||
}
|
||||
|
||||
providerRegistered(providerId: string): boolean {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getConnectionProfileById(profileId: string): IConnectionProfile {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -7,11 +7,17 @@
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeViewer, ViewsRegistry, ICustomViewDescriptor } from 'vs/workbench/common/views';
|
||||
import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeViewer, ViewsRegistry, ICustomViewDescriptor, TreeItemCollapsibleState } from 'vs/workbench/common/views';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import * as sqlops from 'sqlops';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTreeViews)
|
||||
export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape {
|
||||
|
||||
@@ -21,7 +27,9 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IViewsService private viewsService: IViewsService,
|
||||
@INotificationService private notificationService: INotificationService
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
// {{SQL CARBON EDIT}}
|
||||
@IObjectExplorerService private objectExplorerService: IObjectExplorerService
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews);
|
||||
@@ -30,6 +38,10 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
|
||||
$registerTreeViewDataProvider(treeViewId: string): void {
|
||||
const dataProvider = new TreeViewDataProvider(treeViewId, this._proxy, this.notificationService);
|
||||
this._dataProviders.set(treeViewId, dataProvider);
|
||||
// {{SQL CARBON EDIT}}
|
||||
if (this.checkForDataExplorer(treeViewId)) {
|
||||
return;
|
||||
}
|
||||
const viewer = this.getTreeViewer(treeViewId);
|
||||
if (viewer) {
|
||||
viewer.dataProvider = dataProvider;
|
||||
@@ -70,6 +82,25 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
|
||||
return viewDescriptor ? viewDescriptor.treeViewer : null;
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
private checkForDataExplorer(treeViewId: string): boolean {
|
||||
const viewDescriptor: ICustomViewDescriptor = <ICustomViewDescriptor>ViewsRegistry.getView(treeViewId);
|
||||
if (viewDescriptor.container.id === 'workbench.view.dataExplorer') {
|
||||
const dataProvider = new OETreeViewDataProvider(treeViewId, this._proxy);
|
||||
this.objectExplorerService.registerProvider(treeViewId, dataProvider);
|
||||
dataProvider.registerOnExpandCompleted(e => this.objectExplorerService.onNodeExpanded({
|
||||
errorMessage: e.errorMessage,
|
||||
nodePath: e.nodePath,
|
||||
nodes: e.nodes,
|
||||
sessionId: e.sessionId,
|
||||
providerId: treeViewId
|
||||
}));
|
||||
dataProvider.registerOnSessionCreated(e => this.objectExplorerService.onSessionCreated(undefined, e));
|
||||
viewDescriptor.treeViewer.refresh();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._dataProviders.forEach((dataProvider, treeViewId) => {
|
||||
const treeViewer = this.getTreeViewer(treeViewId);
|
||||
@@ -158,3 +189,100 @@ export class TreeViewDataProvider implements ITreeViewDataProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
export class OETreeViewDataProvider implements sqlops.ObjectExplorerProvider {
|
||||
|
||||
protected itemsMap: Map<TreeItemHandle, ITreeItem> = new Map<TreeItemHandle, ITreeItem>();
|
||||
private onExpandComplete = new Emitter<sqlops.ObjectExplorerExpandInfo>();
|
||||
private onSessionCreated = new Emitter<sqlops.ObjectExplorerSession>();
|
||||
|
||||
private sessionId: string;
|
||||
|
||||
handle: number;
|
||||
readonly providerId = this.treeViewId;
|
||||
|
||||
constructor(protected treeViewId: string,
|
||||
protected _proxy: ExtHostTreeViewsShape
|
||||
) {
|
||||
}
|
||||
|
||||
public createNewSession(connInfo: sqlops.ConnectionInfo): Thenable<sqlops.ObjectExplorerSessionResponse> {
|
||||
// no op
|
||||
this.sessionId = generateUuid();
|
||||
setTimeout(() => {
|
||||
this.onSessionCreated.fire({
|
||||
sessionId: this.sessionId,
|
||||
errorMessage: undefined,
|
||||
rootNode: {
|
||||
errorMessage: undefined,
|
||||
iconType: undefined,
|
||||
isLeaf: undefined,
|
||||
label: undefined,
|
||||
metadata: undefined,
|
||||
nodePath: undefined,
|
||||
nodeStatus: undefined,
|
||||
nodeSubType: undefined,
|
||||
nodeType: undefined
|
||||
},
|
||||
success: true
|
||||
});
|
||||
});
|
||||
return TPromise.as({ sessionId: this.sessionId });
|
||||
}
|
||||
|
||||
public expandNode(nodeInfo: sqlops.ExpandNodeInfo): Thenable<boolean> {
|
||||
this._proxy.$getChildren(this.treeViewId, nodeInfo.nodePath).then(e => {
|
||||
this.onExpandComplete.fire({
|
||||
errorMessage: undefined,
|
||||
nodePath: nodeInfo.nodePath,
|
||||
nodes: e.map(e => {
|
||||
return <sqlops.NodeInfo>{
|
||||
nodePath: e.handle,
|
||||
label: e.label,
|
||||
iconType: e.icon,
|
||||
// this is just needed since we don't have this
|
||||
nodeSubType: e.iconDark,
|
||||
isLeaf: e.collapsibleState === TreeItemCollapsibleState.None,
|
||||
childProvider: e.childProvider,
|
||||
payload: e.payload,
|
||||
nodeType: e.contextValue
|
||||
};
|
||||
}),
|
||||
sessionId: this.sessionId
|
||||
});
|
||||
});
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
public refreshNode(nodeInfo: sqlops.ExpandNodeInfo): Thenable<boolean> {
|
||||
// no op
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
public closeSession(closeSessionInfo: sqlops.ObjectExplorerCloseSessionInfo): Thenable<sqlops.ObjectExplorerCloseSessionResponse> {
|
||||
// no op
|
||||
return TPromise.as({ sessionId: undefined, success: true });
|
||||
}
|
||||
|
||||
public findNodes(findNodesInfo: sqlops.FindNodesInfo): Thenable<sqlops.ObjectExplorerFindNodesResponse> {
|
||||
// no op
|
||||
return TPromise.as({ nodes: [] });
|
||||
}
|
||||
|
||||
public registerOnSessionCreated(handler: (response: sqlops.ObjectExplorerSession) => any): void {
|
||||
// no op
|
||||
this.onSessionCreated.event(handler);
|
||||
return;
|
||||
}
|
||||
|
||||
public registerOnSessionDisconnected?(handler: (response: sqlops.ObjectExplorerSession) => any): void {
|
||||
// no op
|
||||
return;
|
||||
}
|
||||
|
||||
public registerOnExpandCompleted(handler: (response: sqlops.ObjectExplorerExpandInfo) => any): void {
|
||||
this.onExpandComplete.event(handler);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ import { IProgressOptions, IProgressStep } from 'vs/workbench/services/progress/
|
||||
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views';
|
||||
|
||||
export interface IEnvironment {
|
||||
isExtensionDevelopmentDebug: boolean;
|
||||
appRoot: string;
|
||||
@@ -668,7 +671,8 @@ export interface ExtHostDocumentsAndEditorsShape {
|
||||
}
|
||||
|
||||
export interface ExtHostTreeViewsShape {
|
||||
$getChildren(treeViewId: string, treeItemHandle?: string): TPromise<ITreeItem[]>;
|
||||
// {{SQL CARBON EDIT}}
|
||||
$getChildren(treeViewId: string, treeItemHandle?: string): TPromise<sqlITreeItem[]>;
|
||||
$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void;
|
||||
$setSelection(treeViewId: string, treeItemHandles: string[]): void;
|
||||
$setVisible(treeViewId: string, visible: boolean): void;
|
||||
|
||||
@@ -21,6 +21,8 @@ import { equals } from 'vs/base/common/arrays';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import * as sqlops from 'sqlops';
|
||||
import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views';
|
||||
export type TreeItemHandle = string;
|
||||
|
||||
export class ExtHostTreeViews implements ExtHostTreeViewsShape {
|
||||
@@ -392,7 +394,7 @@ export class ExtHostTreeView<T> extends Disposable {
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected createTreeItem(element: T, extensionTreeItem: vscode.TreeItem, parent?: TreeNode): ITreeItem {
|
||||
protected createTreeItem(element: T, extensionTreeItem: sqlops.TreeItem, parent?: TreeNode): sqlITreeItem {
|
||||
|
||||
const handle = this.createHandle(element, extensionTreeItem, parent);
|
||||
const icon = this.getLightIconPath(extensionTreeItem);
|
||||
@@ -407,7 +409,10 @@ export class ExtHostTreeView<T> extends Disposable {
|
||||
icon,
|
||||
iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
|
||||
themeIcon: extensionTreeItem.iconPath instanceof ThemeIcon ? { id: extensionTreeItem.iconPath.id } : void 0,
|
||||
collapsibleState: isUndefinedOrNull(extensionTreeItem.collapsibleState) ? TreeItemCollapsibleState.None : extensionTreeItem.collapsibleState
|
||||
collapsibleState: isUndefinedOrNull(extensionTreeItem.collapsibleState) ? TreeItemCollapsibleState.None : extensionTreeItem.collapsibleState,
|
||||
// {{SQL CARBON EDIT}}
|
||||
payload: extensionTreeItem.payload,
|
||||
childProvider: extensionTreeItem.childProvider
|
||||
};
|
||||
|
||||
return item;
|
||||
|
||||
@@ -184,6 +184,7 @@ import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'
|
||||
import { WorkbenchThemeService } from 'vs/workbench/services/themes/electron-browser/workbenchThemeService';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IUriDisplayService, UriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
|
||||
import { OEShimService, IOEShimService } from 'sql/parts/objectExplorer/common/objectExplorerViewTreeShim';
|
||||
|
||||
interface WorkbenchParams {
|
||||
configuration: IWindowConfiguration;
|
||||
@@ -575,6 +576,7 @@ export class Workbench extends Disposable implements IPartService {
|
||||
serviceCollection.set(ITaskService, this.instantiationService.createInstance(TaskService));
|
||||
serviceCollection.set(IMetadataService, this.instantiationService.createInstance(MetadataService));
|
||||
serviceCollection.set(IObjectExplorerService, this.instantiationService.createInstance(ObjectExplorerService));
|
||||
serviceCollection.set(IOEShimService, this.instantiationService.createInstance(OEShimService));
|
||||
serviceCollection.set(IScriptingService, this.instantiationService.createInstance(ScriptingService));
|
||||
serviceCollection.set(IAdminService, this.instantiationService.createInstance(AdminService));
|
||||
serviceCollection.set(IJobManagementService, this.instantiationService.createInstance(JobManagementService));
|
||||
|
||||
Reference in New Issue
Block a user