Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as dom from '../../dom.js';
7
import type { IManagedHover } from '../hover/hover.js';
8
import { IHoverDelegate } from '../hover/hoverDelegate.js';
9
import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js';
10
import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js';
11
import { renderLabelWithIcons } from '../iconLabel/iconLabels.js';
12
import { Disposable } from '../../../common/lifecycle.js';
13
import * as objects from '../../../common/objects.js';
14
15
/**
16
* A range to be highlighted.
17
*/
18
export interface IHighlight {
19
start: number;
20
end: number;
21
readonly extraClasses?: readonly string[];
22
}
23
24
export interface IHighlightedLabelOptions {
25
26
/**
27
* Whether the label supports rendering icons.
28
*/
29
readonly supportIcons?: boolean;
30
31
readonly hoverDelegate?: IHoverDelegate;
32
}
33
34
/**
35
* A widget which can render a label with substring highlights, often
36
* originating from a filter function like the fuzzy matcher.
37
*/
38
export class HighlightedLabel extends Disposable {
39
40
private readonly domNode: HTMLElement;
41
private text: string = '';
42
private title: string = '';
43
private highlights: readonly IHighlight[] = [];
44
private supportIcons: boolean;
45
private didEverRender: boolean = false;
46
private customHover: IManagedHover | undefined;
47
48
/**
49
* Create a new {@link HighlightedLabel}.
50
*
51
* @param container The parent container to append to.
52
*/
53
constructor(container: HTMLElement, private readonly options?: IHighlightedLabelOptions) {
54
super();
55
56
this.supportIcons = options?.supportIcons ?? false;
57
this.domNode = dom.append(container, dom.$('span.monaco-highlighted-label'));
58
}
59
60
/**
61
* The label's DOM node.
62
*/
63
get element(): HTMLElement {
64
return this.domNode;
65
}
66
67
/**
68
* Set the label and highlights.
69
*
70
* @param text The label to display.
71
* @param highlights The ranges to highlight.
72
* @param title An optional title for the hover tooltip.
73
* @param escapeNewLines Whether to escape new lines.
74
* @returns
75
*/
76
set(text: string | undefined, highlights: readonly IHighlight[] = [], title: string = '', escapeNewLines?: boolean) {
77
if (!text) {
78
text = '';
79
}
80
81
if (escapeNewLines) {
82
// adjusts highlights inplace
83
text = HighlightedLabel.escapeNewLines(text, highlights);
84
}
85
86
if (this.didEverRender && this.text === text && this.title === title && objects.equals(this.highlights, highlights)) {
87
return;
88
}
89
90
this.text = text;
91
this.title = title;
92
this.highlights = highlights;
93
this.render();
94
}
95
96
private render(): void {
97
98
const children: Array<HTMLSpanElement | string> = [];
99
let pos = 0;
100
101
for (const highlight of this.highlights) {
102
if (highlight.end === highlight.start) {
103
continue;
104
}
105
106
if (pos < highlight.start) {
107
const substring = this.text.substring(pos, highlight.start);
108
if (this.supportIcons) {
109
children.push(...renderLabelWithIcons(substring));
110
} else {
111
children.push(substring);
112
}
113
pos = highlight.start;
114
}
115
116
const substring = this.text.substring(pos, highlight.end);
117
const element = dom.$('span.highlight', undefined, ...this.supportIcons ? renderLabelWithIcons(substring) : [substring]);
118
119
if (highlight.extraClasses) {
120
element.classList.add(...highlight.extraClasses);
121
}
122
123
children.push(element);
124
pos = highlight.end;
125
}
126
127
if (pos < this.text.length) {
128
const substring = this.text.substring(pos,);
129
if (this.supportIcons) {
130
children.push(...renderLabelWithIcons(substring));
131
} else {
132
children.push(substring);
133
}
134
}
135
136
dom.reset(this.domNode, ...children);
137
138
if (!this.customHover && this.title !== '') {
139
const hoverDelegate = this.options?.hoverDelegate ?? getDefaultHoverDelegate('mouse');
140
this.customHover = this._register(getBaseLayerHoverDelegate().setupManagedHover(hoverDelegate, this.domNode, this.title));
141
} else if (this.customHover) {
142
this.customHover.update(this.title);
143
}
144
145
this.didEverRender = true;
146
}
147
148
static escapeNewLines(text: string, highlights: readonly IHighlight[]): string {
149
let total = 0;
150
let extra = 0;
151
152
return text.replace(/\r\n|\r|\n/g, (match, offset) => {
153
extra = match === '\r\n' ? -1 : 0;
154
offset += total;
155
156
for (const highlight of highlights) {
157
if (highlight.end <= offset) {
158
continue;
159
}
160
if (highlight.start >= offset) {
161
highlight.start += extra;
162
}
163
if (highlight.end >= offset) {
164
highlight.end += extra;
165
}
166
}
167
168
total += extra;
169
return '\u23CE';
170
});
171
}
172
}
173
174