mirror of
https://github.com/Microsoft/vscode
synced 2024-08-28 05:19:39 +00:00
#61312 Implement message for tree views
This commit is contained in:
parent
759fec1680
commit
a8838578af
9
src/vs/vscode.proposed.d.ts
vendored
9
src/vs/vscode.proposed.d.ts
vendored
|
@ -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)
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>;
|
||||
|
|
Loading…
Reference in a new issue