Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/common/iconLabels.ts
3292 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 { IMatch, matchesFuzzy } from './filters.js';
7
import { ltrim } from './strings.js';
8
import { ThemeIcon } from './themables.js';
9
10
const iconStartMarker = '$(';
11
12
const iconsRegex = new RegExp(`\\$\\(${ThemeIcon.iconNameExpression}(?:${ThemeIcon.iconModifierExpression})?\\)`, 'g'); // no capturing groups
13
14
const escapeIconsRegex = new RegExp(`(\\\\)?${iconsRegex.source}`, 'g');
15
export function escapeIcons(text: string): string {
16
return text.replace(escapeIconsRegex, (match, escaped) => escaped ? match : `\\${match}`);
17
}
18
19
const markdownEscapedIconsRegex = new RegExp(`\\\\${iconsRegex.source}`, 'g');
20
export function markdownEscapeEscapedIcons(text: string): string {
21
// Need to add an extra \ for escaping in markdown
22
return text.replace(markdownEscapedIconsRegex, match => `\\${match}`);
23
}
24
25
const stripIconsRegex = new RegExp(`(\\s)?(\\\\)?${iconsRegex.source}(\\s)?`, 'g');
26
27
/**
28
* Takes a label with icons (`$(iconId)xyz`) and strips the icons out (`xyz`)
29
*/
30
export function stripIcons(text: string): string {
31
if (text.indexOf(iconStartMarker) === -1) {
32
return text;
33
}
34
35
return text.replace(stripIconsRegex, (match, preWhitespace, escaped, postWhitespace) => escaped ? match : preWhitespace || postWhitespace || '');
36
}
37
38
39
/**
40
* Takes a label with icons (`$(iconId)xyz`), removes the icon syntax adds whitespace so that screen readers can read the text better.
41
*/
42
export function getCodiconAriaLabel(text: string | undefined) {
43
if (!text) {
44
return '';
45
}
46
47
return text.replace(/\$\((.*?)\)/g, (_match, codiconName) => ` ${codiconName} `).trim();
48
}
49
50
51
export interface IParsedLabelWithIcons {
52
readonly text: string;
53
readonly iconOffsets?: readonly number[];
54
}
55
56
const _parseIconsRegex = new RegExp(`\\$\\(${ThemeIcon.iconNameCharacter}+\\)`, 'g');
57
58
/**
59
* Takes a label with icons (`abc $(iconId)xyz`) and returns the text (`abc xyz`) and the offsets of the icons (`[3]`)
60
*/
61
export function parseLabelWithIcons(input: string): IParsedLabelWithIcons {
62
63
_parseIconsRegex.lastIndex = 0;
64
65
let text = '';
66
const iconOffsets: number[] = [];
67
let iconsOffset = 0;
68
69
while (true) {
70
const pos = _parseIconsRegex.lastIndex;
71
const match = _parseIconsRegex.exec(input);
72
73
const chars = input.substring(pos, match?.index);
74
if (chars.length > 0) {
75
text += chars;
76
for (let i = 0; i < chars.length; i++) {
77
iconOffsets.push(iconsOffset);
78
}
79
}
80
if (!match) {
81
break;
82
}
83
iconsOffset += match[0].length;
84
}
85
86
return { text, iconOffsets };
87
}
88
89
90
export function matchesFuzzyIconAware(query: string, target: IParsedLabelWithIcons, enableSeparateSubstringMatching = false): IMatch[] | null {
91
const { text, iconOffsets } = target;
92
93
// Return early if there are no icon markers in the word to match against
94
if (!iconOffsets || iconOffsets.length === 0) {
95
return matchesFuzzy(query, text, enableSeparateSubstringMatching);
96
}
97
98
// Trim the word to match against because it could have leading
99
// whitespace now if the word started with an icon
100
const wordToMatchAgainstWithoutIconsTrimmed = ltrim(text, ' ');
101
const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutIconsTrimmed.length;
102
103
// match on value without icon
104
const matches = matchesFuzzy(query, wordToMatchAgainstWithoutIconsTrimmed, enableSeparateSubstringMatching);
105
106
// Map matches back to offsets with icon and trimming
107
if (matches) {
108
for (const match of matches) {
109
const iconOffset = iconOffsets[match.start + leadingWhitespaceOffset] /* icon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */;
110
match.start += iconOffset;
111
match.end += iconOffset;
112
}
113
}
114
115
return matches;
116
}
117
118