Path: blob/main/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts
5257 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 {24readonly hoverDelegate?: IHoverDelegate;25}2627/**28* A widget which can render a label with substring highlights, often29* originating from a filter function like the fuzzy matcher.30*/31export class HighlightedLabel extends Disposable {3233private readonly domNode: HTMLElement;34private text: string = '';35private title: string = '';36private highlights: readonly IHighlight[] = [];37private didEverRender: boolean = false;38private customHover: IManagedHover | undefined;3940/**41* Create a new {@link HighlightedLabel}.42*43* @param container The parent container to append to.44*/45constructor(container: HTMLElement, private readonly options?: IHighlightedLabelOptions) {46super();4748this.domNode = dom.append(container, dom.$('span.monaco-highlighted-label'));49}5051/**52* The label's DOM node.53*/54get element(): HTMLElement {55return this.domNode;56}5758/**59* Set the label and highlights.60*61* @param text The label to display.62* @param highlights The ranges to highlight.63* @param title An optional title for the hover tooltip.64* @param escapeNewLines Whether to escape new lines.65* @returns66*/67set(text: string | undefined, highlights: readonly IHighlight[] = [], title: string = '', escapeNewLines?: boolean, supportIcons?: boolean) {68if (!text) {69text = '';70}7172if (escapeNewLines) {73// adjusts highlights inplace74text = HighlightedLabel.escapeNewLines(text, highlights);75}7677if (this.didEverRender && this.text === text && this.title === title && objects.equals(this.highlights, highlights)) {78return;79}8081this.text = text;82this.title = title;83this.highlights = highlights;84this.render(supportIcons);85}8687private render(supportIcons?: boolean): void {8889const children: Array<HTMLSpanElement | string> = [];90let pos = 0;9192for (const highlight of this.highlights) {93if (highlight.end === highlight.start) {94continue;95}9697if (pos < highlight.start) {98const substring = this.text.substring(pos, highlight.start);99if (supportIcons) {100children.push(...renderLabelWithIcons(substring));101} else {102children.push(substring);103}104pos = highlight.start;105}106107const substring = this.text.substring(pos, highlight.end);108const element = dom.$('span.highlight', undefined, ...supportIcons ? renderLabelWithIcons(substring) : [substring]);109110if (highlight.extraClasses) {111element.classList.add(...highlight.extraClasses);112}113114children.push(element);115pos = highlight.end;116}117118if (pos < this.text.length) {119const substring = this.text.substring(pos,);120if (supportIcons) {121children.push(...renderLabelWithIcons(substring));122} else {123children.push(substring);124}125}126127dom.reset(this.domNode, ...children);128129if (!this.customHover && this.title !== '') {130const hoverDelegate = this.options?.hoverDelegate ?? getDefaultHoverDelegate('mouse');131this.customHover = this._register(getBaseLayerHoverDelegate().setupManagedHover(hoverDelegate, this.domNode, this.title));132} else if (this.customHover) {133this.customHover.update(this.title);134}135136this.didEverRender = true;137}138139static escapeNewLines(text: string, highlights: readonly IHighlight[]): string {140let total = 0;141let extra = 0;142143return text.replace(/\r\n|\r|\n/g, (match, offset) => {144extra = match === '\r\n' ? -1 : 0;145offset += total;146147for (const highlight of highlights) {148if (highlight.end <= offset) {149continue;150}151if (highlight.start >= offset) {152highlight.start += extra;153}154if (highlight.end >= offset) {155highlight.end += extra;156}157}158159total += extra;160return '\u23CE';161});162}163}164165166