mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
update data explorer with vscode's (#6442)
This commit is contained in:
@@ -4,33 +4,33 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { Event, Emitter } from 'vs/base/common/event';
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
import { IDisposable, Disposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { IAction, ActionRunner, Action } from 'vs/base/common/actions';
|
import { IAction, IActionViewItem, ActionRunner, Action } from 'vs/base/common/actions';
|
||||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
|
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||||
|
import { ContextAwareMenuEntryActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
import { TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ViewContainer, ITreeItemLabel } from 'vs/workbench/common/views';
|
import { IViewsService, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ViewContainer, ITreeItemLabel, Extensions } from 'vs/workbench/common/views';
|
||||||
import { FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
import { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
|
import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels';
|
||||||
import { ResourceLabel } from 'vs/workbench/browser/labels';
|
|
||||||
import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { basename } from 'vs/base/common/path';
|
import { dirname, basename } from 'vs/base/common/resources';
|
||||||
import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||||
import { FileKind } from 'vs/platform/files/common/files';
|
import { FileKind } from 'vs/platform/files/common/files';
|
||||||
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
|
import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService';
|
||||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { timeout } from 'vs/base/common/async';
|
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 { editorFindMatchHighlight, editorFindMatchHighlightBorder, textLinkForeground, textCodeBlockBackground, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||||
import { isString } from 'vs/base/common/types';
|
import { isString } from 'vs/base/common/types';
|
||||||
@@ -39,55 +39,106 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
|||||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||||
import { IMarkdownRenderResult } from 'vs/editor/contrib/markdown/markdownRenderer';
|
import { IMarkdownRenderResult } from 'vs/editor/contrib/markdown/markdownRenderer';
|
||||||
import { ILabelService } from 'vs/platform/label/common/label';
|
import { ILabelService } from 'vs/platform/label/common/label';
|
||||||
import { dirname } from 'vs/base/common/resources';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
|
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||||
|
import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
|
||||||
|
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||||
|
import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults';
|
||||||
|
|
||||||
import { ITreeItem, ITreeView } from 'sql/workbench/common/views';
|
import { ITreeItem, ITreeView } from 'sql/workbench/common/views';
|
||||||
import { IOEShimService } from 'sql/workbench/parts/objectExplorer/common/objectExplorerViewTreeShim';
|
import { IOEShimService } from 'sql/workbench/parts/objectExplorer/common/objectExplorerViewTreeShim';
|
||||||
import { NodeContextKey } from 'sql/workbench/parts/dataExplorer/common/nodeContext';
|
import { NodeContextKey } from 'sql/workbench/parts/dataExplorer/common/nodeContext';
|
||||||
import { ContextAwareMenuEntryActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
|
||||||
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
|
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
|
||||||
|
|
||||||
class TitleMenus implements IDisposable {
|
export class CustomTreeViewPanel extends ViewletPanel {
|
||||||
|
|
||||||
|
private treeView: ITreeView;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
options: IViewletViewOptions,
|
||||||
|
@INotificationService private readonly notificationService: INotificationService,
|
||||||
|
@IKeybindingService keybindingService: IKeybindingService,
|
||||||
|
@IContextMenuService contextMenuService: IContextMenuService,
|
||||||
|
@IConfigurationService configurationService: IConfigurationService,
|
||||||
|
@IViewsService viewsService: IViewsService,
|
||||||
|
) {
|
||||||
|
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService);
|
||||||
|
const { treeView } = (<ITreeViewDescriptor>Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getView(options.id));
|
||||||
|
this.treeView = treeView as ITreeView;
|
||||||
|
this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this));
|
||||||
|
this._register(toDisposable(() => this.treeView.setVisibility(false)));
|
||||||
|
this._register(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility()));
|
||||||
|
this.updateTreeVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
focus(): void {
|
||||||
|
super.focus();
|
||||||
|
this.treeView.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBody(container: HTMLElement): void {
|
||||||
|
this.treeView.show(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
layoutBody(size: number): void {
|
||||||
|
this.treeView.layout(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
getActions(): IAction[] {
|
||||||
|
return [...this.treeView.getPrimaryActions()];
|
||||||
|
}
|
||||||
|
|
||||||
|
getSecondaryActions(): IAction[] {
|
||||||
|
return [...this.treeView.getSecondaryActions()];
|
||||||
|
}
|
||||||
|
|
||||||
|
getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||||
|
return action instanceof MenuItemAction ? new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOptimalWidth(): number {
|
||||||
|
return this.treeView.getOptimalWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateTreeVisibility(): void {
|
||||||
|
this.treeView.setVisibility(this.isBodyVisible());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TitleMenus extends Disposable {
|
||||||
|
|
||||||
private disposables: IDisposable[] = [];
|
|
||||||
private titleDisposable: IDisposable = Disposable.None;
|
|
||||||
private titleActions: IAction[] = [];
|
private titleActions: IAction[] = [];
|
||||||
|
private readonly titleActionsDisposable = this._register(new MutableDisposable());
|
||||||
private titleSecondaryActions: IAction[] = [];
|
private titleSecondaryActions: IAction[] = [];
|
||||||
|
|
||||||
private _onDidChangeTitle = new Emitter<void>();
|
private _onDidChangeTitle = this._register(new Emitter<void>());
|
||||||
get onDidChangeTitle(): Event<void> { return this._onDidChangeTitle.event; }
|
readonly onDidChangeTitle: Event<void> = this._onDidChangeTitle.event;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
id: string,
|
id: string,
|
||||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||||
@IMenuService private menuService: IMenuService,
|
@IMenuService private readonly menuService: IMenuService,
|
||||||
) {
|
) {
|
||||||
if (this.titleDisposable) {
|
super();
|
||||||
this.titleDisposable.dispose();
|
|
||||||
this.titleDisposable = Disposable.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _contextKeyService = this.contextKeyService.createScoped();
|
const scopedContextKeyService = this._register(this.contextKeyService.createScoped());
|
||||||
_contextKeyService.createKey('view', id);
|
scopedContextKeyService.createKey('view', id);
|
||||||
|
|
||||||
const titleMenu = this.menuService.createMenu(MenuId.ViewTitle, _contextKeyService);
|
const titleMenu = this._register(this.menuService.createMenu(MenuId.ViewTitle, scopedContextKeyService));
|
||||||
const updateActions = () => {
|
const updateActions = () => {
|
||||||
this.titleActions = [];
|
this.titleActions = [];
|
||||||
this.titleSecondaryActions = [];
|
this.titleSecondaryActions = [];
|
||||||
createAndFillInActionBarActions(titleMenu, undefined, { primary: this.titleActions, secondary: this.titleSecondaryActions });
|
this.titleActionsDisposable.value = createAndFillInActionBarActions(titleMenu, undefined, { primary: this.titleActions, secondary: this.titleSecondaryActions });
|
||||||
this._onDidChangeTitle.fire();
|
this._onDidChangeTitle.fire();
|
||||||
};
|
};
|
||||||
|
|
||||||
const listener = titleMenu.onDidChange(updateActions);
|
this._register(titleMenu.onDidChange(updateActions));
|
||||||
updateActions();
|
updateActions();
|
||||||
|
|
||||||
this.titleDisposable = toDisposable(() => {
|
this._register(toDisposable(() => {
|
||||||
listener.dispose();
|
|
||||||
titleMenu.dispose();
|
|
||||||
_contextKeyService.dispose();
|
|
||||||
this.titleActions = [];
|
this.titleActions = [];
|
||||||
this.titleSecondaryActions = [];
|
this.titleSecondaryActions = [];
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
getTitleActions(): IAction[] {
|
getTitleActions(): IAction[] {
|
||||||
@@ -97,18 +148,14 @@ class TitleMenus implements IDisposable {
|
|||||||
getTitleSecondaryActions(): IAction[] {
|
getTitleSecondaryActions(): IAction[] {
|
||||||
return this.titleSecondaryActions;
|
return this.titleSecondaryActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
|
||||||
this.disposables = dispose(this.disposables);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Root implements ITreeItem {
|
class Root implements ITreeItem {
|
||||||
label = { label: 'root' };
|
label = { label: 'root' };
|
||||||
handle = '0';
|
handle = '0';
|
||||||
parentHandle = null;
|
parentHandle: string | undefined = undefined;
|
||||||
collapsibleState = TreeItemCollapsibleState.Expanded;
|
collapsibleState = TreeItemCollapsibleState.Expanded;
|
||||||
children = void 0;
|
children: ITreeItem[] | undefined = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data.");
|
const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data.");
|
||||||
@@ -126,40 +173,44 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
|||||||
private treeContainer: HTMLElement;
|
private treeContainer: HTMLElement;
|
||||||
private _messageValue: string | IMarkdownString | undefined;
|
private _messageValue: string | IMarkdownString | undefined;
|
||||||
private messageElement: HTMLDivElement;
|
private messageElement: HTMLDivElement;
|
||||||
private tree: FileIconThemableWorkbenchTree;
|
private tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>;
|
||||||
|
private treeLabels: ResourceLabels;
|
||||||
private root: ITreeItem;
|
private root: ITreeItem;
|
||||||
private elementsToRefresh: ITreeItem[] = [];
|
private elementsToRefresh: ITreeItem[] = [];
|
||||||
private menus: TitleMenus;
|
private menus: TitleMenus;
|
||||||
|
|
||||||
private markdownRenderer: MarkdownRenderer;
|
private markdownRenderer: MarkdownRenderer;
|
||||||
private markdownResult: IMarkdownRenderResult;
|
private markdownResult: IMarkdownRenderResult | null;
|
||||||
|
|
||||||
private _onDidExpandItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
|
private readonly _onDidExpandItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
|
||||||
readonly onDidExpandItem: Event<ITreeItem> = this._onDidExpandItem.event;
|
readonly onDidExpandItem: Event<ITreeItem> = this._onDidExpandItem.event;
|
||||||
|
|
||||||
private _onDidCollapseItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
|
private readonly _onDidCollapseItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
|
||||||
readonly onDidCollapseItem: Event<ITreeItem> = this._onDidCollapseItem.event;
|
readonly onDidCollapseItem: Event<ITreeItem> = this._onDidCollapseItem.event;
|
||||||
|
|
||||||
private _onDidChangeSelection: Emitter<ITreeItem[]> = this._register(new Emitter<ITreeItem[]>());
|
private _onDidChangeSelection: Emitter<ITreeItem[]> = this._register(new Emitter<ITreeItem[]>());
|
||||||
readonly onDidChangeSelection: Event<ITreeItem[]> = this._onDidChangeSelection.event;
|
readonly onDidChangeSelection: Event<ITreeItem[]> = this._onDidChangeSelection.event;
|
||||||
|
|
||||||
private _onDidChangeVisibility: Emitter<boolean> = this._register(new Emitter<boolean>());
|
private readonly _onDidChangeVisibility: Emitter<boolean> = this._register(new Emitter<boolean>());
|
||||||
readonly onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility.event;
|
readonly onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility.event;
|
||||||
|
|
||||||
private _onDidChangeActions: Emitter<void> = this._register(new Emitter<void>());
|
private readonly _onDidChangeActions: Emitter<void> = this._register(new Emitter<void>());
|
||||||
readonly onDidChangeActions: Event<void> = this._onDidChangeActions.event;
|
readonly onDidChangeActions: Event<void> = this._onDidChangeActions.event;
|
||||||
|
|
||||||
private nodeContext: NodeContextKey;
|
private nodeContext: NodeContextKey;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private id: string,
|
private id: string,
|
||||||
private container: ViewContainer,
|
private title: string,
|
||||||
@IExtensionService private extensionService: IExtensionService,
|
private viewContainer: ViewContainer,
|
||||||
@IWorkbenchThemeService private themeService: IWorkbenchThemeService,
|
@IExtensionService private readonly extensionService: IExtensionService,
|
||||||
@IInstantiationService private instantiationService: IInstantiationService,
|
@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
|
||||||
@ICommandService private commandService: ICommandService,
|
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||||
@IConfigurationService private configurationService: IConfigurationService,
|
@ICommandService private readonly commandService: ICommandService,
|
||||||
@IProgressService private progressService: IProgressService
|
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||||
|
@IProgressService private readonly progressService: IProgressService,
|
||||||
|
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||||
|
@IKeybindingService private readonly keybindingService: IKeybindingService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.root = new Root();
|
this.root = new Root();
|
||||||
@@ -178,29 +229,31 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
|||||||
this.markdownResult.dispose();
|
this.markdownResult.dispose();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
this._register(Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).onDidChangeContainer(({ views, from, to }) => {
|
||||||
|
if (from === this.viewContainer && views.some(v => v.id === this.id)) {
|
||||||
|
this.viewContainer = to;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
this.nodeContext = this._register(instantiationService.createInstance(NodeContextKey));
|
this.nodeContext = this._register(instantiationService.createInstance(NodeContextKey));
|
||||||
|
|
||||||
this.create();
|
this.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dataProvider: ITreeViewDataProvider;
|
private _dataProvider: ITreeViewDataProvider | null;
|
||||||
get dataProvider(): ITreeViewDataProvider {
|
get dataProvider(): ITreeViewDataProvider | null {
|
||||||
return this._dataProvider;
|
return this._dataProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
set dataProvider(dataProvider: ITreeViewDataProvider) {
|
set dataProvider(dataProvider: ITreeViewDataProvider | null) {
|
||||||
if (dataProvider) {
|
if (dataProvider) {
|
||||||
this._dataProvider = new class implements ITreeViewDataProvider {
|
this._dataProvider = new class implements ITreeViewDataProvider {
|
||||||
getChildren(node?: ITreeItem): Promise<ITreeItem[]> {
|
async getChildren(node: ITreeItem): Promise<ITreeItem[]> {
|
||||||
if (node && node.children) {
|
if (node && node.children) {
|
||||||
return Promise.resolve(node.children);
|
return Promise.resolve(node.children);
|
||||||
}
|
}
|
||||||
const promise = node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node);
|
const children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node));
|
||||||
return promise.then(children => {
|
node.children = children;
|
||||||
node.children = children;
|
return children;
|
||||||
return children;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.updateMessage();
|
this.updateMessage();
|
||||||
@@ -246,7 +299,7 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
|||||||
|
|
||||||
getPrimaryActions(): IAction[] {
|
getPrimaryActions(): IAction[] {
|
||||||
if (this.showCollapseAllAction) {
|
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());
|
const collapseAllAction = new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action collapse-all', true, () => this.tree ? new CollapseAllAction<ITreeItem, ITreeItem, FuzzyScore>(this.tree, true).run() : Promise.resolve());
|
||||||
return [...this.menus.getTitleActions(), collapseAllAction];
|
return [...this.menus.getTitleActions(), collapseAllAction];
|
||||||
} else {
|
} else {
|
||||||
return this.menus.getTitleActions();
|
return this.menus.getTitleActions();
|
||||||
@@ -275,12 +328,6 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
|||||||
DOM.hide(this.tree.getHTMLElement()); // make sure the tree goes out of the tabindex world by hiding it
|
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) {
|
if (this.isVisible && this.elementsToRefresh.length) {
|
||||||
this.doRefresh(this.elementsToRefresh);
|
this.doRefresh(this.elementsToRefresh);
|
||||||
this.elementsToRefresh = [];
|
this.elementsToRefresh = [];
|
||||||
@@ -313,29 +360,110 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
|||||||
this.domNode = DOM.$('.tree-explorer-viewlet-tree-view');
|
this.domNode = DOM.$('.tree-explorer-viewlet-tree-view');
|
||||||
this.messageElement = DOM.append(this.domNode, DOM.$('.message'));
|
this.messageElement = DOM.append(this.domNode, DOM.$('.message'));
|
||||||
this.treeContainer = DOM.append(this.domNode, DOM.$('.customview-tree'));
|
this.treeContainer = DOM.append(this.domNode, DOM.$('.customview-tree'));
|
||||||
|
DOM.addClass(this.treeContainer, 'file-icon-themable-tree');
|
||||||
|
DOM.addClass(this.treeContainer, 'show-file-icons');
|
||||||
const focusTracker = this._register(DOM.trackFocus(this.domNode));
|
const focusTracker = this._register(DOM.trackFocus(this.domNode));
|
||||||
this._register(focusTracker.onDidFocus(() => this.focused = true));
|
this._register(focusTracker.onDidFocus(() => this.focused = true));
|
||||||
this._register(focusTracker.onDidBlur(() => this.focused = false));
|
this._register(focusTracker.onDidBlur(() => this.focused = false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTree() {
|
private createTree() {
|
||||||
const actionItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) : undefined;
|
const actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) : undefined;
|
||||||
const menus = this.instantiationService.createInstance(TreeMenus, this.id);
|
const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id));
|
||||||
const dataSource = this.instantiationService.createInstance(TreeDataSource, this, this.container, this.id);
|
this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this));
|
||||||
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, menus, actionItemProvider);
|
const dataSource = this.instantiationService.createInstance(TreeDataSource, this, this.id, <T>(task: Promise<T>) => this.progressService.withProgress({ location: this.viewContainer.id }, () => task));
|
||||||
const controller = this.instantiationService.createInstance(TreeController, this.id, this.container.id, menus);
|
const aligner = new Aligner(this.themeService);
|
||||||
this.tree = this.instantiationService.createInstance(FileIconThemableWorkbenchTree, this.treeContainer, { dataSource, renderer, controller }, {});
|
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner);
|
||||||
|
|
||||||
|
this.tree = this._register(this.instantiationService.createInstance(WorkbenchAsyncDataTree, this.treeContainer, new CustomTreeDelegate(), [renderer],
|
||||||
|
dataSource, {
|
||||||
|
identityProvider: new CustomViewIdentityProvider(),
|
||||||
|
accessibilityProvider: {
|
||||||
|
getAriaLabel(element: ITreeItem): string {
|
||||||
|
return element.label ? element.label.label : '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ariaLabel: this.title,
|
||||||
|
keyboardNavigationLabelProvider: {
|
||||||
|
getKeyboardNavigationLabel: (item: ITreeItem) => {
|
||||||
|
return item.label ? item.label.label : (item.resourceUri ? basename(URI.revive(item.resourceUri)) : undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expandOnlyOnTwistieClick: (e: ITreeItem) => !!e.command,
|
||||||
|
collapseByDefault: (e: ITreeItem): boolean => {
|
||||||
|
return e.collapsibleState !== TreeItemCollapsibleState.Expanded;
|
||||||
|
}
|
||||||
|
}) as WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>);
|
||||||
|
aligner.tree = this.tree;
|
||||||
|
|
||||||
this.tree.contextKeyService.createKey<boolean>(this.id, true);
|
this.tree.contextKeyService.createKey<boolean>(this.id, true);
|
||||||
this._register(this.tree);
|
this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e)));
|
||||||
this._register(this.tree.onDidChangeSelection(e => this.onSelection(e)));
|
this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements)));
|
||||||
this._register(this.tree.onDidExpandItem(e => this._onDidExpandItem.fire(e.item.getElement())));
|
this._register(this.tree.onDidChangeCollapseState(e => {
|
||||||
this._register(this.tree.onDidCollapseItem(e => this._onDidCollapseItem.fire(e.item.getElement())));
|
const element: ITreeItem = Array.isArray(e.node.element.element) ? e.node.element.element[0] : e.node.element.element;
|
||||||
this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.selection)));
|
if (e.node.collapsed) {
|
||||||
|
this._onDidCollapseItem.fire(element);
|
||||||
|
} else {
|
||||||
|
this._onDidExpandItem.fire(element);
|
||||||
|
}
|
||||||
|
}));
|
||||||
// Update resource context based on focused element
|
// Update resource context based on focused element
|
||||||
this._register(this.tree.onDidChangeFocus((e: { focus: ITreeItem }) => {
|
this._register(this.tree.onDidChangeFocus(e => {
|
||||||
this.nodeContext.set({ node: e.focus, viewId: this.id });
|
this.nodeContext.set({ node: e.elements[0], viewId: this.id });
|
||||||
}));
|
}));
|
||||||
this.tree.setInput(this.root).then(() => this.updateContentAreas());
|
this.tree.setInput(this.root).then(() => this.updateContentAreas());
|
||||||
|
|
||||||
|
const customTreeNavigator = new TreeResourceNavigator2(this.tree);
|
||||||
|
this._register(customTreeNavigator);
|
||||||
|
this._register(customTreeNavigator.onDidOpenResource(e => {
|
||||||
|
if (!e.browserEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selection = this.tree.getSelection();
|
||||||
|
if ((selection.length === 1) && selection[0].command) {
|
||||||
|
this.commandService.executeCommand(selection[0].command.id, ...(selection[0].command.arguments || []));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent<ITreeItem>): void {
|
||||||
|
const node: ITreeItem | null = treeEvent.element;
|
||||||
|
if (node === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const event: UIEvent = treeEvent.browserEvent;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
this.tree.setFocus([node]);
|
||||||
|
const actions = treeMenus.getResourceContextActions(node);
|
||||||
|
if (!actions.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.contextMenuService.showContextMenu({
|
||||||
|
getAnchor: () => treeEvent.anchor,
|
||||||
|
|
||||||
|
getActions: () => actions,
|
||||||
|
|
||||||
|
getActionViewItem: (action) => {
|
||||||
|
const keybinding = this.keybindingService.lookupKeybinding(action.id);
|
||||||
|
if (keybinding) {
|
||||||
|
return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() });
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
|
||||||
|
onHide: (wasCancelled?: boolean) => {
|
||||||
|
if (wasCancelled) {
|
||||||
|
this.tree.domFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getActionsContext: () => (<TreeViewItemHandleArg>{ $treeViewId: this.id, $treeItemHandle: node.handle, $treeItem: node, $treeContainerId: this.treeContainer.id }),
|
||||||
|
|
||||||
|
actionRunner: new MultipleSelectionActionRunner(() => this.tree.getSelection())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateMessage(): void {
|
private updateMessage(): void {
|
||||||
@@ -401,59 +529,74 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
|||||||
|
|
||||||
refresh(elements?: ITreeItem[]): Promise<void> {
|
refresh(elements?: ITreeItem[]): Promise<void> {
|
||||||
if (this.dataProvider && this.tree) {
|
if (this.dataProvider && this.tree) {
|
||||||
elements = elements || [this.root];
|
if (!elements) {
|
||||||
|
elements = [this.root];
|
||||||
|
// remove all waiting elements to refresh if root is asked to refresh
|
||||||
|
this.elementsToRefresh = [];
|
||||||
|
}
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
element.children = null; // reset children
|
element.children = undefined; // reset children
|
||||||
}
|
}
|
||||||
if (this.isVisible) {
|
if (this.isVisible) {
|
||||||
return this.doRefresh(elements);
|
return this.doRefresh(elements);
|
||||||
} else {
|
} else {
|
||||||
this.elementsToRefresh.push(...elements);
|
if (this.elementsToRefresh.length) {
|
||||||
|
const seen: Set<string> = new Set<string>();
|
||||||
|
this.elementsToRefresh.forEach(element => seen.add(element.handle));
|
||||||
|
for (const element of elements) {
|
||||||
|
if (!seen.has(element.handle)) {
|
||||||
|
this.elementsToRefresh.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.elementsToRefresh.push(...elements);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
expand(itemOrItems: ITreeItem | ITreeItem[]): Promise<void> {
|
collapse(element: ITreeItem): boolean {
|
||||||
if (this.tree) {
|
if (this.tree) {
|
||||||
itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
|
return this.tree.collapse(element);
|
||||||
return this.tree.expandAll(itemOrItems);
|
|
||||||
}
|
}
|
||||||
return Promise.arguments(null);
|
return Promise.arguments(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
collapse(itemOrItems: ITreeItem | ITreeItem[]): Thenable<void> {
|
async expand(itemOrItems: ITreeItem | ITreeItem[]): Promise<void> {
|
||||||
if (this.tree) {
|
if (this.tree) {
|
||||||
itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
|
itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
|
||||||
return this.tree.collapseAll(itemOrItems);
|
await Promise.all(itemOrItems.map(element => {
|
||||||
|
return this.tree.expand(element, false);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
return Promise.arguments(null);
|
return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelection(items: ITreeItem[]): void {
|
setSelection(items: ITreeItem[]): void {
|
||||||
if (this.tree) {
|
if (this.tree) {
|
||||||
this.tree.setSelection(items, { source: 'api' });
|
this.tree.setSelection(items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setFocus(item: ITreeItem): void {
|
setFocus(item: ITreeItem): void {
|
||||||
if (this.tree) {
|
if (this.tree) {
|
||||||
this.focus();
|
this.focus();
|
||||||
this.tree.setFocus(item);
|
this.tree.setFocus([item]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reveal(item: ITreeItem): Promise<void> {
|
reveal(item: ITreeItem): Promise<void> {
|
||||||
if (this.tree) {
|
if (this.tree) {
|
||||||
return this.tree.reveal(item);
|
return Promise.resolve(this.tree.reveal(item));
|
||||||
}
|
}
|
||||||
return Promise.arguments(null);
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
private activate() {
|
private activate() {
|
||||||
if (!this.activated) {
|
if (!this.activated) {
|
||||||
this.createTree();
|
this.createTree();
|
||||||
this.progressService.withProgress({ location: this.container.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`))
|
this.progressService.withProgress({ location: this.viewContainer.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`))
|
||||||
.then(() => timeout(2000))
|
.then(() => timeout(2000))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.updateMessage();
|
this.updateMessage();
|
||||||
@@ -463,19 +606,16 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private refreshing: boolean = false;
|
private refreshing: boolean = false;
|
||||||
private doRefresh(elements: ITreeItem[]): Promise<void> {
|
private async doRefresh(elements: ITreeItem[]): Promise<void> {
|
||||||
if (this.tree) {
|
if (this.tree) {
|
||||||
this.refreshing = true;
|
this.refreshing = true;
|
||||||
return Promise.all(elements.map(e => this.tree.refresh(e)))
|
await Promise.all(elements.map(element => this.tree.updateChildren(element, true)));
|
||||||
.then(() => {
|
this.refreshing = false;
|
||||||
this.refreshing = false;
|
this.updateContentAreas();
|
||||||
this.updateContentAreas();
|
if (this.focused) {
|
||||||
if (this.focused) {
|
this.focus();
|
||||||
this.focus();
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return Promise.resolve(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateContentAreas(): void {
|
private updateContentAreas(): void {
|
||||||
@@ -489,100 +629,83 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
|||||||
this.domNode.removeAttribute('tabindex');
|
this.domNode.removeAttribute('tabindex');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private onSelection({ payload }: any): void {
|
class CustomViewIdentityProvider implements IIdentityProvider<ITreeItem> {
|
||||||
if (payload && (!!payload.didClickOnTwistie || payload.source === 'api')) {
|
getId(element: ITreeItem): { toString(): string; } {
|
||||||
return;
|
return element.handle;
|
||||||
}
|
|
||||||
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 {
|
class CustomTreeDelegate implements IListVirtualDelegate<ITreeItem> {
|
||||||
|
|
||||||
|
getHeight(element: ITreeItem): number {
|
||||||
|
return TreeRenderer.ITEM_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTemplateId(element: ITreeItem): string {
|
||||||
|
return TreeRenderer.TREE_TEMPLATE_ID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TreeDataSource implements IAsyncDataSource<ITreeItem, ITreeItem> {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private treeView: ITreeView,
|
private treeView: ITreeView,
|
||||||
private container: ViewContainer,
|
|
||||||
private id: string,
|
private id: string,
|
||||||
@IProgressService private progressService: IProgressService,
|
private withProgress: <T>(task: Promise<T>) => Promise<T>,
|
||||||
@IOEShimService private objectExplorerService: IOEShimService
|
@IOEShimService private objectExplorerService: IOEShimService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
getId(tree: ITree, node: ITreeItem): string {
|
hasChildren(node: ITreeItem): boolean {
|
||||||
return node.handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasChildren(tree: ITree, node: ITreeItem): boolean {
|
|
||||||
if (node.childProvider) {
|
if (node.childProvider) {
|
||||||
return this.objectExplorerService.providerExists(node.childProvider) && node.collapsibleState !== TreeItemCollapsibleState.None;
|
return this.objectExplorerService.providerExists(node.childProvider) && node.collapsibleState !== TreeItemCollapsibleState.None;
|
||||||
}
|
}
|
||||||
return this.treeView.dataProvider && node.collapsibleState !== TreeItemCollapsibleState.None;
|
return this.treeView.dataProvider && node.collapsibleState !== TreeItemCollapsibleState.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildren(tree: ITree, node: ITreeItem): Promise<any[]> {
|
getChildren(node: ITreeItem): Promise<any[]> {
|
||||||
if (node.childProvider) {
|
if (node.childProvider) {
|
||||||
return this.progressService.withProgress({ location: this.container.id }, () => this.objectExplorerService.getChildren(node, this.id)).catch(e => {
|
return this.withProgress(this.objectExplorerService.getChildren(node, this.id)).catch(e => {
|
||||||
// if some error is caused we assume something tangently happened
|
// if some error is caused we assume something tangently happened
|
||||||
// i.e the user could retry if they wanted.
|
// i.e the user could retry if they wanted.
|
||||||
// So in order to enable this we need to tell the tree to refresh this node so it will ask us for the data again
|
// So in order to enable this we need to tell the tree to refresh this node so it will ask us for the data again
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
tree.collapse(node).then(() => tree.refresh(node));
|
this.treeView.collapse(node);
|
||||||
|
this.treeView.refresh([node]);
|
||||||
});
|
});
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this.treeView.dataProvider) {
|
if (this.treeView.dataProvider) {
|
||||||
return this.progressService.withProgress({ location: this.container.id }, () => this.treeView.dataProvider.getChildren(node));
|
return this.withProgress(this.treeView.dataProvider.getChildren(node));
|
||||||
}
|
}
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldAutoexpand(tree: ITree, node: ITreeItem): boolean {
|
|
||||||
return node.collapsibleState === TreeItemCollapsibleState.Expanded;
|
|
||||||
}
|
|
||||||
|
|
||||||
getParent(tree: ITree, node: any): Promise<any> {
|
|
||||||
return Promise.resolve(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ITreeExplorerTemplateData {
|
|
||||||
resourceLabel: ResourceLabel;
|
|
||||||
icon: HTMLElement;
|
|
||||||
actionBar: ActionBar;
|
|
||||||
aligner: Aligner;
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo@joh,sandy make this proper and contributable from extensions
|
// todo@joh,sandy make this proper and contributable from extensions
|
||||||
registerThemingParticipant((theme, collector) => {
|
registerThemingParticipant((theme, collector) => {
|
||||||
|
|
||||||
const findMatchHighlightColor = theme.getColor(editorFindMatchHighlight);
|
const findMatchHighlightColor = theme.getColor(editorFindMatchHighlight);
|
||||||
if (findMatchHighlightColor) {
|
if (findMatchHighlightColor) {
|
||||||
collector.addRule(`.file-icon-themable-tree .monaco-tree-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${findMatchHighlightColor}; }`);
|
collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${findMatchHighlightColor}; }`);
|
||||||
|
collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; background-color: ${findMatchHighlightColor}; }`);
|
||||||
}
|
}
|
||||||
const findMatchHighlightColorBorder = theme.getColor(editorFindMatchHighlightBorder);
|
const findMatchHighlightColorBorder = theme.getColor(editorFindMatchHighlightBorder);
|
||||||
if (findMatchHighlightColorBorder) {
|
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; }`);
|
collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${findMatchHighlightColorBorder}; box-sizing: border-box; }`);
|
||||||
|
collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${findMatchHighlightColorBorder}; box-sizing: border-box; }`);
|
||||||
}
|
}
|
||||||
const link = theme.getColor(textLinkForeground);
|
const link = theme.getColor(textLinkForeground);
|
||||||
if (link) {
|
if (link) {
|
||||||
collector.addRule(`.tree-explorer-viewlet-tree-view > .message a { color: ${link}; }`);
|
collector.addRule(`.tree-explorer-viewlet-tree-view > .message a { color: ${link}; }`);
|
||||||
}
|
}
|
||||||
const focustBorderColor = theme.getColor(focusBorder);
|
const focusBorderColor = theme.getColor(focusBorder);
|
||||||
if (focustBorderColor) {
|
if (focusBorderColor) {
|
||||||
collector.addRule(`.tree-explorer-viewlet-tree-view > .message a:focus { outline: 1px solid ${focustBorderColor}; outline-offset: -1px; }`);
|
collector.addRule(`.tree-explorer-viewlet-tree-view > .message a:focus { outline: 1px solid ${focusBorderColor}; outline-offset: -1px; }`);
|
||||||
}
|
}
|
||||||
const codeBackground = theme.getColor(textCodeBlockBackground);
|
const codeBackground = theme.getColor(textCodeBlockBackground);
|
||||||
if (codeBackground) {
|
if (codeBackground) {
|
||||||
@@ -590,77 +713,84 @@ registerThemingParticipant((theme, collector) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
class TreeRenderer implements IRenderer {
|
interface ITreeExplorerTemplateData {
|
||||||
|
elementDisposable: IDisposable;
|
||||||
|
container: HTMLElement;
|
||||||
|
resourceLabel: IResourceLabel;
|
||||||
|
icon: HTMLElement;
|
||||||
|
actionBar: ActionBar;
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly ITEM_HEIGHT = 22;
|
class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyScore, ITreeExplorerTemplateData> {
|
||||||
private static readonly TREE_TEMPLATE_ID = 'treeExplorer';
|
static readonly ITEM_HEIGHT = 22;
|
||||||
private static readonly MSSQL_TREE_TEMPLATE_ID = 'mssqltreeExplorer';
|
static readonly TREE_TEMPLATE_ID = 'treeExplorer';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private treeViewId: string,
|
private treeViewId: string,
|
||||||
private menus: TreeMenus,
|
private menus: TreeMenus,
|
||||||
private actionItemProvider: IActionViewItemProvider,
|
private labels: ResourceLabels,
|
||||||
@IInstantiationService private instantiationService: IInstantiationService,
|
private actionViewItemProvider: IActionViewItemProvider,
|
||||||
@IWorkbenchThemeService private themeService: IWorkbenchThemeService,
|
private aligner: Aligner,
|
||||||
@IConfigurationService private configurationService: IConfigurationService,
|
@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
|
||||||
@ILabelService private labelService: ILabelService
|
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||||
|
@ILabelService private readonly labelService: ILabelService
|
||||||
) {
|
) {
|
||||||
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeight(tree: ITree, element: any): number {
|
get templateId(): string {
|
||||||
return TreeRenderer.ITEM_HEIGHT;
|
return TreeRenderer.TREE_TEMPLATE_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTemplateId(tree: ITree, element: ITreeItem): string {
|
renderTemplate(container: HTMLElement): ITreeExplorerTemplateData {
|
||||||
return element.providerHandle === mssqlProviderName ? 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');
|
DOM.addClass(container, 'custom-view-tree-node-item');
|
||||||
|
|
||||||
const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon'));
|
const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon'));
|
||||||
const resourceLabel = this.instantiationService.createInstance(ResourceLabel, container, { supportHighlights: true, donotSupportOcticons: true });
|
|
||||||
DOM.addClass(resourceLabel.element.element, 'custom-view-tree-node-item-resourceLabel');
|
const resourceLabel = this.labels.create(container, { supportHighlights: true, donotSupportOcticons: true });
|
||||||
const actionsContainer = DOM.append(resourceLabel.element.element, DOM.$('.actions'));
|
const actionsContainer = DOM.append(resourceLabel.element, DOM.$('.actions'));
|
||||||
const actionBar = new ActionBar(actionsContainer, {
|
const actionBar = new ActionBar(actionsContainer, {
|
||||||
actionViewItemProvider: this.actionItemProvider,
|
actionViewItemProvider: this.actionViewItemProvider
|
||||||
actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection())
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return { resourceLabel, icon, actionBar, aligner: new Aligner(container, tree, this.themeService) };
|
return { resourceLabel, icon, actionBar, container, elementDisposable: Disposable.None };
|
||||||
}
|
}
|
||||||
|
|
||||||
renderElement(tree: ITree, node: ITreeItem, templateId: string, templateData: ITreeExplorerTemplateData): void {
|
renderElement(element: ITreeNode<ITreeItem, FuzzyScore>, index: number, templateData: ITreeExplorerTemplateData): void {
|
||||||
|
templateData.elementDisposable.dispose();
|
||||||
|
const node = element.element;
|
||||||
const resource = node.resourceUri ? URI.revive(node.resourceUri) : null;
|
const resource = node.resourceUri ? URI.revive(node.resourceUri) : null;
|
||||||
const treeItemLabel: ITreeItemLabel = node.label ? node.label : resource ? { label: basename(resource.path) } : void 0;
|
const treeItemLabel: ITreeItemLabel | undefined = node.label ? node.label : resource ? { label: basename(resource) } : undefined;
|
||||||
const description = isString(node.description) ? node.description : resource && node.description === true ? this.labelService.getUriLabel(dirname(resource), { relative: true }) : void 0;
|
const description = isString(node.description) ? node.description : resource && node.description === true ? this.labelService.getUriLabel(dirname(resource), { relative: true }) : undefined;
|
||||||
const label = treeItemLabel ? treeItemLabel.label : void 0;
|
const label = treeItemLabel ? treeItemLabel.label : undefined;
|
||||||
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 icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark;
|
||||||
const iconUrl = icon ? URI.revive(icon) : null;
|
const iconUrl = icon ? URI.revive(icon) : null;
|
||||||
const title = node.tooltip ? node.tooltip : resource ? void 0 : label;
|
const title = node.tooltip ? node.tooltip : resource ? undefined : label;
|
||||||
const sqlIcon = node.sqlIcon;
|
const sqlIcon = node.sqlIcon;
|
||||||
|
|
||||||
// reset
|
// reset
|
||||||
templateData.resourceLabel.clear();
|
|
||||||
templateData.actionBar.clear();
|
templateData.actionBar.clear();
|
||||||
|
|
||||||
if (resource || node.themeIcon) {
|
if (resource || node.themeIcon) {
|
||||||
const fileDecorations = this.configurationService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations');
|
const fileDecorations = this.configurationService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations');
|
||||||
templateData.resourceLabel.element.setResource({ name: label, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, fileDecorations: fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches });
|
templateData.resourceLabel.setResource({ 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: createMatches(element.filterData) });
|
||||||
} else {
|
} else {
|
||||||
templateData.resourceLabel.element.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches });
|
templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: createMatches(element.filterData) });
|
||||||
}
|
}
|
||||||
// clear out icons to prevent duplication from other templates
|
|
||||||
templateData.icon.className = '';
|
templateData.icon.className = '';
|
||||||
templateData.icon.style.backgroundImage = iconUrl ? `url('${iconUrl.toString(true)}')` : '';
|
templateData.icon.style.backgroundImage = iconUrl ? `url('${DOM.asDomUri(iconUrl).toString(true)}')` : '';
|
||||||
DOM.toggleClass(templateData.icon, sqlIcon, !!sqlIcon);
|
DOM.toggleClass(templateData.icon, sqlIcon, !!sqlIcon);
|
||||||
DOM.toggleClass(templateData.icon, 'icon', !!sqlIcon);
|
DOM.toggleClass(templateData.icon, 'icon', !!sqlIcon);
|
||||||
DOM.toggleClass(templateData.icon, 'custom-view-tree-node-item-icon', !!iconUrl || !!sqlIcon);
|
DOM.toggleClass(templateData.icon, 'custom-view-tree-node-item-icon', !!iconUrl || !!sqlIcon);
|
||||||
templateData.actionBar.context = (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle });
|
templateData.actionBar.context = (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle });
|
||||||
templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });
|
templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });
|
||||||
|
this.setAlignment(templateData.container, node);
|
||||||
|
templateData.elementDisposable = (this.themeService.onDidFileIconThemeChange(() => this.setAlignment(templateData.container, node)));
|
||||||
|
}
|
||||||
|
|
||||||
templateData.aligner.treeItem = node;
|
private setAlignment(container: HTMLElement, treeItem: ITreeItem) {
|
||||||
|
DOM.toggleClass(container.parentElement!, 'align-icon-with-twisty', this.aligner.alignIconWithTwisty(treeItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFileKind(node: ITreeItem): FileKind {
|
private getFileKind(node: ITreeItem): FileKind {
|
||||||
@@ -675,50 +805,41 @@ class TreeRenderer implements IRenderer {
|
|||||||
return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE;
|
return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE;
|
||||||
}
|
}
|
||||||
|
|
||||||
disposeTemplate(tree: ITree, templateId: string, templateData: ITreeExplorerTemplateData): void {
|
disposeElement(resource: ITreeNode<ITreeItem, FuzzyScore>, index: number, templateData: ITreeExplorerTemplateData): void {
|
||||||
|
templateData.elementDisposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
disposeTemplate(templateData: ITreeExplorerTemplateData): void {
|
||||||
templateData.resourceLabel.dispose();
|
templateData.resourceLabel.dispose();
|
||||||
templateData.actionBar.dispose();
|
templateData.actionBar.dispose();
|
||||||
templateData.aligner.dispose();
|
templateData.elementDisposable.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Aligner extends Disposable {
|
class Aligner extends Disposable {
|
||||||
|
private _tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>;
|
||||||
|
|
||||||
private _treeItem: ITreeItem;
|
constructor(private themeService: IWorkbenchThemeService) {
|
||||||
|
|
||||||
constructor(
|
|
||||||
private container: HTMLElement,
|
|
||||||
private tree: ITree,
|
|
||||||
private themeService: IWorkbenchThemeService
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
this._register(this.themeService.onDidFileIconThemeChange(() => this.render()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set treeItem(treeItem: ITreeItem) {
|
set tree(tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>) {
|
||||||
this._treeItem = treeItem;
|
this._tree = tree;
|
||||||
this.render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private render(): void {
|
public alignIconWithTwisty(treeItem: ITreeItem): boolean {
|
||||||
if (this._treeItem) {
|
if (treeItem.collapsibleState !== TreeItemCollapsibleState.None) {
|
||||||
DOM.toggleClass(this.container, 'align-icon-with-twisty', this.hasToAlignIconWithTwisty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private hasToAlignIconWithTwisty(): boolean {
|
|
||||||
if (this._treeItem.collapsibleState !== TreeItemCollapsibleState.None) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!this.hasIcon(this._treeItem)) {
|
if (!this.hasIcon(treeItem)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
const parent: ITreeItem = this.tree.getNavigator(this._treeItem).parent() || this.tree.getInput();
|
|
||||||
|
const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput();
|
||||||
if (this.hasIcon(parent)) {
|
if (this.hasIcon(parent)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c));
|
return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasIcon(node: ITreeItem): boolean {
|
private hasIcon(node: ITreeItem): boolean {
|
||||||
@@ -738,64 +859,9 @@ class Aligner extends Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TreeController extends WorkbenchTreeController {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private treeViewId: string,
|
|
||||||
private containerId: 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: () => actions,
|
|
||||||
|
|
||||||
getActionViewItem: (action) => {
|
|
||||||
const keybinding = this._keybindingService.lookupKeybinding(action.id);
|
|
||||||
if (keybinding) {
|
|
||||||
return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() });
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
onHide: (wasCancelled?: boolean) => {
|
|
||||||
if (wasCancelled) {
|
|
||||||
tree.domFocus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getActionsContext: () => (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle, $treeItem: node, $treeContainerId: this.containerId }),
|
|
||||||
|
|
||||||
actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection())
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MultipleSelectionActionRunner extends ActionRunner {
|
class MultipleSelectionActionRunner extends ActionRunner {
|
||||||
|
|
||||||
constructor(private getSelectedResources: () => any[]) {
|
constructor(private getSelectedResources: (() => any[])) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,9 +885,9 @@ class TreeMenus extends Disposable implements IDisposable {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private id: string,
|
private id: string,
|
||||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||||
@IMenuService private menuService: IMenuService,
|
@IMenuService private readonly menuService: IMenuService,
|
||||||
@IContextMenuService private contextMenuService: IContextMenuService
|
@IContextMenuService private readonly contextMenuService: IContextMenuService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -889,11 +955,11 @@ class MarkdownRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render(markdown: IMarkdownString): IMarkdownRenderResult {
|
render(markdown: IMarkdownString): IMarkdownRenderResult {
|
||||||
let disposeables = new DisposableStore();
|
const disposeables = new DisposableStore();
|
||||||
const element: HTMLElement = markdown ? renderMarkdown(markdown, this.getOptions(disposeables)) : document.createElement('span');
|
const element: HTMLElement = markdown ? renderMarkdown(markdown, this.getOptions(disposeables)) : document.createElement('span');
|
||||||
return {
|
return {
|
||||||
element,
|
element,
|
||||||
dispose: () => dispose(disposeables)
|
dispose: () => disposeables.dispose()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export interface ITreeItem extends vsITreeItem {
|
|||||||
|
|
||||||
export interface ITreeView extends vsITreeView {
|
export interface ITreeView extends vsITreeView {
|
||||||
|
|
||||||
collapse(itemOrItems: ITreeItem | ITreeItem[]): Thenable<void>;
|
collapse(itemOrItems: ITreeItem): boolean;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class DataExplorerContainerExtensionHandler implements IWorkbenchContribution {
|
|||||||
when: ContextKeyExpr.deserialize(item.when),
|
when: ContextKeyExpr.deserialize(item.when),
|
||||||
canToggleVisibility: true,
|
canToggleVisibility: true,
|
||||||
collapsed: this.showCollapsed(container),
|
collapsed: this.showCollapsed(container),
|
||||||
treeView: this.instantiationService.createInstance(CustomTreeView, item.id, container)
|
treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name, container)
|
||||||
};
|
};
|
||||||
|
|
||||||
viewIds.push(viewDescriptor.id);
|
viewIds.push(viewDescriptor.id);
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ CommandsRegistry.registerCommand({
|
|||||||
return oeService.disconnectNode(args.$treeViewId, args.$treeItem).then(() => {
|
return oeService.disconnectNode(args.$treeViewId, args.$treeItem).then(() => {
|
||||||
const { treeView } = (<ICustomViewDescriptor>Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getView(args.$treeViewId));
|
const { treeView } = (<ICustomViewDescriptor>Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getView(args.$treeViewId));
|
||||||
// we need to collapse it then refresh it so that the tree doesn't try and use it's cache next time the user expands the node
|
// we need to collapse it then refresh it so that the tree doesn't try and use it's cache next time the user expands the node
|
||||||
return treeView.collapse(args.$treeItem).then(() => treeView.refresh([args.$treeItem]).then(() => true));
|
treeView.collapse(args.$treeItem);
|
||||||
|
treeView.refresh([args.$treeItem]).then(() => true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user