Path: blob/main/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dom from '../../dom.js';6import type { IManagedHover } from '../hover/hover.js';7import { IHoverDelegate } from '../hover/hoverDelegate.js';8import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js';9import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js';10import { renderLabelWithIcons } from '../iconLabel/iconLabels.js';11import { Disposable } from '../../../common/lifecycle.js';12import * as objects from '../../../common/objects.js';1314/**15* A range to be highlighted.16*/17export interface IHighlight {18start: number;19end: number;20readonly extraClasses?: readonly string[];21}2223export interface IHighlightedLabelOptions {2425/**26* Whether the label supports rendering icons.27*/28readonly supportIcons?: boolean;2930readonly hoverDelegate?: IHoverDelegate;31}3233/**34* A widget which can render a label with substring highlights, often35* originating from a filter function like the fuzzy matcher.36*/37export class HighlightedLabel extends Disposable {3839private readonly domNode: HTMLElement;40private text: string = '';41private title: string = '';42private highlights: readonly IHighlight[] = [];43private supportIcons: boolean;44private didEverRender: boolean = false;45private customHover: IManagedHover | undefined;4647/**48* Create a new {@link HighlightedLabel}.49*50* @param container The parent container to append to.51*/52constructor(container: HTMLElement, private readonly options?: IHighlightedLabelOptions) {53super();5455this.supportIcons = options?.supportIcons ?? false;56this.domNode = dom.append(container, dom.$('span.monaco-highlighted-label'));57}5859/**60* The label's DOM node.61*/62get element(): HTMLElement {63return this.domNode;64}6566/**67* Set the label and highlights.68*69* @param text The label to display.70* @param highlights The ranges to highlight.71* @param title An optional title for the hover tooltip.72* @param escapeNewLines Whether to escape new lines.73* @returns74*/75set(text: string | undefined, highlights: readonly IHighlight[] = [], title: string = '', escapeNewLines?: boolean) {76if (!text) {77text = '';78}7980if (escapeNewLines) {81// adjusts highlights inplace82text = HighlightedLabel.escapeNewLines(text, highlights);83}8485if (this.didEverRender && this.text === text && this.title === title && objects.equals(this.highlights, highlights)) {86return;87}8889this.text = text;90this.title = title;91this.highlights = highlights;92this.render();93}9495private render(): void {9697const children: Array<HTMLSpanElement | string> = [];98let pos = 0;99100for (const highlight of this.highlights) {101if (highlight.end === highlight.start) {102continue;103}104105if (pos < highlight.start) {106const substring = this.text.substring(pos, highlight.start);107if (this.supportIcons) {108children.push(...renderLabelWithIcons(substring));109} else {110children.push(substring);111}112pos = highlight.start;113}114115const substring = this.text.substring(pos, highlight.end);116const element = dom.$('span.highlight', undefined, ...this.supportIcons ? renderLabelWithIcons(substring) : [substring]);117118if (highlight.extraClasses) {119element.classList.add(...highlight.extraClasses);120}121122children.push(element);123pos = highlight.end;124}125126if (pos < this.text.length) {127const substring = this.text.substring(pos,);128if (this.supportIcons) {129children.push(...renderLabelWithIcons(substring));130} else {131children.push(substring);132}133}134135dom.reset(this.domNode, ...children);136137if (!this.customHover && this.title !== '') {138const hoverDelegate = this.options?.hoverDelegate ?? getDefaultHoverDelegate('mouse');139this.customHover = this._register(getBaseLayerHoverDelegate().setupManagedHover(hoverDelegate, this.domNode, this.title));140} else if (this.customHover) {141this.customHover.update(this.title);142}143144this.didEverRender = true;145}146147static escapeNewLines(text: string, highlights: readonly IHighlight[]): string {148let total = 0;149let extra = 0;150151return text.replace(/\r\n|\r|\n/g, (match, offset) => {152extra = match === '\r\n' ? -1 : 0;153offset += total;154155for (const highlight of highlights) {156if (highlight.end <= offset) {157continue;158}159if (highlight.start >= offset) {160highlight.start += extra;161}162if (highlight.end >= offset) {163highlight.end += extra;164}165}166167total += extra;168return '\u23CE';169});170}171}172173174