Fix file icon mapping (#33855)

Use the file extension mapping from VSCode's extensions.
Otherwise js/ts/vba/... files won't get correct icons.
pull/33694/head^2
wxiaoguang 2 days ago committed by GitHub
parent 608ccc32e5
commit f61f30153b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -21,6 +21,7 @@ type materialIconRulesData struct {
FileNames map[string]string `json:"fileNames"`
FolderNames map[string]string `json:"folderNames"`
FileExtensions map[string]string `json:"fileExtensions"`
LanguageIDs map[string]string `json:"languageIds"`
}
type MaterialIconProvider struct {
@ -107,25 +108,40 @@ func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, entry *git.Tr
return svg.RenderHTML("octicon-file")
}
func (m *MaterialIconProvider) findIconNameWithLangID(s string) string {
if _, ok := m.svgs[s]; ok {
return s
}
if s, ok := m.rules.LanguageIDs[s]; ok {
if _, ok = m.svgs[s]; ok {
return s
}
}
return ""
}
func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
iconsData := m.rules
fileNameLower := strings.ToLower(path.Base(name))
if isDir {
if s, ok := iconsData.FolderNames[fileNameLower]; ok {
if s, ok := m.rules.FolderNames[fileNameLower]; ok {
return s
}
return "folder"
}
if s, ok := iconsData.FileNames[fileNameLower]; ok {
return s
if s, ok := m.rules.FileNames[fileNameLower]; ok {
if s = m.findIconNameWithLangID(s); s != "" {
return s
}
}
for i := len(fileNameLower) - 1; i >= 0; i-- {
if fileNameLower[i] == '.' {
ext := fileNameLower[i+1:]
if s, ok := iconsData.FileExtensions[ext]; ok {
return s
if s, ok := m.rules.FileExtensions[ext]; ok {
if s = m.findIconNameWithLangID(s); s != "" {
return s
}
}
}
}

@ -21,4 +21,6 @@ func TestFindIconName(t *testing.T) {
p := fileicon.DefaultMaterialIconProvider()
assert.Equal(t, "php", p.FindIconName("foo.php", false))
assert.Equal(t, "php", p.FindIconName("foo.PHP", false))
assert.Equal(t, "javascript", p.FindIconName("foo.js", false))
assert.Equal(t, "visualstudio", p.FindIconName("foo.vba", false))
}

@ -7038,107 +7038,201 @@
"gnu": "gnuplot",
"yaml-tmlanguage": "yaml",
"tmlanguage": "xml",
"git": "git",
"git-commit": "git",
"git-rebase": "git",
"ignore": "git",
"github-actions-workflow": "github-actions-workflow",
"yaml": "yaml",
"spring-boot-properties-yaml": "yaml",
"ansible": "yaml",
"ansible-jinja": "yaml",
"matlab": "matlab",
"makefile": "settings",
"spring-boot-properties": "settings",
"cljx": "clojure",
"clojure": "clojure",
"edn": "clojure",
"cppm": "cpp",
"ccm": "cpp",
"cxxm": "cpp",
"c++m": "cpp",
"ipp": "cpp",
"ixx": "cpp",
"tpp": "cpp",
"txx": "cpp",
"hpp.in": "cpp",
"h.in": "cpp",
"diff": "diff",
"razor": "razor",
"aspnetcorerazor": "razor",
"python": "python",
"javascript": "javascript",
"typescript": "typescript",
"rej": "diff",
"fsscript": "fsharp",
"gitignore_global": "ignore",
"gitignore": "ignore",
"git-blame-ignore-revs": "ignore",
"gvy": "groovy",
"nf": "groovy",
"handlebars": "handlebars",
"perl": "perl",
"perl6": "perl",
"haxe": "haxe",
"hxml": "haxe",
"puppet": "puppet",
"elixir": "elixir",
"livescript": "livescript",
"erlang": "erlang",
"julia": "julia",
"purescript": "purescript",
"stylus": "stylus",
"robotframework": "robot",
"testoutput": "visualstudio",
"solidity": "solidity",
"autoit": "autoit",
"terraform": "terraform",
"cucumber": "cucumber",
"postcss": "postcss",
"lang-cfml": "coldfusion",
"haskell": "haskell",
"ruby": "ruby",
"php": "php",
"hack": "hack",
"javascriptreact": "react",
"processing": "processing",
"django-html": "django",
"django-txt": "django",
"hjs": "handlebars",
"hlsli": "hlsl",
"fx": "hlsl",
"fxh": "hlsl",
"vsh": "hlsl",
"psh": "hlsl",
"cginc": "hlsl",
"compute": "hlsl",
"html": "html",
"gdscript": "godot",
"gdresource": "godot-assets",
"viml": "vim",
"prolog": "prolog",
"pawn": "pawn",
"reason": "reason",
"reason_lisp": "reason",
"doctex": "tex",
"latex": "tex",
"latex-expl3": "tex",
"apex": "salesforce",
"dockercompose": "docker",
"shellscript": "console",
"objective-c": "objective-c",
"objective-cpp": "objective-cpp",
"coffeescript": "coffee",
"fsharp": "fsharp",
"editorconfig": "editorconfig",
"clojure": "clojure",
"pip-requirements": "python-misc",
"vue-postcss": "vue",
"vue-html": "vue",
"bibtex": "lib",
"bibtex-style": "lib",
"jupyter": "jupyter",
"plaintext": "document",
"powershell": "powershell",
"rsweave": "r",
"rust": "rust",
"ssh_config": "lock",
"typescriptreact": "react_ts",
"search-result": "search",
"rescript": "rescript",
"twee3": "twine",
"twee3-harlowe-3": "twine",
"twee3-chapbook-1": "twine",
"twee3-sugarcube-2": "twine",
"grain": "grain",
"lolcode": "lolcode",
"idris": "idris",
"text-gemini": "gemini",
"wolfram": "wolframlanguage",
"shaderlab": "shader",
"cadence": "cadence",
"stylable": "stylable",
"capnb": "cds",
"cds-markdown-injection": "cds",
"concourse-pipeline-yaml": "concourse",
"concourse-task-yaml": "concourse",
"systemd-conf": "systemd",
"systemd-unit-file": "systemd",
"hosts": "hosts",
"ahk2": "ahk2",
"gnuplot": "gnuplot"
"shtml": "html",
"xht": "html",
"aspx": "html",
"jshtm": "html",
"volt": "html",
"rhtml": "html",
"directory": "properties",
"gitattributes": "properties",
"gitconfig": "properties",
"gitmodules": "properties",
"editorconfig": "properties",
"repo": "properties",
"jav": "java",
"js": "javascript",
"es6": "javascript",
"cjs": "javascript",
"pac": "javascript",
"bowerrc": "json",
"jscsrc": "json",
"webmanifest": "json",
"ts.map": "json",
"har": "json",
"jslintrc": "json",
"jsonld": "json",
"geojson": "json",
"vuerc": "json",
"eslintrc": "jsonc",
"eslintrc.json": "jsonc",
"jsfmtrc": "jsonc",
"jshintrc": "jsonc",
"hintrc": "jsonc",
"babelrc": "jsonc",
"jmd": "juliamarkdown",
"cls": "tex",
"bbx": "tex",
"cbx": "tex",
"ctx": "latex",
"mak": "makefile",
"mkd": "markdown",
"mdwn": "markdown",
"mdown": "markdown",
"markdn": "markdown",
"mdtxt": "markdown",
"mdtext": "markdown",
"workbook": "markdown",
"npmignore": "ignore",
"npmrc": "properties",
"m": "objective-c",
"mm": "objective-cpp",
"pod": "perl",
"t": "perl",
"psgi": "perl",
"rakumod": "raku",
"rakutest": "raku",
"rakudoc": "raku",
"nqp": "raku",
"p6": "raku",
"pl6": "raku",
"pm6": "raku",
"php": "php",
"php4": "php",
"php5": "php",
"phtml": "php",
"ctp": "php",
"psrc": "powershell",
"rpy": "python",
"pyw": "python",
"cpy": "python",
"gyp": "python",
"gypi": "python",
"pyi": "python",
"ipy": "python",
"pyt": "python",
"rhistory": "r",
"rprofile": "r",
"rt": "r",
"razor": "razor",
"rbx": "ruby",
"rjs": "ruby",
"gemspec": "ruby",
"rake": "ruby",
"ru": "ruby",
"podspec": "ruby",
"rbi": "ruby",
"bashrc": "shellscript",
"bash_aliases": "shellscript",
"bash_profile": "shellscript",
"bash_login": "shellscript",
"ebuild": "shellscript",
"eclass": "shellscript",
"profile": "shellscript",
"bash_logout": "shellscript",
"xprofile": "shellscript",
"xsession": "shellscript",
"xsessionrc": "shellscript",
"zshrc": "shellscript",
"zprofile": "shellscript",
"zlogin": "shellscript",
"zlogout": "shellscript",
"zshenv": "shellscript",
"zsh-theme": "shellscript",
"cshrc": "shellscript",
"tcshrc": "shellscript",
"yashrc": "shellscript",
"yash_profile": "shellscript",
"dsql": "sql",
"ts": "typescript",
"cts": "typescript",
"mts": "typescript",
"brs": "vb",
"bas": "vb",
"vba": "vb",
"ascx": "xml",
"atom": "xml",
"axml": "xml",
"axaml": "xml",
"bpmn": "xml",
"csl": "xml",
"csproj.user": "xml",
"dita": "xml",
"ditamap": "xml",
"ent": "xml",
"dtml": "xml",
"fxml": "xml",
"isml": "xml",
"jmx": "xml",
"launch": "xml",
"menu": "xml",
"nuspec": "xml",
"opml": "xml",
"owl": "xml",
"proj": "xml",
"pt": "xml",
"publishsettings": "xml",
"pubxml": "xml",
"pubxml.user": "xml",
"rdf": "xml",
"rng": "xml",
"rss": "xml",
"shproj": "xml",
"storyboard": "xml",
"targets": "xml",
"tld": "xml",
"tmx": "xml",
"vbproj": "xml",
"vbproj.user": "xml",
"wsdl": "xml",
"wxi": "xml",
"wxl": "xml",
"wxs": "xml",
"xbl": "xml",
"xib": "xml",
"xliff": "xml",
"xpdl": "xml",
"xul": "xml",
"xoml": "xml",
"yaml": "yaml",
"yml": "yaml",
"eyaml": "yaml",
"eyml": "yaml",
"cff": "yaml",
"yaml-tmpreferences": "yaml",
"yaml-tmtheme": "yaml",
"winget": "yaml"
},
"fileNames": {
".pug-lintrc": "pug",
@ -9015,7 +9109,11 @@
"caddyfile": "caddy",
"pklproject": "pkl",
"pklproject.deps.json": "pkl",
".github/funding.yml": "github-sponsors"
".github/funding.yml": "github-sponsors",
"language-configuration.json": "jsonc",
"icon-theme.json": "jsonc",
"color-theme.json": "jsonc",
"*.log.?": "log"
},
"languageIds": {
"git": "git",

@ -0,0 +1,570 @@
{
"pkg:bat": {
"bat": [
".bat",
".cmd"
]
},
"pkg:clojure": {
"clojure": [
".clj",
".cljs",
".cljc",
".cljx",
".clojure",
".edn"
]
},
"pkg:coffeescript": {
"coffeescript": [
".coffee",
".cson",
".iced"
]
},
"pkg:configuration-editing": {
"jsonc": [
".code-workspace",
"language-configuration.json",
"icon-theme.json",
"color-theme.json"
],
"json": [
".code-profile"
]
},
"pkg:cpp": {
"c": [
".c",
".i"
],
"cpp": [
".cpp",
".cppm",
".cc",
".ccm",
".cxx",
".cxxm",
".c++",
".c++m",
".hpp",
".hh",
".hxx",
".h++",
".h",
".ii",
".ino",
".inl",
".ipp",
".ixx",
".tpp",
".txx",
".hpp.in",
".h.in"
],
"cuda-cpp": [
".cu",
".cuh"
]
},
"pkg:csharp": {
"csharp": [
".cs",
".csx",
".cake"
]
},
"pkg:css": {
"css": [
".css"
]
},
"pkg:dart": {
"dart": [
".dart"
]
},
"pkg:diff": {
"diff": [
".diff",
".patch",
".rej"
]
},
"pkg:docker": {
"dockerfile": [
".dockerfile",
".containerfile"
]
},
"pkg:fsharp": {
"fsharp": [
".fs",
".fsi",
".fsx",
".fsscript"
]
},
"pkg:git-base": {
"ignore": [
".gitignore_global",
".gitignore",
".git-blame-ignore-revs"
]
},
"pkg:go": {
"go": [
".go"
]
},
"pkg:groovy": {
"groovy": [
".groovy",
".gvy",
".gradle",
".jenkinsfile",
".nf"
]
},
"pkg:handlebars": {
"handlebars": [
".handlebars",
".hbs",
".hjs"
]
},
"pkg:hlsl": {
"hlsl": [
".hlsl",
".hlsli",
".fx",
".fxh",
".vsh",
".psh",
".cginc",
".compute"
]
},
"pkg:html": {
"html": [
".html",
".htm",
".shtml",
".xhtml",
".xht",
".mdoc",
".jsp",
".asp",
".aspx",
".jshtm",
".volt",
".ejs",
".rhtml"
]
},
"pkg:ini": {
"ini": [
".ini"
],
"properties": [
".conf",
".properties",
".cfg",
".directory",
".gitattributes",
".gitconfig",
".gitmodules",
".editorconfig",
".repo"
]
},
"pkg:java": {
"java": [
".java",
".jav"
]
},
"pkg:javascript": {
"javascriptreact": [
".jsx"
],
"javascript": [
".js",
".es6",
".mjs",
".cjs",
".pac"
]
},
"pkg:json": {
"json": [
".json",
".bowerrc",
".jscsrc",
".webmanifest",
".js.map",
".css.map",
".ts.map",
".har",
".jslintrc",
".jsonld",
".geojson",
".ipynb",
".vuerc"
],
"jsonc": [
".jsonc",
".eslintrc",
".eslintrc.json",
".jsfmtrc",
".jshintrc",
".swcrc",
".hintrc",
".babelrc"
],
"jsonl": [
".jsonl",
".ndjson"
],
"snippets": [
".code-snippets"
]
},
"pkg:julia": {
"julia": [
".jl"
],
"juliamarkdown": [
".jmd"
]
},
"pkg:latex": {
"tex": [
".sty",
".cls",
".bbx",
".cbx"
],
"latex": [
".tex",
".ltx",
".ctx"
],
"bibtex": [
".bib"
]
},
"pkg:less": {
"less": [
".less"
]
},
"pkg:log": {
"log": [
".log",
"*.log.?"
]
},
"pkg:lua": {
"lua": [
".lua"
]
},
"pkg:make": {
"makefile": [
".mak",
".mk"
]
},
"pkg:markdown-basics": {
"markdown": [
".md",
".mkd",
".mdwn",
".mdown",
".markdown",
".markdn",
".mdtxt",
".mdtext",
".workbook"
]
},
"pkg:ms-vscode.js-debug": {
"wat": [
".wat",
".wasm"
]
},
"pkg:npm": {
"ignore": [
".npmignore"
],
"properties": [
".npmrc"
]
},
"pkg:objective-c": {
"objective-c": [
".m"
],
"objective-cpp": [
".mm"
]
},
"pkg:perl": {
"perl": [
".pl",
".pm",
".pod",
".t",
".PL",
".psgi"
],
"raku": [
".raku",
".rakumod",
".rakutest",
".rakudoc",
".nqp",
".p6",
".pl6",
".pm6"
]
},
"pkg:php": {
"php": [
".php",
".php4",
".php5",
".phtml",
".ctp"
]
},
"pkg:powershell": {
"powershell": [
".ps1",
".psm1",
".psd1",
".pssc",
".psrc"
]
},
"pkg:pug": {
"jade": [
".pug",
".jade"
]
},
"pkg:python": {
"python": [
".py",
".rpy",
".pyw",
".cpy",
".gyp",
".gypi",
".pyi",
".ipy",
".pyt"
]
},
"pkg:r": {
"r": [
".r",
".rhistory",
".rprofile",
".rt"
]
},
"pkg:razor": {
"razor": [
".cshtml",
".razor"
]
},
"pkg:restructuredtext": {
"restructuredtext": [
".rst"
]
},
"pkg:ruby": {
"ruby": [
".rb",
".rbx",
".rjs",
".gemspec",
".rake",
".ru",
".erb",
".podspec",
".rbi"
]
},
"pkg:rust": {
"rust": [
".rs"
]
},
"pkg:scss": {
"scss": [
".scss"
]
},
"pkg:search-result": {
"search-result": [
".code-search"
]
},
"pkg:shaderlab": {
"shaderlab": [
".shader"
]
},
"pkg:shellscript": {
"shellscript": [
".sh",
".bash",
".bashrc",
".bash_aliases",
".bash_profile",
".bash_login",
".ebuild",
".eclass",
".profile",
".bash_logout",
".xprofile",
".xsession",
".xsessionrc",
".Xsession",
".zsh",
".zshrc",
".zprofile",
".zlogin",
".zlogout",
".zshenv",
".zsh-theme",
".fish",
".ksh",
".csh",
".cshrc",
".tcshrc",
".yashrc",
".yash_profile"
]
},
"pkg:sql": {
"sql": [
".sql",
".dsql"
]
},
"pkg:swift": {
"swift": [
".swift"
]
},
"pkg:typescript-basics": {
"typescript": [
".ts",
".cts",
".mts"
],
"typescriptreact": [
".tsx"
],
"json": [
".tsbuildinfo"
]
},
"pkg:vb": {
"vb": [
".vb",
".brs",
".vbs",
".bas",
".vba"
]
},
"pkg:xml": {
"xml": [
".xml",
".xsd",
".ascx",
".atom",
".axml",
".axaml",
".bpmn",
".cpt",
".csl",
".csproj",
".csproj.user",
".dita",
".ditamap",
".dtd",
".ent",
".mod",
".dtml",
".fsproj",
".fxml",
".iml",
".isml",
".jmx",
".launch",
".menu",
".mxml",
".nuspec",
".opml",
".owl",
".proj",
".props",
".pt",
".publishsettings",
".pubxml",
".pubxml.user",
".rbxlx",
".rbxmx",
".rdf",
".rng",
".rss",
".shproj",
".storyboard",
".svg",
".targets",
".tld",
".tmx",
".vbproj",
".vbproj.user",
".vcxproj",
".vcxproj.filters",
".wsdl",
".wxi",
".wxl",
".wxs",
".xaml",
".xbl",
".xib",
".xlf",
".xliff",
".xpdl",
".xul",
".xoml"
],
"xsl": [
".xsl",
".xslt"
]
},
"pkg:yaml": {
"yaml": [
".yaml",
".yml",
".eyaml",
".eyml",
".cff",
".yaml-tmlanguage",
".yaml-tmpreferences",
".yaml-tmtheme",
".winget"
]
}
}

@ -63,17 +63,32 @@ async function processMaterialFileIcons() {
}
fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-svgs.json`, import.meta.url)), JSON.stringify(svgSymbols, null, 2));
const vscodeExtensionsJson = await readFile(fileURLToPath(new URL(`generate-svg-vscode-extensions.json`, import.meta.url)));
const vscodeExtensions = JSON.parse(vscodeExtensionsJson);
const iconRulesJson = await readFile(fileURLToPath(new URL(`../node_modules/material-icon-theme/dist/material-icons.json`, import.meta.url)));
const iconRules = JSON.parse(iconRulesJson);
// The rules are from VSCode material-icon-theme, we need to adjust them to our needs
// 1. We only use lowercase filenames to match (it should be good enough for most cases and more efficient)
// 2. We do not have a "Language ID" system: https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers
// * So we just treat the "Language ID" as file extension, it is not always true, but it is good enough for most cases.
// 2. We do not have a "Language ID" system:
// * https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers
// * https://github.com/microsoft/vscode/tree/1.98.0/extensions
delete iconRules.iconDefinitions;
for (const [k, v] of Object.entries(iconRules.fileNames)) iconRules.fileNames[k.toLowerCase()] = v;
for (const [k, v] of Object.entries(iconRules.folderNames)) iconRules.folderNames[k.toLowerCase()] = v;
for (const [k, v] of Object.entries(iconRules.fileExtensions)) iconRules.fileExtensions[k.toLowerCase()] = v;
for (const [k, v] of Object.entries(iconRules.languageIds)) iconRules.fileExtensions[k.toLowerCase()] = v;
// Use VSCode's "Language ID" mapping from its extensions
for (const [_, langIdExtMap] of Object.entries(vscodeExtensions)) {
for (const [langId, names] of Object.entries(langIdExtMap)) {
for (const name of names) {
const nameLower = name.toLowerCase();
if (nameLower[0] === '.') {
iconRules.fileExtensions[nameLower.substring(1)] ??= langId;
} else {
iconRules.fileNames[nameLower] ??= langId;
}
}
}
}
const iconRulesPretty = JSON.stringify(iconRules, null, 2);
fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-rules.json`, import.meta.url)), iconRulesPretty);
}

Loading…
Cancel
Save