2023-05-16 10:47:08 +08:00

122 lines
5.3 KiB
JavaScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CSSIcon } from './codicons.js';
import { matchesFuzzy } from './filters.js';
import { ltrim } from './strings.js';
export const iconStartMarker = '$(';
const iconsRegex = new RegExp(`\\$\\(${CSSIcon.iconNameExpression}(?:${CSSIcon.iconModifierExpression})?\\)`, 'g'); // no capturing groups
const iconNameCharacterRegexp = new RegExp(CSSIcon.iconNameCharacter);
const escapeIconsRegex = new RegExp(`(\\\\)?${iconsRegex.source}`, 'g');
export function escapeIcons(text) {
return text.replace(escapeIconsRegex, (match, escaped) => escaped ? match : `\\${match}`);
}
const markdownEscapedIconsRegex = new RegExp(`\\\\${iconsRegex.source}`, 'g');
export function markdownEscapeEscapedIcons(text) {
// Need to add an extra \ for escaping in markdown
return text.replace(markdownEscapedIconsRegex, match => `\\${match}`);
}
const stripIconsRegex = new RegExp(`(\\s)?(\\\\)?${iconsRegex.source}(\\s)?`, 'g');
export function stripIcons(text) {
if (text.indexOf(iconStartMarker) === -1) {
return text;
}
return text.replace(stripIconsRegex, (match, preWhitespace, escaped, postWhitespace) => escaped ? match : preWhitespace || postWhitespace || '');
}
export function parseLabelWithIcons(text) {
const firstIconIndex = text.indexOf(iconStartMarker);
if (firstIconIndex === -1) {
return { text }; // return early if the word does not include an icon
}
return doParseLabelWithIcons(text, firstIconIndex);
}
function doParseLabelWithIcons(text, firstIconIndex) {
const iconOffsets = [];
let textWithoutIcons = '';
function appendChars(chars) {
if (chars) {
textWithoutIcons += chars;
for (const _ of chars) {
iconOffsets.push(iconsOffset); // make sure to fill in icon offsets
}
}
}
let currentIconStart = -1;
let currentIconValue = '';
let iconsOffset = 0;
let char;
let nextChar;
let offset = firstIconIndex;
const length = text.length;
// Append all characters until the first icon
appendChars(text.substr(0, firstIconIndex));
// example: $(file-symlink-file) my cool $(other-icon) entry
while (offset < length) {
char = text[offset];
nextChar = text[offset + 1];
// beginning of icon: some value $( <--
if (char === iconStartMarker[0] && nextChar === iconStartMarker[1]) {
currentIconStart = offset;
// if we had a previous potential icon value without
// the closing ')', it was actually not an icon and
// so we have to add it to the actual value
appendChars(currentIconValue);
currentIconValue = iconStartMarker;
offset++; // jump over '('
}
// end of icon: some value $(some-icon) <--
else if (char === ')' && currentIconStart !== -1) {
const currentIconLength = offset - currentIconStart + 1; // +1 to include the closing ')'
iconsOffset += currentIconLength;
currentIconStart = -1;
currentIconValue = '';
}
// within icon
else if (currentIconStart !== -1) {
// Make sure this is a real icon name
if (iconNameCharacterRegexp.test(char)) {
currentIconValue += char;
}
else {
// This is not a real icon, treat it as text
appendChars(currentIconValue);
currentIconStart = -1;
currentIconValue = '';
}
}
// any value outside of icon
else {
appendChars(char);
}
offset++;
}
// if we had a previous potential icon value without
// the closing ')', it was actually not an icon and
// so we have to add it to the actual value
appendChars(currentIconValue);
return { text: textWithoutIcons, iconOffsets };
}
export function matchesFuzzyIconAware(query, target, enableSeparateSubstringMatching = false) {
const { text, iconOffsets } = target;
// Return early if there are no icon markers in the word to match against
if (!iconOffsets || iconOffsets.length === 0) {
return matchesFuzzy(query, text, enableSeparateSubstringMatching);
}
// Trim the word to match against because it could have leading
// whitespace now if the word started with an icon
const wordToMatchAgainstWithoutIconsTrimmed = ltrim(text, ' ');
const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutIconsTrimmed.length;
// match on value without icon
const matches = matchesFuzzy(query, wordToMatchAgainstWithoutIconsTrimmed, enableSeparateSubstringMatching);
// Map matches back to offsets with icon and trimming
if (matches) {
for (const match of matches) {
const iconOffset = iconOffsets[match.start + leadingWhitespaceOffset] /* icon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */;
match.start += iconOffset;
match.end += iconOffset;
}
}
return matches;
}