Skip to content

Commit

Permalink
Merge pull request #140079 from SNDST00M/feat-theme-dirname-class
Browse files Browse the repository at this point in the history
Add optional directory syntax in file icon themes
  • Loading branch information
aeschli authored Feb 3, 2022
2 parents 47fc3a1 + 7fce4b6 commit 318f04c
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 14 deletions.
16 changes: 14 additions & 2 deletions src/vs/editor/common/services/getIconClasses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
42 changes: 30 additions & 12 deletions src/vs/workbench/services/themes/browser/fileIconThemeData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
}
Expand All @@ -317,34 +323,36 @@ 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++) {
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`, fileNames[fileName]);
addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileNames[key]);
result.hasFileIcons = true;
hasSpecificFileIcons = true;
}
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 318f04c

Please sign in to comment.