From f61f30153b51f2db50d96268e718a1319f5c03b2 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 11 Mar 2025 21:06:59 +0800 Subject: [PATCH] Fix file icon mapping (#33855) Use the file extension mapping from VSCode's extensions. Otherwise js/ts/vba/... files won't get correct icons. --- modules/fileicon/material.go | 28 +- modules/fileicon/material_test.go | 2 + options/fileicon/material-icon-rules.json | 300 ++++++++---- tools/generate-svg-vscode-extensions.json | 570 ++++++++++++++++++++++ tools/generate-svg.js | 21 +- 5 files changed, 811 insertions(+), 110 deletions(-) create mode 100644 tools/generate-svg-vscode-extensions.json diff --git a/modules/fileicon/material.go b/modules/fileicon/material.go index 201cf6f455..adea625c06 100644 --- a/modules/fileicon/material.go +++ b/modules/fileicon/material.go @@ -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 + } } } } diff --git a/modules/fileicon/material_test.go b/modules/fileicon/material_test.go index bce316c692..f36385aaf3 100644 --- a/modules/fileicon/material_test.go +++ b/modules/fileicon/material_test.go @@ -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)) } diff --git a/options/fileicon/material-icon-rules.json b/options/fileicon/material-icon-rules.json index 05d56cc546..fd058c82dc 100644 --- a/options/fileicon/material-icon-rules.json +++ b/options/fileicon/material-icon-rules.json @@ -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", - "diff": "diff", - "razor": "razor", - "aspnetcorerazor": "razor", - "python": "python", - "javascript": "javascript", - "typescript": "typescript", - "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", - "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", + "cljx": "clojure", "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" + "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", + "rej": "diff", + "fsscript": "fsharp", + "gitignore_global": "ignore", + "gitignore": "ignore", + "git-blame-ignore-revs": "ignore", + "gvy": "groovy", + "nf": "groovy", + "handlebars": "handlebars", + "hjs": "handlebars", + "hlsli": "hlsl", + "fx": "hlsl", + "fxh": "hlsl", + "vsh": "hlsl", + "psh": "hlsl", + "cginc": "hlsl", + "compute": "hlsl", + "html": "html", + "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", diff --git a/tools/generate-svg-vscode-extensions.json b/tools/generate-svg-vscode-extensions.json new file mode 100644 index 0000000000..b258d3f1a5 --- /dev/null +++ b/tools/generate-svg-vscode-extensions.json @@ -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" + ] + } +} diff --git a/tools/generate-svg.js b/tools/generate-svg.js index 7368392d01..ec04bf655e 100755 --- a/tools/generate-svg.js +++ b/tools/generate-svg.js @@ -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); }