microsoft / vscode

Visual Studio Code
https://code.visualstudio.com
MIT License
164.75k stars 29.47k forks source link

[icon-themes] Support for globs in file associations (Icon themes) #12493

Open robertohuertasm opened 8 years ago

robertohuertasm commented 8 years ago

See vscode-icons #328

This will allow to support a very common scenario which is a filename with small variations depending on environments:

kristianmandrup commented 6 years ago

I would assume it would just require using a different matcher for when the file extension expression is a regexp?

kristianmandrup commented 6 years ago

Looking into it now (for about ~5 mins).

From what I can gather, the changes need to be done in [vscode-icons]((https://github.com/vscode-icons/vscode-icons) in iconGenerator.ts

        defs[iconFolderDefinition] = {
          iconPath: `${folderPath}${sts.iconSuffix}${iconFileExtension}`,
        };

The iconFileExtension should not be taken directly from the icon JSON config loaded but be calculated on match from expression (if detected to be an expression and not a simple file extension!)

L37:

const iconFileExtension = utils.fileFormatToString(current.format);

Should instead be sth like

const iconFileExtension = utils.firstExtensionMatch(current.format, iconExprMatches);

Should somehow use data gathered from the extensions list (which should be able to take reg expressions or perhaps mappings of the form {"svg": "*.svg$")

        current.extensions.forEach(extension => {
          const key = extension;
          names.folderNames[key] = iconFolderDefinition;
          names.folderNamesExpanded[key] = iconOpenFolderDefinition;
          light.folderNames[key] = hasLightVersion
            ? iconFolderLightDefinition
            : iconFolderDefinition;
          light.folderNamesExpanded[key] = hasLightVersion
            ? iconOpenFolderLightDefinition
            : iconOpenFolderDefinition;
        });

Instead should be sth like:

        current.extensions.forEach(handleExtensionConfig)

function isSimpleExtension(extension) {
  return typeof extension === 'string'
}

function isRegExprMappingExtension(extension) {
  return typeof extension === 'object'
}

function handleExtensionConfig(extension) {
  extension => {
  if (isSimpleExtension(extension)) {
  // old simple extension handler code
  return
} 
if (isRegExprMappingExtension(extension) {
  // use regexp mapping to define more complex key entry to match on
  return
}
console.error('BAD icon extension definition', extension)
// ignore bad entry?
}

The iconGenerator tests then also need to be extended with such functionality, similar to:

    it('new file extensions are included into the manifest', function() {
      const custom = emptyFileCollection;
      custom.supported.push({
        icon: 'actionscript',
        extensions: ['as'],
        format: 'svg',
      });
      const json = iconGenerator.generateJson(custom, emptyFolderCollection);
      const def = `${settings.manifestFilePrefix}actionscript`;
      const ext = json.iconDefinitions[def];
      expect(ext).to.exist;
      expect(ext.iconPath).not.to.be.empty;
      expect(json.fileExtensions['as']).to.be.equal(def);
    });

To sth like:

      custom.supported.push({
        icon: 'actionscript',
        extensions: [{as: ["y.as", "x.as"]}],
        format: 'svg',
      });

Or perhaps even taking regexp expr:

  as: ["*.as"]
}, "bs", {
//...
 }],

What do you guys think?

aeschli commented 6 years ago

@deiga Why not define in your user settings:

    "files.associations": {
        "docker-compose.*.yml": "dockerfile",
        "Dockerfile.*": "dockerfile"
    },

image

kristianmandrup commented 6 years ago

@aeschli Great to see you can just define file associations. Not well documented I guess ;) Still I think vscode icons should be updated to support mappings of the sort I sketched out above.

robertohuertasm commented 6 years ago

@kristianmandrup the icons extensions must generate a final json with all mapped extensions, and that's what vscode will use in order to do the mappings. This is a well defined api and there's no support for regex at all. So trying to somehow convert a regex to all possible candidates that would match would be not feasible (imagine to have to provide matches for *.yml)

As a temporal solution you have the one that @aeschli provided (which associates some files to a file type, so it wouldn't work for not supported language ids I guess) or you can use our customization options, which would allow you to associate any kind of extension to the icon you want (but still without the power of globs because the JSON that vscode expects doesn't support it - and hence this issue still stands).

deiga commented 6 years ago

@aeschli Awesome, didn't know that was possible!

Now we need to figure out how to build a system that wouldn't require custom rules for sensible defaults :)

muuvmuuv commented 6 years ago

I tried @aeschli suggestion with binary and can't get it working. Is there a list of available files.associations?

  "files.associations": {
    "*.{dist,disabled}": "binary"
  },
andreluzz commented 5 years ago

+1

kwiss commented 4 years ago

Is it dead ? is there no way to do this ?

graue70 commented 4 years ago

I tried @aeschli suggestion with binary and can't get it working. Is there a list of available files.associations?

  "files.associations": {
    "*.{dist,disabled}": "binary"
  },

@muuvmuuv There is a list in the docs.

ghost commented 3 years ago

Could the directory also be exposed in the File Icon API? (Moved to new issue)

danielflippance commented 3 years ago

+1 Want to have different icons for python files *.py and python tests test_*.py

metuuu commented 3 years ago

+1

ghost commented 3 years ago

Because this should be a new issue.. I opened #136656 for anyone interested in support of folder/directory name in file association.

pgbradbury commented 2 years ago

@aeschli is this still open in the backlog? Is there anything that can be done to help it along? It's been five and a half years. Is it likely to be done?

aeschli commented 2 years ago

We have no plans for implementing this future. It doesn't go well with the current implementation of file icons based on matching CSS rules and would require a rewrite.

MisterLight commented 2 years ago

We have no plans for implementing this future. It doesn't go well with the current implementation of file icons based on matching CSS rules and would require a rewrite.

@aeschli Could be a way to specify the extension separator? For example see: https://github.com/PKief/vscode-material-icon-theme/issues/1598

A posible solution for that could be change the default separator '.' to another special character like '_' or '-'

zm-cttae commented 1 year ago

It doesn't go well with the current implementation of file icons based on matching CSS rules and would require a rewrite.

Correct that file icon theme consumption and icon CSS class logic is separate:

File icons would probably suffer an FOUC if the two interacted, because the entire vscode-icons file icon theme would be recomputed again for a simple action like a folder open.

(Take $n$ as the number of dot segments in a filename.)

There are two possible approaches and both need new data (1. is super ugly IMHO and should be avoided):

  1. Add a basename class, then a positional anchor class before and after the extension list, then figure out a combination of .ext, [class*=] and :not([class*=]) that can match that glob. That can be formalised as some kind of algorithm, but the testing and maintainability cost would be high. Also *= attribute selectors are brittle.
  2. Explode a filename into all possible matching globs and just match that glob.
    • The use cases described here would be quick to run - $2n-2$ still $O(N)$ - and easier to maintain.
    • If we want a comprehensive set of useful globs, that would lead to a $2^n$ now $O(c^N)$ exponential combination (e.g. for webpack.dev.config.json you'd have 16 relevant results..)
    • Perhaps we can invest in the comprehensive set when $n < 5$ .

We also would want ** to match $n>=1$-segment matching instead of $n>=0$ (So ** = *, *.*, *.*.*) Because otherwise we'd be increasing our big-O $N$ by $0.5$.

Proof of concept, $O(N)$ linear solution for getIconClasses.ts:

// Prefix-matching coalescing globs and non-coalescing wildcard globs
function getIconClassesForSomeGlobs(name) {
    // remove ellipsis + chars >=255 to defend against explosive combination
    // https://github.com/microsoft/vscode/issues/116199
    const dotSegments = name.slice(-255).replace(/\.\.+/g, '').split('.');
    const lastDotIndex = dotSegments.length - 1;
    const globs = [];
    for (let i = 0; i < lastDotIndex; i++) {
        const suffix = dotSegments.slice(0, i + 1);
        suffix.push(i < lastDotIndex - 1 ? '**' : '*');
        globs.push(suffix.join('.'));
        const base = dotSegments.slice();
        base.splice(i, 1, '*');
        globs.push(base.join('.'));
    }
    return globs;
}

This is the $O(c^N), c = 2$ solution:

// All globs matching a file name, excluding globs with chained `*` segments
function getIconClassesForAllGlobs(name) {
    // remove ellipsis + chars >=255 to defend against explosive combination
    // https://github.com/microsoft/vscode/issues/116199
    const dotSegments = name.slice(-255).replace(/\.\.+/g, '').split('.'); 
    const globs = []
    const bitmask = Math.pow(2, dotSegments.length) - 1;
    for (let i = 0; i < bitmask; i++) {
        let buffer = [];
        for (let j = 0; j < dotSegments.length; j++) {
            buffer.push(i & Math.pow(2, j) ? dotSegments[j] : '*');
        }
        globs[i] = buffer.join('.').replace(/(?<=\*)(?:\.\*)+/, '*'); // coalesce chained * filename segments into **
    }
    return globs;
}
zm-cttae commented 1 year ago

@aeschli now that this is feasible - run this replit to see - there are two questions here:

  1. Should iconLabel.ts be able to generate data attributes? Then fileIconThemeData.ts could do [data-glob~=glob]. getIconClasses function sending out 50+ selectors suffixed with -glob-file-icon is not sane to me.
  2. Thoughts on when the linear solution (getIconClassesForSomeGlobs) is ideal and getIconClassesForAllGlobs isn't? The replit shows how the first function outputs a limited subset of the original.

Noting here - filenames with 5 or more dot segments are consistently considered "weird" and we're not losing much with the $n < 5$ limit explained in https://github.com/microsoft/vscode/issues/12493#issuecomment-1382322653.

9ssi7 commented 1 year ago

You must allow patterns on the icon issue.

I guess you think this can be achieved with extensions but there are exceptions. In Golang, test files must end with _test. Click for detailed information.

Golang related test documentation:

Screenshot 2023-01-28 at 16 21 23

I want to add a corresponding icon to my go files ending with_test pattern. But vscode api doesn't accept a fileNames value like *_test. We cannot achieve this with fileExtensions. Goland, an alternative to vscode, provides this. I think vscode should provide it too.

I'm open to feedback.

zm-cttae commented 1 year ago

It is possible to do name._test.go for now it seems, so fileExtensions works for Go. Doesn't work for test_*.py in Python. vscode-icons/vscode-icons#2754 explains that this use case has significant A11Y benefit for dyslexia.


As I understand it, we have (maybe past tense now) a significant blocker:

File icons would probably suffer an FOUC if the two interacted, because the entire vscode-icons file icon theme would be recomputed again for a simple action like a folder open.

My suggestion was to generate globs on the CSS service side, and those get targeted in file icon theme.. but this needs the assignee's input (plus core team has other work to do).

I threw together code for your use case still - see maybePushBasenameDashGlobs function in the replit.

zm-cttae commented 1 year ago

I've implemented the feature at #174286.
Please take a look at new spec - https://github.com/microsoft/vscode/pull/174286#issuecomment-1464506764 - and see if you have suggestions The PR will probably be tested during the March or April 2023 iteration - when the assignee gets time.
Huge thanks to them for their early reviews also 👍

NatoBoram commented 4 months ago

Also affected by this.

"material-icon-theme.files.associations": {
    "tsconfig.*.json": "tsconfig"
},

image

"material-icon-theme.files.associations": {
    "tsconfig.rollup.json": "tsconfig"
},

image