Archived
1
0

Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@ -0,0 +1,350 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./iconlabel';
import * as dom from 'vs/base/browser/dom';
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { IMatch } from 'vs/base/common/filters';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Range } from 'vs/base/common/range';
import { equals } from 'vs/base/common/objects';
import { isMacintosh } from 'vs/base/common/platform';
import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { isString } from 'vs/base/common/types';
import { domEvent } from 'vs/base/browser/event';
export interface IIconLabelCreationOptions {
supportHighlights?: boolean;
supportDescriptionHighlights?: boolean;
supportCodicons?: boolean;
hoverDelegate?: IHoverDelegate;
}
export interface IIconLabelValueOptions {
title?: string | IMarkdownString | Promise<IMarkdownString | string | undefined>;
descriptionTitle?: string;
hideIcon?: boolean;
extraClasses?: string[];
italic?: boolean;
strikethrough?: boolean;
matches?: IMatch[];
labelEscapeNewLines?: boolean;
descriptionMatches?: IMatch[];
readonly separator?: string;
readonly domId?: string;
}
class FastLabelNode {
private disposed: boolean | undefined;
private _textContent: string | undefined;
private _className: string | undefined;
private _empty: boolean | undefined;
constructor(private _element: HTMLElement) {
}
get element(): HTMLElement {
return this._element;
}
set textContent(content: string) {
if (this.disposed || content === this._textContent) {
return;
}
this._textContent = content;
this._element.textContent = content;
}
set className(className: string) {
if (this.disposed || className === this._className) {
return;
}
this._className = className;
this._element.className = className;
}
set empty(empty: boolean) {
if (this.disposed || empty === this._empty) {
return;
}
this._empty = empty;
this._element.style.marginLeft = empty ? '0' : '';
}
dispose(): void {
this.disposed = true;
}
}
export class IconLabel extends Disposable {
private domNode: FastLabelNode;
private nameNode: Label | LabelWithHighlights;
private descriptionContainer: FastLabelNode;
private descriptionNode: FastLabelNode | HighlightedLabel | undefined;
private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
private hoverDelegate: IHoverDelegate | undefined = undefined;
private readonly customHovers: Map<HTMLElement, IDisposable> = new Map();
constructor(container: HTMLElement, options?: IIconLabelCreationOptions) {
super();
this.domNode = this._register(new FastLabelNode(dom.append(container, dom.$('.monaco-icon-label'))));
const labelContainer = dom.append(this.domNode.element, dom.$('.monaco-icon-label-container'));
const nameContainer = dom.append(labelContainer, dom.$('span.monaco-icon-name-container'));
this.descriptionContainer = this._register(new FastLabelNode(dom.append(labelContainer, dom.$('span.monaco-icon-description-container'))));
if (options?.supportHighlights) {
this.nameNode = new LabelWithHighlights(nameContainer, !!options.supportCodicons);
} else {
this.nameNode = new Label(nameContainer);
}
if (options?.supportDescriptionHighlights) {
this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.descriptionContainer.element, dom.$('span.label-description')), !!options.supportCodicons);
} else {
this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.descriptionContainer.element, dom.$('span.label-description'))));
}
if (options?.hoverDelegate) {
this.hoverDelegate = options.hoverDelegate;
}
}
get element(): HTMLElement {
return this.domNode.element;
}
setLabel(label: string | string[], description?: string, options?: IIconLabelValueOptions): void {
const classes = ['monaco-icon-label'];
if (options) {
if (options.extraClasses) {
classes.push(...options.extraClasses);
}
if (options.italic) {
classes.push('italic');
}
if (options.strikethrough) {
classes.push('strikethrough');
}
}
this.domNode.className = classes.join(' ');
this.setupHover(this.domNode.element, options?.title);
this.nameNode.setLabel(label, options);
if (description || this.descriptionNode) {
if (!this.descriptionNode) {
this.descriptionNode = this.descriptionNodeFactory(); // description node is created lazily on demand
}
if (this.descriptionNode instanceof HighlightedLabel) {
this.descriptionNode.set(description || '', options ? options.descriptionMatches : undefined);
this.setupHover(this.descriptionNode.element, options?.descriptionTitle);
} else {
this.descriptionNode.textContent = description || '';
this.setupHover(this.descriptionNode.element, options?.descriptionTitle || '');
this.descriptionNode.empty = !description;
}
}
}
private setupHover(htmlElement: HTMLElement, tooltip: string | IMarkdownString | Promise<IMarkdownString | string | undefined> | undefined): void {
const previousCustomHover = this.customHovers.get(htmlElement);
if (previousCustomHover) {
previousCustomHover.dispose();
this.customHovers.delete(htmlElement);
}
if (!tooltip) {
htmlElement.removeAttribute('title');
return;
}
if (!this.hoverDelegate) {
return this.setupNativeHover(htmlElement, tooltip);
} else {
return this.setupCustomHover(this.hoverDelegate, htmlElement, tooltip);
}
}
private setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, tooltip: string | IMarkdownString | Promise<IMarkdownString | string | undefined> | undefined): void {
htmlElement.removeAttribute('title');
// Testing has indicated that on Windows and Linux 500 ms matches the native hovers most closely.
// On Mac, the delay is 1500.
const hoverDelay = isMacintosh ? 1500 : 500;
let hoverOptions: IHoverDelegateOptions | undefined;
let mouseX: number | undefined;
function mouseOver(this: HTMLElement, e: MouseEvent): any {
let isHovering = true;
function mouseMove(this: HTMLElement, e: MouseEvent): any {
mouseX = e.x;
}
function mouseLeave(this: HTMLElement, e: MouseEvent): any {
isHovering = false;
}
const mouseLeaveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_LEAVE, true)(mouseLeave.bind(htmlElement));
const mouseMoveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_MOVE, true)(mouseMove.bind(htmlElement));
setTimeout(async () => {
if (isHovering && tooltip) {
// Re-use the already computed hover options if they exist.
if (!hoverOptions) {
const target: IHoverDelegateTarget = {
targetElements: [this],
dispose: () => { }
};
const resolvedTooltip = await tooltip;
if (resolvedTooltip) {
hoverOptions = {
text: resolvedTooltip,
target,
anchorPosition: AnchorPosition.BELOW
};
}
}
if (hoverOptions) {
if (mouseX !== undefined) {
(<IHoverDelegateTarget>hoverOptions.target).x = mouseX + 10;
}
hoverDelegate.showHover(hoverOptions);
}
}
mouseMoveDisposable.dispose();
mouseLeaveDisposable.dispose();
}, hoverDelay);
}
const mouseOverDisposable = this._register(domEvent(htmlElement, dom.EventType.MOUSE_OVER, true)(mouseOver.bind(htmlElement)));
this.customHovers.set(htmlElement, mouseOverDisposable);
}
private setupNativeHover(htmlElement: HTMLElement, tooltip: string | IMarkdownString | Promise<IMarkdownString | string | undefined> | undefined): void {
htmlElement.title = isString(tooltip) ? tooltip : '';
}
}
class Label {
private label: string | string[] | undefined = undefined;
private singleLabel: HTMLElement | undefined = undefined;
private options: IIconLabelValueOptions | undefined;
constructor(private container: HTMLElement) { }
setLabel(label: string | string[], options?: IIconLabelValueOptions): void {
if (this.label === label && equals(this.options, options)) {
return;
}
this.label = label;
this.options = options;
if (typeof label === 'string') {
if (!this.singleLabel) {
this.container.innerText = '';
this.container.classList.remove('multiple');
this.singleLabel = dom.append(this.container, dom.$('a.label-name', { id: options?.domId }));
}
this.singleLabel.textContent = label;
} else {
this.container.innerText = '';
this.container.classList.add('multiple');
this.singleLabel = undefined;
for (let i = 0; i < label.length; i++) {
const l = label[i];
const id = options?.domId && `${options?.domId}_${i}`;
dom.append(this.container, dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i, 'role': 'treeitem' }, l));
if (i < label.length - 1) {
dom.append(this.container, dom.$('span.label-separator', undefined, options?.separator || '/'));
}
}
}
}
}
function splitMatches(labels: string[], separator: string, matches: IMatch[] | undefined): IMatch[][] | undefined {
if (!matches) {
return undefined;
}
let labelStart = 0;
return labels.map(label => {
const labelRange = { start: labelStart, end: labelStart + label.length };
const result = matches
.map(match => Range.intersect(labelRange, match))
.filter(range => !Range.isEmpty(range))
.map(({ start, end }) => ({ start: start - labelStart, end: end - labelStart }));
labelStart = labelRange.end + separator.length;
return result;
});
}
class LabelWithHighlights {
private label: string | string[] | undefined = undefined;
private singleLabel: HighlightedLabel | undefined = undefined;
private options: IIconLabelValueOptions | undefined;
constructor(private container: HTMLElement, private supportCodicons: boolean) { }
setLabel(label: string | string[], options?: IIconLabelValueOptions): void {
if (this.label === label && equals(this.options, options)) {
return;
}
this.label = label;
this.options = options;
if (typeof label === 'string') {
if (!this.singleLabel) {
this.container.innerText = '';
this.container.classList.remove('multiple');
this.singleLabel = new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), this.supportCodicons);
}
this.singleLabel.set(label, options?.matches, undefined, options?.labelEscapeNewLines);
} else {
this.container.innerText = '';
this.container.classList.add('multiple');
this.singleLabel = undefined;
const separator = options?.separator || '/';
const matches = splitMatches(label, separator, options?.matches);
for (let i = 0; i < label.length; i++) {
const l = label[i];
const m = matches ? matches[i] : undefined;
const id = options?.domId && `${options?.domId}_${i}`;
const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i, 'role': 'treeitem' });
const highlightedLabel = new HighlightedLabel(dom.append(this.container, name), this.supportCodicons);
highlightedLabel.set(l, m, undefined, options?.labelEscapeNewLines);
if (i < label.length - 1) {
dom.append(name, dom.$('span.label-separator', undefined, separator));
}
}
}
}
}