diff --git a/src/vs/editor/common/services/getIconClasses.ts b/src/vs/editor/common/services/getIconClasses.ts index fab05c61a97b3..57a63980ce713 100644 --- a/src/vs/editor/common/services/getIconClasses.ts +++ b/src/vs/editor/common/services/getIconClasses.ts @@ -4,13 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { Schemas } from 'vs/base/common/network'; -import { DataUri, basenameOrAuthority } from 'vs/base/common/resources'; +import { DataUri } from 'vs/base/common/resources'; import { URI as uri } from 'vs/base/common/uri'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { IModelService } from 'vs/editor/common/services/model'; import { FileKind } from 'vs/platform/files/common/files'; +const fileIconDirectoryRegex = /(?:\/|^)(?:([^\/]+)\/)?([^\/]+)$/; + export function getIconClasses(modelService: IModelService, languageService: ILanguageService, resource: uri | undefined, fileKind?: FileKind): string[] { // we always set these base classes even if we do not have a path @@ -23,7 +25,16 @@ export function getIconClasses(modelService: IModelService, languageService: ILa const metadata = DataUri.parseMetaData(resource); name = metadata.get(DataUri.META_DATA_LABEL); } else { - name = cssEscape(basenameOrAuthority(resource).toLowerCase()); + const match = resource.path.match(fileIconDirectoryRegex); + if (match) { + name = cssEscape(match[2].toLowerCase()); + if (match[1]) { + classes.push(`${cssEscape(match[1].toLowerCase())}-name-dir-icon`); // parent directory + } + + } else { + name = cssEscape(resource.authority.toLowerCase()); + } } // Folders @@ -37,6 +48,7 @@ export function getIconClasses(modelService: IModelService, languageService: ILa // Name & Extension(s) if (name) { classes.push(`${name}-name-file-icon`); + classes.push(`name-file-icon`); // extra segment to increase file-name score // Avoid doing an explosive combination of extensions for very long filenames // (most file systems do not allow files > 255 length) with lots of `.` characters // https://github.com/microsoft/vscode/issues/116199 diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index e9594023a5dad..f725f7a3540bb 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; -import * as Paths from 'vs/base/common/path'; +import * as paths from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import * as Json from 'vs/base/common/json'; import { ExtensionData, IThemeExtensionPoint, IWorkbenchFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; @@ -57,7 +57,7 @@ export class FileIconThemeData implements IWorkbenchFileIconTheme { static fromExtensionTheme(iconTheme: IThemeExtensionPoint, iconThemeLocation: URI, extensionData: ExtensionData): FileIconThemeData { const id = extensionData.extensionId + '-' + iconTheme.id; - const label = iconTheme.label || Paths.basename(iconTheme.path); + const label = iconTheme.label || paths.basename(iconTheme.path); const settingsId = iconTheme.id; const themeData = new FileIconThemeData(id, label, settingsId); @@ -290,15 +290,21 @@ export class FileIconThemeLoader { const folderNames = associations.folderNames; if (folderNames) { - for (const folderName in folderNames) { - addSelector(`${qualifier} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNames[folderName]); + for (const key in folderNames) { + const selectors: string[] = []; + const name = handleParentFolder(key.toLowerCase(), selectors); + selectors.push(`.${escapeCSS(name)}-name-folder-icon`); + addSelector(`${qualifier} ${selectors.join('')}.folder-icon::before`, folderNames[key]); result.hasFolderIcons = true; } } const folderNamesExpanded = associations.folderNamesExpanded; if (folderNamesExpanded) { - for (const folderName in folderNamesExpanded) { - addSelector(`${qualifier} ${expanded} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNamesExpanded[folderName]); + for (const key in folderNamesExpanded) { + const selectors: string[] = []; + const name = handleParentFolder(key.toLowerCase(), selectors); + selectors.push(`.${escapeCSS(name)}-name-folder-icon`); + addSelector(`${qualifier} ${expanded} ${selectors.join('')}.folder-icon::before`, folderNamesExpanded[key]); result.hasFolderIcons = true; } } @@ -317,26 +323,28 @@ export class FileIconThemeLoader { } const fileExtensions = associations.fileExtensions; if (fileExtensions) { - for (const fileExtension in fileExtensions) { + for (const key in fileExtensions) { const selectors: string[] = []; - const segments = fileExtension.toLowerCase().split('.'); + const name = handleParentFolder(key.toLowerCase(), selectors); + const segments = name.split('.'); if (segments.length) { for (let i = 0; i < segments.length; i++) { selectors.push(`.${escapeCSS(segments.slice(i).join('.'))}-ext-file-icon`); } selectors.push('.ext-file-icon'); // extra segment to increase file-ext score } - addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileExtensions[fileExtension]); + addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileExtensions[key]); result.hasFileIcons = true; hasSpecificFileIcons = true; } } const fileNames = associations.fileNames; if (fileNames) { - for (let fileName in fileNames) { + for (const key in fileNames) { const selectors: string[] = []; - fileName = fileName.toLowerCase(); + const fileName = handleParentFolder(key.toLowerCase(), selectors); selectors.push(`.${escapeCSS(fileName)}-name-file-icon`); + selectors.push('.name-file-icon'); // extra segment to increase file-name score const segments = fileName.split('.'); if (segments.length) { for (let i = 1; i < segments.length; i++) { @@ -344,7 +352,7 @@ export class FileIconThemeLoader { } selectors.push('.ext-file-icon'); // extra segment to increase file-ext score } - addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileNames[fileName]); + addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileNames[key]); result.hasFileIcons = true; hasSpecificFileIcons = true; } @@ -425,6 +433,16 @@ export class FileIconThemeLoader { } +function handleParentFolder(key: string, selectors: string[]): string { + const lastIndexOfSlash = key.lastIndexOf('/'); + if (lastIndexOfSlash >= 0) { + const parentFolder = key.substring(0, lastIndexOfSlash); + selectors.push(`.${escapeCSS(parentFolder)}-name-dir-icon`); + return key.substring(lastIndexOfSlash + 1); + } + return key; +} + function escapeCSS(str: string) { str = str.replace(/[\11\12\14\15\40]/g, '/'); // HTML class names can not contain certain whitespace characters, use / instead, which doesn't exist in file names. return window.CSS.escape(str);