iconLabels.js 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  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. import { matchesFuzzy } from './filters.js';
  6. import { ltrim } from './strings.js';
  7. import { ThemeIcon } from './themables.js';
  8. const iconStartMarker = '$(';
  9. const iconsRegex = new RegExp(`\\$\\(${ThemeIcon.iconNameExpression}(?:${ThemeIcon.iconModifierExpression})?\\)`, 'g'); // no capturing groups
  10. const escapeIconsRegex = new RegExp(`(\\\\)?${iconsRegex.source}`, 'g');
  11. export function escapeIcons(text) {
  12. return text.replace(escapeIconsRegex, (match, escaped) => escaped ? match : `\\${match}`);
  13. }
  14. const markdownEscapedIconsRegex = new RegExp(`\\\\${iconsRegex.source}`, 'g');
  15. export function markdownEscapeEscapedIcons(text) {
  16. // Need to add an extra \ for escaping in markdown
  17. return text.replace(markdownEscapedIconsRegex, match => `\\${match}`);
  18. }
  19. const stripIconsRegex = new RegExp(`(\\s)?(\\\\)?${iconsRegex.source}(\\s)?`, 'g');
  20. /**
  21. * Takes a label with icons (`$(iconId)xyz`) and strips the icons out (`xyz`)
  22. */
  23. export function stripIcons(text) {
  24. if (text.indexOf(iconStartMarker) === -1) {
  25. return text;
  26. }
  27. return text.replace(stripIconsRegex, (match, preWhitespace, escaped, postWhitespace) => escaped ? match : preWhitespace || postWhitespace || '');
  28. }
  29. /**
  30. * Takes a label with icons (`$(iconId)xyz`), removes the icon syntax adds whitespace so that screen readers can read the text better.
  31. */
  32. export function getCodiconAriaLabel(text) {
  33. if (!text) {
  34. return '';
  35. }
  36. return text.replace(/\$\((.*?)\)/g, (_match, codiconName) => ` ${codiconName} `).trim();
  37. }
  38. const _parseIconsRegex = new RegExp(`\\$\\(${ThemeIcon.iconNameCharacter}+\\)`, 'g');
  39. /**
  40. * Takes a label with icons (`abc $(iconId)xyz`) and returns the text (`abc xyz`) and the offsets of the icons (`[3]`)
  41. */
  42. export function parseLabelWithIcons(input) {
  43. _parseIconsRegex.lastIndex = 0;
  44. let text = '';
  45. const iconOffsets = [];
  46. let iconsOffset = 0;
  47. while (true) {
  48. const pos = _parseIconsRegex.lastIndex;
  49. const match = _parseIconsRegex.exec(input);
  50. const chars = input.substring(pos, match === null || match === void 0 ? void 0 : match.index);
  51. if (chars.length > 0) {
  52. text += chars;
  53. for (let i = 0; i < chars.length; i++) {
  54. iconOffsets.push(iconsOffset);
  55. }
  56. }
  57. if (!match) {
  58. break;
  59. }
  60. iconsOffset += match[0].length;
  61. }
  62. return { text, iconOffsets };
  63. }
  64. export function matchesFuzzyIconAware(query, target, enableSeparateSubstringMatching = false) {
  65. const { text, iconOffsets } = target;
  66. // Return early if there are no icon markers in the word to match against
  67. if (!iconOffsets || iconOffsets.length === 0) {
  68. return matchesFuzzy(query, text, enableSeparateSubstringMatching);
  69. }
  70. // Trim the word to match against because it could have leading
  71. // whitespace now if the word started with an icon
  72. const wordToMatchAgainstWithoutIconsTrimmed = ltrim(text, ' ');
  73. const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutIconsTrimmed.length;
  74. // match on value without icon
  75. const matches = matchesFuzzy(query, wordToMatchAgainstWithoutIconsTrimmed, enableSeparateSubstringMatching);
  76. // Map matches back to offsets with icon and trimming
  77. if (matches) {
  78. for (const match of matches) {
  79. const iconOffset = iconOffsets[match.start + leadingWhitespaceOffset] /* icon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */;
  80. match.start += iconOffset;
  81. match.end += iconOffset;
  82. }
  83. }
  84. return matches;
  85. }