#61312 Implement message for tree views

This commit is contained in:
Sandeep Somavarapu 2018-11-16 14:57:03 +01:00
parent 759fec1680
commit a8838578af
7 changed files with 156 additions and 28 deletions

View file

@ -1187,6 +1187,15 @@ declare module 'vscode' {
//#region Tree View
export interface TreeView<T> extends Disposable {
/**
* An optional human-readable message that will be rendered in the view.
*/
message?: string | MarkdownString;
}
/**
* Label describing the [Tree item](#TreeItem)
*/

View file

@ -10,6 +10,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostC
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';
@extHostNamedCustomer(MainContext.MainThreadTreeViews)
export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape {
@ -58,6 +59,13 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
return null;
}
$setMessage(treeViewId: string, message: string | IMarkdownString): void {
const viewer = this.getTreeView(treeViewId);
if (viewer) {
viewer.message = message;
}
}
private async reveal(treeView: ITreeView, dataProvider: TreeViewDataProvider, item: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise<void> {
options = options ? options : { select: false, focus: false };
const select = isUndefinedOrNull(options.select) ? false : options.select;

View file

@ -42,6 +42,7 @@ import { IRPCProtocol, ProxyIdentifier, createExtHostContextProxyIdentifier as c
import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress';
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
import * as vscode from 'vscode';
import { IMarkdownString } from 'vs/base/common/htmlContent';
export interface IEnvironment {
isExtensionDevelopmentDebug: boolean;
@ -215,6 +216,7 @@ export interface MainThreadTreeViewsShape extends IDisposable {
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean }): void;
$refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Thenable<void>;
$reveal(treeViewId: string, treeItem: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Thenable<void>;
$setMessage(treeViewId: string, message: string | IMarkdownString): void;
}
export interface MainThreadErrorsShape extends IDisposable {

View file

@ -13,11 +13,12 @@ import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.proto
import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions } from 'vs/workbench/common/views';
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands';
import { asThenable } from 'vs/base/common/async';
import { TreeItemCollapsibleState, ThemeIcon } from 'vs/workbench/api/node/extHostTypes';
import { TreeItemCollapsibleState, ThemeIcon, MarkdownString } from 'vs/workbench/api/node/extHostTypes';
import { isUndefinedOrNull, isString } from 'vs/base/common/types';
import { equals } from 'vs/base/common/arrays';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtensionDescription, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import * as typeConvert from 'vs/workbench/api/node/extHostTypeConverters';
type TreeItemHandle = string;
@ -82,6 +83,8 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
get onDidChangeSelection() { return treeView.onDidChangeSelection; },
get visible() { return treeView.visible; },
get onDidChangeVisibility() { return treeView.onDidChangeVisibility; },
get message() { return treeView.message; },
set message(message: string | MarkdownString) { treeView.message = message; },
reveal: (element: T, options?: IRevealOptions): Thenable<void> => {
return treeView.reveal(element, options);
},
@ -225,6 +228,16 @@ class ExtHostTreeView<T> extends Disposable {
.then(treeNode => this.proxy.$reveal(this.viewId, treeNode.item, parentChain.map(p => p.item), { select, focus, expand })), error => this.logService.error(error));
}
private _message: string | MarkdownString;
get message(): string | MarkdownString {
return this._message;
}
set message(message: string | MarkdownString) {
this._message = message;
this.proxy.$setMessage(this.viewId, typeConvert.MarkdownString.fromStrict(this._message));
}
setExpanded(treeItemHandle: TreeItemHandle, expanded: boolean): void {
const element = this.getExtensionElement(treeItemHandle);
if (element) {

View file

@ -36,6 +36,12 @@ import { localize } from 'vs/nls';
import { timeout } from 'vs/base/common/async';
import { CollapseAllAction } from 'vs/base/parts/tree/browser/treeDefaults';
import { editorFindMatchHighlight, editorFindMatchHighlightBorder } 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';
export class CustomTreeViewPanel extends ViewletPanel {
@ -182,13 +188,15 @@ export class CustomTreeView extends Disposable implements ITreeView {
private domNode: HTMLElement;
private treeContainer: HTMLElement;
private message: HTMLDivElement;
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;
@ -217,7 +225,7 @@ export class CustomTreeView extends Disposable implements ITreeView {
) {
super();
this.root = new Root();
this.menus = this._register(this.instantiationService.createInstance(TitleMenus, this.id));
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 **/));
@ -226,10 +234,16 @@ 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;
}
@ -248,14 +262,24 @@ export class CustomTreeView extends Disposable implements ITreeView {
});
}
};
this.hideMessage();
this.updateMessage();
this.refresh();
} else {
this._dataProvider = null;
this.showMessage(noDataProviderMessage);
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 {
return this._hasIconForParentNode;
}
@ -346,9 +370,8 @@ export class CustomTreeView extends Disposable implements ITreeView {
}
private create() {
this.domNode = DOM.$('.tree-explorer-viewlet-tree-view.message');
this.message = DOM.append(this.domNode, DOM.$('.customview-message'));
this.message.innerText = noDataProviderMessage;
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'));
}
@ -367,19 +390,55 @@ export class CustomTreeView extends Disposable implements ITreeView {
this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.selection)));
}
private showMessage(message: string): void {
DOM.addClass(this.domNode, 'message');
this.message.innerText = message;
private updateMessage(): void {
if (this._message) {
this.showMessage(this._message);
} else if (!this.dataProvider) {
this.showMessage(noDataProviderMessage);
} else {
this.hideMessage();
}
}
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.innerText = this._messageValue;
} else {
this.markdownResult = this.markdownRenderer.render(this._messageValue);
DOM.append(this.messageElement, this.markdownResult.element);
}
this.layout(this._size);
}
}
private hideMessage(): void {
DOM.removeClass(this.domNode, 'message');
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) {
this.domNode.style.height = size + 'px';
if (this.tree) {
this.tree.layout(size);
if (size) {
this._size = size;
this.domNode.style.height = size + 'px';
const treeSize = size - DOM.getTotalHeight(this.messageElement);
this.treeContainer.style.height = treeSize + 'px';
if (this.tree) {
this.tree.layout(treeSize);
}
}
}
@ -426,15 +485,12 @@ export class CustomTreeView extends Disposable implements ITreeView {
}
private activate() {
this.hideMessage();
if (!this.activated) {
this.tree.setInput(this.root);
this.progressService.withProgress({ location: this.container.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`))
.then(() => timeout(2000))
.then(() => {
if (!this.dataProvider) {
this.showMessage(noDataProviderMessage);
}
this.updateMessage();
});
this.activated = true;
}
@ -772,3 +828,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

@ -50,17 +50,18 @@
display: none;
}
.monaco-workbench .tree-explorer-viewlet-tree-view .customview-message {
display: none;
padding: 10px 22px 0 22px;
opacity: 0.5;
.monaco-workbench .tree-explorer-viewlet-tree-view .message {
padding: 4px 0px 0px 18px;
user-select: text
}
.monaco-workbench .tree-explorer-viewlet-tree-view.message .customview-message {
display: inherit;
.monaco-workbench .tree-explorer-viewlet-tree-view .message p {
margin-top: 0px;
margin-bottom: 0px;
padding-bottom: 4px;
}
.monaco-workbench .tree-explorer-viewlet-tree-view.message .customview-tree {
.monaco-workbench .tree-explorer-viewlet-tree-view .message.hide {
display: none;
}

View file

@ -17,6 +17,7 @@ import { values } from 'vs/base/common/map';
import { Registry } from 'vs/platform/registry/common/platform';
import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IAction } from 'vs/base/common/actions';
import { IMarkdownString } from 'vs/base/common/htmlContent';
export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test';
@ -241,6 +242,8 @@ export interface ITreeView extends IDisposable {
showCollapseAllAction: boolean;
message: string | IMarkdownString;
readonly visible: boolean;
readonly onDidExpandItem: Event<ITreeItem>;