Fixes azure sql expansion (#4185)

* fixes azure sql expansion

* remove unneeded changes

* remove unused code
This commit is contained in:
Anthony Dresser
2019-02-26 15:29:02 -08:00
committed by GitHub
parent b11a8e9c0c
commit 2e9b5f3a2b
6 changed files with 371 additions and 285 deletions

View File

@@ -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, CustomTreeView } 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 { CustomTreeView } from 'sql/workbench/browser/parts/views/customView';
export const DataExplorerViewlet = {
DataExplorer: 'dataExplorer'
};
@@ -163,4 +165,4 @@ class DataExplorerContainerExtensionHandler implements IWorkbenchContribution {
}
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(DataExplorerContainerExtensionHandler, LifecyclePhase.Starting);
workbenchRegistry.registerWorkbenchContribution(DataExplorerContainerExtensionHandler, LifecyclePhase.Starting);

View File

@@ -28,7 +28,7 @@ export const IOEShimService = createDecorator<IOEShimService>(SERVICE_ID);
export interface IOEShimService {
_serviceBrand: any;
getChildren(node: ITreeItem, identifier: any): TPromise<ITreeItem[]>;
getChildren(node: ITreeItem, identifier: any): Promise<ITreeItem[]>;
providerExists(providerId: string): boolean;
}
@@ -69,7 +69,7 @@ export class OEShimService implements IOEShimService {
return TPromise.wrap(deferred.promise);
}
public async getChildren(node: ITreeItem, identifier: any): TPromise<ITreeItem[]> {
public async getChildren(node: ITreeItem, identifier: any): Promise<ITreeItem[]> {
try {
if (!this.sessionMap.has(identifier)) {
this.sessionMap.set(identifier, new Map<number, string>());
@@ -111,27 +111,6 @@ export class OEShimService implements IOEShimService {
}
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 {
@@ -141,8 +120,6 @@ export class OEShimService implements IOEShimService {
label: {
label: node.label
},
icon: URI.parse(icon),
iconDark: URI.parse(iconDark),
childProvider: node.childProvider || parentNode.childProvider,
providerHandle: parentNode.childProvider,
payload: node.payload || parentNode.payload,

View File

@@ -127,7 +127,6 @@ export class DisconnectConnectionAction extends Action {
}
}
/**
* Actions to add a server to the group
*/
@@ -402,4 +401,4 @@ export class ClearSearchAction extends Action {
this._viewlet.clearSearch();
return TPromise.as(true);
}
}
}

View File

@@ -3,65 +3,135 @@
* 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, ITreeView, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItem as vsITreeItem } from 'vs/workbench/common/views';
import { TPromise } from 'vs/base/common/winjs.base';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IAction, ActionRunner, Action } from 'vs/base/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
import { ContextAwareMenuItemActionItem, fillInActionBarActions, fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ITreeView, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ViewContainer, ITreeItemLabel } from 'vs/workbench/common/views';
import { FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { Emitter, Event } from 'vs/base/common/event';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IProgressService2 } from 'vs/platform/progress/common/progress';
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 { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
import { ResourceLabel } from 'vs/workbench/browser/labels';
import { ActionBar, IActionItemProvider, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { URI } from 'vs/base/common/uri';
import { basename } from 'vs/base/common/paths';
import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
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 { localize } from 'vs/nls';
import { timeout } from 'vs/base/common/async';
import { CollapseAllAction } from 'vs/base/parts/tree/browser/treeDefaults';
import { editorFindMatchHighlight, editorFindMatchHighlightBorder, textLinkForeground, textCodeBlockBackground, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { isString } from 'vs/base/common/types';
import { renderMarkdown, RenderOptions } from 'vs/base/browser/htmlContentRenderer';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IMarkdownRenderResult } from 'vs/editor/contrib/markdown/markdownRenderer';
import { ILabelService } from 'vs/platform/label/common/label';
import { dirname } from 'vs/base/common/resources';
import { ITreeItem } from 'sql/workbench/common/views';
import { IOEShimService } from 'sql/parts/objectExplorer/common/objectExplorerViewTreeShim';
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { IOEShimService } from 'sql/parts/objectExplorer/common/objectExplorerViewTreeShim';
import { ITreeItem } from 'sql/workbench/common/views';
import { IProgressService2 } from 'vs/platform/progress/common/progress';
class TitleMenus implements IDisposable {
private disposables: IDisposable[] = [];
private titleDisposable: IDisposable = Disposable.None;
private titleActions: IAction[] = [];
private titleSecondaryActions: IAction[] = [];
private _onDidChangeTitle = new Emitter<void>();
get onDidChangeTitle(): Event<void> { return this._onDidChangeTitle.event; }
constructor(
id: string,
@IContextKeyService private contextKeyService: IContextKeyService,
@IMenuService private menuService: IMenuService,
) {
if (this.titleDisposable) {
this.titleDisposable.dispose();
this.titleDisposable = Disposable.None;
}
const _contextKeyService = this.contextKeyService.createScoped();
_contextKeyService.createKey('view', id);
const titleMenu = this.menuService.createMenu(MenuId.ViewTitle, _contextKeyService);
const updateActions = () => {
this.titleActions = [];
this.titleSecondaryActions = [];
fillInActionBarActions(titleMenu, undefined, { primary: this.titleActions, secondary: this.titleSecondaryActions });
this._onDidChangeTitle.fire();
};
const listener = titleMenu.onDidChange(updateActions);
updateActions();
this.titleDisposable = toDisposable(() => {
listener.dispose();
titleMenu.dispose();
_contextKeyService.dispose();
this.titleActions = [];
this.titleSecondaryActions = [];
});
}
getTitleActions(): IAction[] {
return this.titleActions;
}
getTitleSecondaryActions(): IAction[] {
return this.titleSecondaryActions;
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}
class Root implements ITreeItem {
constructor(public readonly childProvider: string) { }
label = {
label: 'root'
};
label = { label: 'root' };
handle = '0';
parentHandle = null;
collapsibleState = TreeItemCollapsibleState.Expanded;
children = void 0;
}
const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data.");
export class CustomTreeView extends Disposable implements ITreeView {
private isVisible: boolean = false;
private activated: boolean = false;
private _hasIconForParentNode = false;
private _hasIconForLeafNode = false;
private _showCollapseAllAction = false;
private focused: boolean = false;
private domNode: HTMLElement;
private treeContainer: HTMLElement;
private _messageValue: string | IMarkdownString | undefined;
private messageElement: HTMLDivElement;
private tree: FileIconThemableWorkbenchTree;
private root: ITreeItem;
private elementsToRefresh: ITreeItem[] = [];
private menus: TitleMenus;
private _dataProvider: ITreeViewDataProvider;
private markdownRenderer: MarkdownRenderer;
private markdownResult: IMarkdownRenderResult;
private _onDidExpandItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
readonly onDidExpandItem: Event<ITreeItem> = this._onDidExpandItem.event;
@@ -78,9 +148,6 @@ export class CustomTreeView extends Disposable implements ITreeView {
private _onDidChangeActions: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChangeActions: Event<void> = this._onDidChangeActions.event;
public showCollapseAllAction: boolean = false;
public message = undefined;
constructor(
private id: string,
private container: ViewContainer,
@@ -88,10 +155,13 @@ export class CustomTreeView extends Disposable implements ITreeView {
@IWorkbenchThemeService private themeService: IWorkbenchThemeService,
@IInstantiationService private instantiationService: IInstantiationService,
@ICommandService private commandService: ICommandService,
@IConfigurationService private configurationService: IConfigurationService
@IConfigurationService private configurationService: IConfigurationService,
@IProgressService2 private progressService: IProgressService2
) {
super();
this.root = new Root(id);
this.root = new Root();
this.menus = this._register(instantiationService.createInstance(TitleMenus, this.id));
this._register(this.menus.onDidChangeTitle(() => this._onDidChangeActions.fire()));
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 => {
@@ -99,13 +169,50 @@ export class CustomTreeView extends Disposable implements ITreeView {
this.doRefresh([this.root]); /** soft refresh **/
}
}));
this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer);
this._register(toDisposable(() => {
if (this.markdownResult) {
this.markdownResult.dispose();
}
}));
this.create();
}
private _dataProvider: ITreeViewDataProvider;
get dataProvider(): ITreeViewDataProvider {
return this._dataProvider;
}
set dataProvider(dataProvider: ITreeViewDataProvider) {
if (dataProvider) {
this._dataProvider = new class implements ITreeViewDataProvider {
getChildren(node?: ITreeItem): Promise<ITreeItem[]> {
if (node && node.children) {
return Promise.resolve(node.children);
}
const promise = node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node);
return promise.then(children => {
node.children = children;
return children;
});
}
};
this.updateMessage();
this.refresh();
} else {
this._dataProvider = null;
this.updateMessage();
}
}
private _message: string | IMarkdownString | undefined;
get message(): string | IMarkdownString | undefined {
return this._message;
}
set message(message: string | IMarkdownString | undefined) {
this._message = message;
this.updateMessage();
}
get hasIconForParentNode(): boolean {
@@ -120,33 +227,28 @@ export class CustomTreeView extends Disposable implements ITreeView {
return this.isVisible;
}
expand(itemOrItems: ITreeItem | ITreeItem[]): Thenable<void> {
if (this.tree) {
itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
return this.tree.expandAll(itemOrItems);
}
return Promise.arguments(null);
get showCollapseAllAction(): boolean {
return this._showCollapseAllAction;
}
setSelection(items: ITreeItem[]): void {
if (this.tree) {
this.tree.setSelection(items, { source: 'api' });
}
}
setFocus(item: ITreeItem): void {
if (this.tree) {
this.focus();
this.tree.setFocus(item);
set showCollapseAllAction(showCollapseAllAction: boolean) {
if (this._showCollapseAllAction !== !!showCollapseAllAction) {
this._showCollapseAllAction = !!showCollapseAllAction;
this._onDidChangeActions.fire();
}
}
getPrimaryActions(): IAction[] {
return [];
if (this.showCollapseAllAction) {
const collapseAllAction = new Action('vs.tree.collapse', localize('collapse', "Collapse"), 'monaco-tree-action collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve());
return [...this.menus.getTitleActions(), collapseAllAction];
} else {
return this.menus.getTitleActions();
}
}
getSecondaryActions(): IAction[] {
return [];
return this.menus.getTitleSecondaryActions();
}
setVisibility(isVisible: boolean): void {
@@ -183,30 +285,37 @@ export class CustomTreeView extends Disposable implements ITreeView {
}
focus(): void {
if (this.tree) {
if (this.tree && this.root.children && this.root.children.length > 0) {
// Make sure the current selected element is revealed
const selectedElement = this.tree.getSelection()[0];
if (selectedElement) {
this.tree.reveal(selectedElement, 0.5).then(null, errors.onUnexpectedError);
this.tree.reveal(selectedElement, 0.5);
}
// Pass Focus to Viewer
this.tree.domFocus();
} else {
this.domNode.focus();
}
}
show(container: HTMLElement): void {
if (!this.tree) {
this.createTree();
}
DOM.append(container, this.treeContainer);
DOM.append(container, this.domNode);
}
private create() {
this.domNode = DOM.$('.tree-explorer-viewlet-tree-view');
this.messageElement = DOM.append(this.domNode, DOM.$('.message'));
this.treeContainer = DOM.append(this.domNode, DOM.$('.customview-tree'));
const focusTracker = this._register(DOM.trackFocus(this.domNode));
this._register(focusTracker.onDidFocus(() => this.focused = true));
this._register(focusTracker.onDidBlur(() => this.focused = false));
}
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 dataSource = this.instantiationService.createInstance(TreeDataSource, this, 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 }, {});
@@ -216,27 +325,72 @@ export class CustomTreeView extends Disposable implements ITreeView {
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);
this.tree.setInput(this.root).then(() => this.updateContentAreas());
}
private updateMessage(): void {
if (this._message) {
this.showMessage(this._message);
} else if (!this.dataProvider) {
this.showMessage(noDataProviderMessage);
} else {
this.hideMessage();
}
this.updateContentAreas();
}
private showMessage(message: string | IMarkdownString): void {
DOM.removeClass(this.messageElement, 'hide');
if (this._messageValue !== message) {
this.resetMessageElement();
this._messageValue = message;
if (isString(this._messageValue)) {
this.messageElement.textContent = this._messageValue;
} else {
this.markdownResult = this.markdownRenderer.render(this._messageValue);
DOM.append(this.messageElement, this.markdownResult.element);
}
this.layout(this._size);
}
}
private hideMessage(): void {
this.resetMessageElement();
DOM.addClass(this.messageElement, 'hide');
this.layout(this._size);
}
private resetMessageElement(): void {
if (this.markdownResult) {
this.markdownResult.dispose();
this.markdownResult = null;
}
DOM.clearNode(this.messageElement);
}
private _size: number;
layout(size: number) {
if (this.tree) {
this.treeContainer.style.height = size + 'px';
this.tree.layout(size);
if (size) {
this._size = size;
const treeSize = size - DOM.getTotalHeight(this.messageElement);
this.treeContainer.style.height = treeSize + 'px';
if (this.tree) {
this.tree.layout(treeSize);
}
}
}
getOptimalWidth(): number {
if (this.tree) {
const parentNode = this.tree.getHTMLElement();
const childNodes = [].slice.call(parentNode.querySelectorAll('.outline-item-label > a'));
const childNodes = ([] as Element[]).slice.call(parentNode.querySelectorAll('.outline-item-label > a'));
return DOM.getLargestChildWidth(parentNode, childNodes);
}
return 0;
}
refresh(elements?: ITreeItem[]): Promise<void> {
if (this.tree) {
if (this.dataProvider && this.tree) {
elements = elements || [this.root];
for (const element of elements) {
element.children = null; // reset children
@@ -250,50 +404,74 @@ export class CustomTreeView extends Disposable implements ITreeView {
return Promise.resolve(null);
}
reveal(item: ITreeItem, parentChain?: ITreeItem[], options?: { select?: boolean, focus?: boolean }): Thenable<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 ? Promise.resolve(null) : this.refresh(); // Refresh if root is not populated
return promise.then(() => {
var result = TPromise.as(null);
if (parentChain) {
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);
}
});
});
expand(itemOrItems: ITreeItem | ITreeItem[]): Thenable<void> {
if (this.tree) {
itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
return this.tree.expandAll(itemOrItems);
}
return Promise.resolve(null);
return Promise.arguments(null);
}
setSelection(items: ITreeItem[]): void {
if (this.tree) {
this.tree.setSelection(items, { source: 'api' });
}
}
setFocus(item: ITreeItem): void {
if (this.tree) {
this.focus();
this.tree.setFocus(item);
}
}
reveal(item: ITreeItem): Thenable<void> {
if (this.tree) {
return this.tree.reveal(item);
}
return Promise.arguments(null);
}
private activate() {
if (!this.activated) {
this.extensionService.activateByEvent(`onView:${this.id}`);
this.createTree();
this.progressService.withProgress({ location: this.container.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`))
.then(() => timeout(2000))
.then(() => {
this.updateMessage();
});
this.activated = true;
}
}
private refreshing: boolean = false;
private doRefresh(elements: ITreeItem[]): Promise<void> {
if (this.tree) {
return Promise.all(elements.map(e => this.tree.refresh(e))).then(() => null);
this.refreshing = true;
return Promise.all(elements.map(e => this.tree.refresh(e)))
.then(() => {
this.refreshing = false;
this.updateContentAreas();
if (this.focused) {
this.focus();
}
});
}
return Promise.resolve(null);
}
private updateContentAreas(): void {
const isTreeEmpty = !this.root.children || this.root.children.length === 0;
// Hide tree container only when there is a message and tree is empty and not refreshing
if (this._messageValue && isTreeEmpty && !this.refreshing) {
DOM.addClass(this.treeContainer, 'hide');
this.domNode.setAttribute('tabindex', '0');
} else {
DOM.removeClass(this.treeContainer, 'hide');
this.domNode.removeAttribute('tabindex');
}
}
private onSelection({ payload }: any): void {
if (payload && (!!payload.didClickOnTwistie || payload.source === 'api')) {
return;
@@ -316,6 +494,7 @@ export class CustomTreeView extends Disposable implements ITreeView {
class TreeDataSource implements IDataSource {
constructor(
private treeView: ITreeView,
private container: ViewContainer,
@IProgressService2 private progressService: IProgressService2,
@IOEShimService private objectExplorerService: IOEShimService
@@ -327,31 +506,28 @@ class TreeDataSource implements IDataSource {
}
hasChildren(tree: ITree, node: ITreeItem): boolean {
return this.objectExplorerService.providerExists(node.childProvider) && node.collapsibleState !== TreeItemCollapsibleState.None;
if (node.childProvider) {
return this.objectExplorerService.providerExists(node.childProvider) && node.collapsibleState !== TreeItemCollapsibleState.None;
}
return this.treeView.dataProvider && 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.id }, () => {
// 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);
}));
getChildren(tree: ITree, node: ITreeItem): Promise<any[]> {
if (node.childProvider) {
return this.progressService.withProgress({ location: this.container.id }, () => this.objectExplorerService.getChildren(node, this));
}
return TPromise.as([]);
if (this.treeView.dataProvider) {
return this.progressService.withProgress({ location: this.container.id }, () => this.treeView.dataProvider.getChildren(node));
}
return Promise.resolve([]);
}
shouldAutoexpand(tree: ITree, node: ITreeItem): boolean {
return node.collapsibleState === TreeItemCollapsibleState.Expanded;
}
getParent(tree: ITree, node: any): TPromise<any> {
return TPromise.as(null);
getParent(tree: ITree, node: any): Promise<any> {
return Promise.resolve(null);
}
}
@@ -362,6 +538,31 @@ interface ITreeExplorerTemplateData {
aligner: Aligner;
}
// todo@joh,sandy make this proper and contributable from extensions
registerThemingParticipant((theme, collector) => {
const findMatchHighlightColor = theme.getColor(editorFindMatchHighlight);
if (findMatchHighlightColor) {
collector.addRule(`.file-icon-themable-tree .monaco-tree-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${findMatchHighlightColor}; }`);
}
const findMatchHighlightColorBorder = theme.getColor(editorFindMatchHighlightBorder);
if (findMatchHighlightColorBorder) {
collector.addRule(`.file-icon-themable-tree .monaco-tree-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${findMatchHighlightColorBorder}; box-sizing: border-box; }`);
}
const link = theme.getColor(textLinkForeground);
if (link) {
collector.addRule(`.tree-explorer-viewlet-tree-view > .message a { color: ${link}; }`);
}
const focustBorderColor = theme.getColor(focusBorder);
if (focustBorderColor) {
collector.addRule(`.tree-explorer-viewlet-tree-view > .message a:focus { outline: 1px solid ${focustBorderColor}; outline-offset: -1px; }`);
}
const codeBackground = theme.getColor(textCodeBlockBackground);
if (codeBackground) {
collector.addRule(`.tree-explorer-viewlet-tree-view > .message code { background-color: ${codeBackground}; }`);
}
});
class TreeRenderer implements IRenderer {
private static readonly ITEM_HEIGHT = 22;
@@ -375,6 +576,7 @@ class TreeRenderer implements IRenderer {
@IInstantiationService private instantiationService: IInstantiationService,
@IWorkbenchThemeService private themeService: IWorkbenchThemeService,
@IConfigurationService private configurationService: IConfigurationService,
@ILabelService private labelService: ILabelService
) {
}
@@ -390,7 +592,7 @@ class TreeRenderer implements IRenderer {
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, {});
const resourceLabel = this.instantiationService.createInstance(ResourceLabel, container, { supportHighlights: true, donotSupportOcticons: true });
DOM.addClass(resourceLabel.element, 'custom-view-tree-node-item-resourceLabel');
const actionsContainer = DOM.append(resourceLabel.element, DOM.$('.actions'));
const actionBar = new ActionBar(actionsContainer, {
@@ -403,28 +605,27 @@ class TreeRenderer implements IRenderer {
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.label : resource ? basename(resource.path) : '';
let icon = this.themeService.getTheme().type === LIGHT ? node.icon.path : node.iconDark.path;
const treeItemLabel: ITreeItemLabel = node.label ? node.label : resource ? { label: basename(resource.path) } : void 0;
const description = isString(node.description) ? node.description : resource && node.description === true ? this.labelService.getUriLabel(dirname(resource), { relative: true }) : void 0;
const label = treeItemLabel ? treeItemLabel.label : void 0;
const matches = treeItemLabel && treeItemLabel.highlights ? treeItemLabel.highlights.map(([start, end]) => ({ start, end })) : void 0;
const icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark;
const iconUrl = icon ? URI.revive(icon) : null;
const title = node.tooltip ? node.tooltip : resource ? void 0 : label;
// reset
templateData.resourceLabel.clear();
templateData.actionBar.clear();
if ((resource || node.themeIcon) && !icon) {
if (resource || node.themeIcon) {
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'] });
templateData.resourceLabel.setLabel({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches });
} else {
templateData.resourceLabel.setLabel({ name: label }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'] });
templateData.resourceLabel.setLabel({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches });
}
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.icon.style.backgroundImage = iconUrl ? `url('${iconUrl.toString(true)}')` : '';
DOM.toggleClass(templateData.icon, 'custom-view-tree-node-item-icon', !!iconUrl);
templateData.actionBar.context = (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle });
templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });
@@ -489,7 +690,7 @@ class Aligner extends Disposable {
return parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c));
}
private hasIcon(node: vsITreeItem): boolean {
private hasIcon(node: ITreeItem): boolean {
const icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark;
if (icon) {
return true;
@@ -566,7 +767,7 @@ class MultipleSelectionActionRunner extends ActionRunner {
super();
}
runAction(action: IAction, context: any): TPromise<any> {
runAction(action: IAction, context: any): Thenable<any> {
if (action instanceof MenuItemAction) {
const selection = this.getSelectedResources();
const filteredSelection = selection.filter(s => s !== context);
@@ -618,3 +819,39 @@ class TreeMenus extends Disposable implements IDisposable {
return result;
}
}
class MarkdownRenderer {
constructor(
@IOpenerService private readonly _openerService: IOpenerService
) {
}
private getOptions(disposeables: IDisposable[]): RenderOptions {
return {
actionHandler: {
callback: (content) => {
let uri: URI | undefined;
try {
uri = URI.parse(content);
} catch {
// ignore
}
if (uri && this._openerService) {
this._openerService.open(uri).catch(onUnexpectedError);
}
},
disposeables
}
};
}
render(markdown: IMarkdownString): IMarkdownRenderResult {
let disposeables: IDisposable[] = [];
const element: HTMLElement = markdown ? renderMarkdown(markdown, this.getOptions(disposeables)) : document.createElement('span');
return {
element,
dispose: () => dispose(disposeables)
};
}
}

View File

@@ -98,7 +98,7 @@ export const enum MenuId {
ViewTitle,
// {{SQL CARBON EDIT}}
ObjectExplorerItemContext,
NotebookToolbar,
NotebookToolbar
}
export interface IMenuActionOptions {

View File

@@ -5,20 +5,13 @@
import { Disposable } from 'vs/base/common/lifecycle';
import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
// {{SQL CARBON EDIT}}
import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, ViewsRegistry, ICustomViewDescriptor, IRevealOptions, TreeItemCollapsibleState } from 'vs/workbench/common/views';
import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, ViewsRegistry, ICustomViewDescriptor, IRevealOptions } 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';
import { isUndefinedOrNull, isNumber } from 'vs/base/common/types';
import { IMarkdownString } from 'vs/base/common/htmlContent';
// {{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 {
@@ -29,8 +22,6 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
extHostContext: IExtHostContext,
@IViewsService private viewsService: IViewsService,
@INotificationService private notificationService: INotificationService,
// {{SQL CARBON EDIT}}
@IObjectExplorerService private objectExplorerService: IObjectExplorerService
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews);
@@ -39,10 +30,6 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean }): 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.getTreeView(treeViewId);
if (viewer) {
viewer.dataProvider = dataProvider;
@@ -127,25 +114,6 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
return viewDescriptor ? viewDescriptor.treeView : 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.treeView.refresh();
}
return false;
}
dispose(): void {
this._dataProviders.forEach((dataProvider, treeViewId) => {
const treeView = this.getTreeView(treeViewId);
@@ -239,100 +207,3 @@ 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 Promise.resolve({ 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.label,
iconType: e.icon.path,
// this is just needed since we don't have this
nodeSubType: e.iconDark.path,
isLeaf: e.collapsibleState === TreeItemCollapsibleState.None,
childProvider: e.childProvider,
payload: e.payload,
nodeType: e.contextValue
};
}),
sessionId: this.sessionId
});
});
return Promise.resolve(true);
}
public refreshNode(nodeInfo: sqlops.ExpandNodeInfo): Thenable<boolean> {
// no op
return Promise.resolve(true);
}
public closeSession(closeSessionInfo: sqlops.ObjectExplorerCloseSessionInfo): Thenable<sqlops.ObjectExplorerCloseSessionResponse> {
// no op
return Promise.resolve({ sessionId: undefined, success: true });
}
public findNodes(findNodesInfo: sqlops.FindNodesInfo): Thenable<sqlops.ObjectExplorerFindNodesResponse> {
// no op
return Promise.resolve({ 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;
}
}