diff --git a/.github/workflows/typos.toml b/.github/workflows/typos.toml index 9008a998a9..0aaf7c2547 100644 --- a/.github/workflows/typos.toml +++ b/.github/workflows/typos.toml @@ -30,6 +30,9 @@ HELO = "HELO" LKE = "LKE" byt = "byt" typ = "typ" +# file extensions used in seti icon theme +styl = "styl" +edn = "edn" Inferrable = "Inferrable" [files] diff --git a/site/src/components/FileIcon/FileIcon.tsx b/site/src/components/FileIcon/FileIcon.tsx new file mode 100644 index 0000000000..a204a7f5da --- /dev/null +++ b/site/src/components/FileIcon/FileIcon.tsx @@ -0,0 +1,286 @@ +import { type FC, useMemo } from "react"; +import setiIconTheme from "./seti-icon-theme.json"; + +interface SetiIconDefinition { + fontCharacter?: string; + fontColor?: string; +} + +interface FileIconGlyph { + character: string; + color?: string; +} + +const setiIconDefinitions: Record = + setiIconTheme.iconDefinitions; +const setiDefaultIconId = setiIconTheme.file; +const setiFileNames = setiIconTheme.fileNames as Record; +const setiFileExtensions = setiIconTheme.fileExtensions as Record< + string, + string +>; +const setiLanguageIds = setiIconTheme.languageIds as Record; + +const setiDefaultIconDefinition: SetiIconDefinition = setiIconDefinitions[ + setiDefaultIconId +] ?? { + fontCharacter: "\\E023", +}; + +const decodeFontCharacter = (encoded?: string): string => { + if (!encoded) { + return ""; + } + if (!encoded.startsWith("\\")) { + return encoded; + } + + const hex = encoded.slice(1); + const codePoint = Number.parseInt(hex, 16); + if (Number.isNaN(codePoint)) { + return ""; + } + + return String.fromCodePoint(codePoint); +}; + +const collectExtensionCandidates = (fileName: string): string[] => { + const parts = fileName.split("."); + if (parts.length <= 1) { + return []; + } + + const candidates: string[] = []; + for (let i = 1; i < parts.length; i++) { + const candidate = parts.slice(i).join("."); + if (candidate) { + candidates.push(candidate); + } + } + + return candidates; +}; + +/** + * Maps common file extensions to VS Code language identifiers + * when the extension alone doesn't match a key in the Seti + * theme's `languageIds` map. + */ +const extToLanguageId: Record = { + js: "javascript", + jsx: "javascriptreact", + mjs: "javascript", + cjs: "javascript", + py: "python", + rb: "ruby", + rs: "rust", + md: "markdown", + mdx: "markdown", + yml: "yaml", + sh: "shellscript", + bash: "shellscript", + zsh: "shellscript", + fish: "shellscript", + ps1: "powershell", + cs: "csharp", + fs: "fsharp", + kt: "kotlin", + kts: "kotlin", + swift: "swift", + pl: "perl", + php: "php", + ex: "elixir", + exs: "elixir", + erl: "erlang", + hrl: "erlang", + hs: "haskell", + lua: "lua", + vim: "viml", + clj: "clojure", + cljs: "clojure", + cljc: "clojure", + jl: "julia", + r: "r", + ml: "ocaml", + mli: "ocaml", + nim: "nim", + nix: "nix", + tf: "terraform", + tfvars: "terraform", + hcl: "terraform", + sql: "sql", + gql: "graphql", + graphql: "graphql", + proto: "proto3", + svg: "xml", + xml: "xml", + html: "html", + htm: "html", + css: "css", + scss: "scss", + sass: "sass", + less: "less", + styl: "stylus", + vue: "vue", + svelte: "svelte", + java: "java", + scala: "scala", + groovy: "groovy", + dart: "dart", + elm: "elm", + tpx: "typoscript", +}; + +const resolveSetiIconId = (fileName: string): string | undefined => { + const direct = setiFileNames[fileName]; + if (direct) { + return direct; + } + + const lowerName = fileName.toLowerCase(); + if (lowerName !== fileName) { + const lowerDirect = setiFileNames[lowerName]; + if (lowerDirect) { + return lowerDirect; + } + } + + if (fileName.startsWith(".") && fileName.length > 1) { + const withoutDot = fileName.slice(1); + const withoutDotMatch = setiFileNames[withoutDot]; + if (withoutDotMatch) { + return withoutDotMatch; + } + } + + const extensionCandidates = collectExtensionCandidates(fileName); + + for (const candidate of extensionCandidates) { + const extMatch = setiFileExtensions[candidate]; + if (extMatch) { + return extMatch; + } + + const lowerCandidate = candidate.toLowerCase(); + if (lowerCandidate !== candidate) { + const lowerExtMatch = setiFileExtensions[lowerCandidate]; + if (lowerExtMatch) { + return lowerExtMatch; + } + } + } + + const languageMatch = setiLanguageIds[lowerName]; + if (languageMatch) { + return languageMatch; + } + + for (const candidate of extensionCandidates) { + const languageIdMatch = setiLanguageIds[candidate]; + if (languageIdMatch) { + return languageIdMatch; + } + + const lowerCandidate = candidate.toLowerCase(); + if (lowerCandidate !== candidate) { + const lowerLanguageIdMatch = setiLanguageIds[lowerCandidate]; + if (lowerLanguageIdMatch) { + return lowerLanguageIdMatch; + } + } + } + + // Try mapping the extension to a known language identifier. + for (const candidate of extensionCandidates) { + const langId = extToLanguageId[candidate.toLowerCase()]; + if (langId) { + const langMatch = setiLanguageIds[langId]; + if (langMatch) { + return langMatch; + } + } + } + + if (fileName.startsWith(".") && fileName.length > 1) { + const trimmedLower = fileName.slice(1).toLowerCase(); + const trimmedLanguageMatch = setiLanguageIds[trimmedLower]; + if (trimmedLanguageMatch) { + return trimmedLanguageMatch; + } + } + + return undefined; +}; + +const getSetiIconForFile = (fileName: string): FileIconGlyph => { + if (!fileName) { + return { + character: + decodeFontCharacter(setiDefaultIconDefinition.fontCharacter) || " ", + color: setiDefaultIconDefinition.fontColor, + }; + } + + const iconId = resolveSetiIconId(fileName); + const iconDefinition = iconId ? setiIconDefinitions[iconId] : undefined; + + return { + character: + decodeFontCharacter(iconDefinition?.fontCharacter) || + decodeFontCharacter(setiDefaultIconDefinition.fontCharacter) || + " ", + color: iconDefinition?.fontColor ?? setiDefaultIconDefinition.fontColor, + }; +}; + +const BASE_ICON_STYLE: React.CSSProperties = { + fontFamily: + '"seti", "Geist Mono Variable", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace', + fontSize: 22, + lineHeight: 1, + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + minWidth: "1.375rem", + height: "1.375rem", + userSelect: "none", + fontStyle: "normal", + fontWeight: "normal", + letterSpacing: "normal", +}; + +interface FileIconProps { + fileName?: string | null; + filePath?: string | null; + className?: string; + style?: React.CSSProperties; +} + +export const FileIcon: FC = ({ + fileName, + filePath, + className, + style, +}) => { + const targetName = + fileName ?? (filePath ? (filePath.split("/").pop() ?? "") : ""); + + const icon = useMemo( + () => getSetiIconForFile(targetName ?? ""), + [targetName], + ); + + if (!icon.character.trim()) { + return null; + } + + return ( + + ); +}; diff --git a/site/src/components/FileIcon/seti-icon-theme.json b/site/src/components/FileIcon/seti-icon-theme.json new file mode 100644 index 0000000000..6154b48b34 --- /dev/null +++ b/site/src/components/FileIcon/seti-icon-theme.json @@ -0,0 +1,2408 @@ +{ + "information_for_contributors": [ + "This file has been generated from data in https://github.com/jesseweed/seti-ui", + "- icon definitions: https://github.com/jesseweed/seti-ui/blob/master/styles/_fonts/seti.less", + "- icon colors: https://github.com/jesseweed/seti-ui/blob/master/styles/ui-variables.less", + "- file associations: https://github.com/jesseweed/seti-ui/blob/master/styles/components/icons/mapping.less", + "If you want to provide a fix or improvement, please create a pull request against the jesseweed/seti-ui repository.", + "Once accepted there, we are happy to receive an update request." + ], + "fonts": [ + { + "id": "seti", + "src": [ + { + "path": "./seti.woff", + "format": "woff" + } + ], + "weight": "normal", + "style": "normal", + "size": "150%" + } + ], + "iconDefinitions": { + "_R_light": { + "fontCharacter": "\\E001", + "fontColor": "#498ba7" + }, + "_R": { + "fontCharacter": "\\E001", + "fontColor": "#519aba" + }, + "_argdown_light": { + "fontCharacter": "\\E003", + "fontColor": "#498ba7" + }, + "_argdown": { + "fontCharacter": "\\E003", + "fontColor": "#519aba" + }, + "_asm_light": { + "fontCharacter": "\\E004", + "fontColor": "#b8383d" + }, + "_asm": { + "fontCharacter": "\\E004", + "fontColor": "#cc3e44" + }, + "_audio_light": { + "fontCharacter": "\\E005", + "fontColor": "#9068b0" + }, + "_audio": { + "fontCharacter": "\\E005", + "fontColor": "#a074c4" + }, + "_babel_light": { + "fontCharacter": "\\E006", + "fontColor": "#b7b73b" + }, + "_babel": { + "fontCharacter": "\\E006", + "fontColor": "#cbcb41" + }, + "_bazel_light": { + "fontCharacter": "\\E007", + "fontColor": "#7fae42" + }, + "_bazel": { + "fontCharacter": "\\E007", + "fontColor": "#8dc149" + }, + "_bazel_1_light": { + "fontCharacter": "\\E007", + "fontColor": "#455155" + }, + "_bazel_1": { + "fontCharacter": "\\E007", + "fontColor": "#4d5a5e" + }, + "_bicep_light": { + "fontCharacter": "\\E008", + "fontColor": "#498ba7" + }, + "_bicep": { + "fontCharacter": "\\E008", + "fontColor": "#519aba" + }, + "_bower_light": { + "fontCharacter": "\\E009", + "fontColor": "#cc6d2e" + }, + "_bower": { + "fontCharacter": "\\E009", + "fontColor": "#e37933" + }, + "_bsl_light": { + "fontCharacter": "\\E00A", + "fontColor": "#b8383d" + }, + "_bsl": { + "fontCharacter": "\\E00A", + "fontColor": "#cc3e44" + }, + "_c_light": { + "fontCharacter": "\\E00C", + "fontColor": "#498ba7" + }, + "_c": { + "fontCharacter": "\\E00C", + "fontColor": "#519aba" + }, + "_c-sharp_light": { + "fontCharacter": "\\E00B", + "fontColor": "#498ba7" + }, + "_c-sharp": { + "fontCharacter": "\\E00B", + "fontColor": "#519aba" + }, + "_c_1_light": { + "fontCharacter": "\\E00C", + "fontColor": "#9068b0" + }, + "_c_1": { + "fontCharacter": "\\E00C", + "fontColor": "#a074c4" + }, + "_c_2_light": { + "fontCharacter": "\\E00C", + "fontColor": "#b7b73b" + }, + "_c_2": { + "fontCharacter": "\\E00C", + "fontColor": "#cbcb41" + }, + "_cake_light": { + "fontCharacter": "\\E00D", + "fontColor": "#b8383d" + }, + "_cake": { + "fontCharacter": "\\E00D", + "fontColor": "#cc3e44" + }, + "_cake_php_light": { + "fontCharacter": "\\E00E", + "fontColor": "#b8383d" + }, + "_cake_php": { + "fontCharacter": "\\E00E", + "fontColor": "#cc3e44" + }, + "_clock_light": { + "fontCharacter": "\\E012", + "fontColor": "#498ba7" + }, + "_clock": { + "fontCharacter": "\\E012", + "fontColor": "#519aba" + }, + "_clock_1_light": { + "fontCharacter": "\\E012", + "fontColor": "#627379" + }, + "_clock_1": { + "fontCharacter": "\\E012", + "fontColor": "#6d8086" + }, + "_clojure_light": { + "fontCharacter": "\\E013", + "fontColor": "#7fae42" + }, + "_clojure": { + "fontCharacter": "\\E013", + "fontColor": "#8dc149" + }, + "_clojure_1_light": { + "fontCharacter": "\\E013", + "fontColor": "#498ba7" + }, + "_clojure_1": { + "fontCharacter": "\\E013", + "fontColor": "#519aba" + }, + "_code-climate_light": { + "fontCharacter": "\\E014", + "fontColor": "#7fae42" + }, + "_code-climate": { + "fontCharacter": "\\E014", + "fontColor": "#8dc149" + }, + "_code-search_light": { + "fontCharacter": "\\E015", + "fontColor": "#9068b0" + }, + "_code-search": { + "fontCharacter": "\\E015", + "fontColor": "#a074c4" + }, + "_coffee_light": { + "fontCharacter": "\\E016", + "fontColor": "#b7b73b" + }, + "_coffee": { + "fontCharacter": "\\E016", + "fontColor": "#cbcb41" + }, + "_coldfusion_light": { + "fontCharacter": "\\E018", + "fontColor": "#498ba7" + }, + "_coldfusion": { + "fontCharacter": "\\E018", + "fontColor": "#519aba" + }, + "_config_light": { + "fontCharacter": "\\E019", + "fontColor": "#627379" + }, + "_config": { + "fontCharacter": "\\E019", + "fontColor": "#6d8086" + }, + "_cpp_light": { + "fontCharacter": "\\E01A", + "fontColor": "#498ba7" + }, + "_cpp": { + "fontCharacter": "\\E01A", + "fontColor": "#519aba" + }, + "_cpp_1_light": { + "fontCharacter": "\\E01A", + "fontColor": "#9068b0" + }, + "_cpp_1": { + "fontCharacter": "\\E01A", + "fontColor": "#a074c4" + }, + "_cpp_2_light": { + "fontCharacter": "\\E01A", + "fontColor": "#b7b73b" + }, + "_cpp_2": { + "fontCharacter": "\\E01A", + "fontColor": "#cbcb41" + }, + "_crystal_light": { + "fontCharacter": "\\E01B", + "fontColor": "#bfc2c1" + }, + "_crystal": { + "fontCharacter": "\\E01B", + "fontColor": "#d4d7d6" + }, + "_crystal_embedded_light": { + "fontCharacter": "\\E01C", + "fontColor": "#bfc2c1" + }, + "_crystal_embedded": { + "fontCharacter": "\\E01C", + "fontColor": "#d4d7d6" + }, + "_css_light": { + "fontCharacter": "\\E01D", + "fontColor": "#498ba7" + }, + "_css": { + "fontCharacter": "\\E01D", + "fontColor": "#519aba" + }, + "_csv_light": { + "fontCharacter": "\\E01E", + "fontColor": "#7fae42" + }, + "_csv": { + "fontCharacter": "\\E01E", + "fontColor": "#8dc149" + }, + "_cu_light": { + "fontCharacter": "\\E01F", + "fontColor": "#7fae42" + }, + "_cu": { + "fontCharacter": "\\E01F", + "fontColor": "#8dc149" + }, + "_cu_1_light": { + "fontCharacter": "\\E01F", + "fontColor": "#9068b0" + }, + "_cu_1": { + "fontCharacter": "\\E01F", + "fontColor": "#a074c4" + }, + "_d_light": { + "fontCharacter": "\\E020", + "fontColor": "#b8383d" + }, + "_d": { + "fontCharacter": "\\E020", + "fontColor": "#cc3e44" + }, + "_dart_light": { + "fontCharacter": "\\E021", + "fontColor": "#498ba7" + }, + "_dart": { + "fontCharacter": "\\E021", + "fontColor": "#519aba" + }, + "_db_light": { + "fontCharacter": "\\E022", + "fontColor": "#dd4b78" + }, + "_db": { + "fontCharacter": "\\E022", + "fontColor": "#f55385" + }, + "_db_1_light": { + "fontCharacter": "\\E022", + "fontColor": "#498ba7" + }, + "_db_1": { + "fontCharacter": "\\E022", + "fontColor": "#519aba" + }, + "_default_light": { + "fontCharacter": "\\E023", + "fontColor": "#bfc2c1" + }, + "_default": { + "fontCharacter": "\\E023", + "fontColor": "#d4d7d6" + }, + "_docker_light": { + "fontCharacter": "\\E025", + "fontColor": "#498ba7" + }, + "_docker": { + "fontCharacter": "\\E025", + "fontColor": "#519aba" + }, + "_docker_1_light": { + "fontCharacter": "\\E025", + "fontColor": "#455155" + }, + "_docker_1": { + "fontCharacter": "\\E025", + "fontColor": "#4d5a5e" + }, + "_docker_2_light": { + "fontCharacter": "\\E025", + "fontColor": "#7fae42" + }, + "_docker_2": { + "fontCharacter": "\\E025", + "fontColor": "#8dc149" + }, + "_docker_3_light": { + "fontCharacter": "\\E025", + "fontColor": "#dd4b78" + }, + "_docker_3": { + "fontCharacter": "\\E025", + "fontColor": "#f55385" + }, + "_ejs_light": { + "fontCharacter": "\\E027", + "fontColor": "#b7b73b" + }, + "_ejs": { + "fontCharacter": "\\E027", + "fontColor": "#cbcb41" + }, + "_elixir_light": { + "fontCharacter": "\\E028", + "fontColor": "#9068b0" + }, + "_elixir": { + "fontCharacter": "\\E028", + "fontColor": "#a074c4" + }, + "_elixir_script_light": { + "fontCharacter": "\\E029", + "fontColor": "#9068b0" + }, + "_elixir_script": { + "fontCharacter": "\\E029", + "fontColor": "#a074c4" + }, + "_elm_light": { + "fontCharacter": "\\E02A", + "fontColor": "#498ba7" + }, + "_elm": { + "fontCharacter": "\\E02A", + "fontColor": "#519aba" + }, + "_eslint_light": { + "fontCharacter": "\\E02C", + "fontColor": "#9068b0" + }, + "_eslint": { + "fontCharacter": "\\E02C", + "fontColor": "#a074c4" + }, + "_eslint_1_light": { + "fontCharacter": "\\E02C", + "fontColor": "#455155" + }, + "_eslint_1": { + "fontCharacter": "\\E02C", + "fontColor": "#4d5a5e" + }, + "_ethereum_light": { + "fontCharacter": "\\E02D", + "fontColor": "#498ba7" + }, + "_ethereum": { + "fontCharacter": "\\E02D", + "fontColor": "#519aba" + }, + "_f-sharp_light": { + "fontCharacter": "\\E02E", + "fontColor": "#498ba7" + }, + "_f-sharp": { + "fontCharacter": "\\E02E", + "fontColor": "#519aba" + }, + "_favicon_light": { + "fontCharacter": "\\E02F", + "fontColor": "#b7b73b" + }, + "_favicon": { + "fontCharacter": "\\E02F", + "fontColor": "#cbcb41" + }, + "_firebase_light": { + "fontCharacter": "\\E030", + "fontColor": "#cc6d2e" + }, + "_firebase": { + "fontCharacter": "\\E030", + "fontColor": "#e37933" + }, + "_firefox_light": { + "fontCharacter": "\\E031", + "fontColor": "#cc6d2e" + }, + "_firefox": { + "fontCharacter": "\\E031", + "fontColor": "#e37933" + }, + "_font_light": { + "fontCharacter": "\\E033", + "fontColor": "#b8383d" + }, + "_font": { + "fontCharacter": "\\E033", + "fontColor": "#cc3e44" + }, + "_git_light": { + "fontCharacter": "\\E034", + "fontColor": "#3b4b52" + }, + "_git": { + "fontCharacter": "\\E034", + "fontColor": "#41535b" + }, + "_github_light": { + "fontCharacter": "\\E037", + "fontColor": "#bfc2c1" + }, + "_github": { + "fontCharacter": "\\E037", + "fontColor": "#d4d7d6" + }, + "_gitlab_light": { + "fontCharacter": "\\E038", + "fontColor": "#cc6d2e" + }, + "_gitlab": { + "fontCharacter": "\\E038", + "fontColor": "#e37933" + }, + "_go_light": { + "fontCharacter": "\\E039", + "fontColor": "#498ba7" + }, + "_go": { + "fontCharacter": "\\E039", + "fontColor": "#519aba" + }, + "_go2_light": { + "fontCharacter": "\\E03A", + "fontColor": "#498ba7" + }, + "_go2": { + "fontCharacter": "\\E03A", + "fontColor": "#519aba" + }, + "_godot_light": { + "fontCharacter": "\\E03B", + "fontColor": "#498ba7" + }, + "_godot": { + "fontCharacter": "\\E03B", + "fontColor": "#519aba" + }, + "_godot_1_light": { + "fontCharacter": "\\E03B", + "fontColor": "#b8383d" + }, + "_godot_1": { + "fontCharacter": "\\E03B", + "fontColor": "#cc3e44" + }, + "_godot_2_light": { + "fontCharacter": "\\E03B", + "fontColor": "#b7b73b" + }, + "_godot_2": { + "fontCharacter": "\\E03B", + "fontColor": "#cbcb41" + }, + "_godot_3_light": { + "fontCharacter": "\\E03B", + "fontColor": "#9068b0" + }, + "_godot_3": { + "fontCharacter": "\\E03B", + "fontColor": "#a074c4" + }, + "_gradle_light": { + "fontCharacter": "\\E03C", + "fontColor": "#498ba7" + }, + "_gradle": { + "fontCharacter": "\\E03C", + "fontColor": "#519aba" + }, + "_grails_light": { + "fontCharacter": "\\E03D", + "fontColor": "#7fae42" + }, + "_grails": { + "fontCharacter": "\\E03D", + "fontColor": "#8dc149" + }, + "_graphql_light": { + "fontCharacter": "\\E03E", + "fontColor": "#dd4b78" + }, + "_graphql": { + "fontCharacter": "\\E03E", + "fontColor": "#f55385" + }, + "_grunt_light": { + "fontCharacter": "\\E03F", + "fontColor": "#cc6d2e" + }, + "_grunt": { + "fontCharacter": "\\E03F", + "fontColor": "#e37933" + }, + "_gulp_light": { + "fontCharacter": "\\E040", + "fontColor": "#b8383d" + }, + "_gulp": { + "fontCharacter": "\\E040", + "fontColor": "#cc3e44" + }, + "_hacklang_light": { + "fontCharacter": "\\E041", + "fontColor": "#cc6d2e" + }, + "_hacklang": { + "fontCharacter": "\\E041", + "fontColor": "#e37933" + }, + "_haml_light": { + "fontCharacter": "\\E042", + "fontColor": "#b8383d" + }, + "_haml": { + "fontCharacter": "\\E042", + "fontColor": "#cc3e44" + }, + "_happenings_light": { + "fontCharacter": "\\E043", + "fontColor": "#498ba7" + }, + "_happenings": { + "fontCharacter": "\\E043", + "fontColor": "#519aba" + }, + "_haskell_light": { + "fontCharacter": "\\E044", + "fontColor": "#9068b0" + }, + "_haskell": { + "fontCharacter": "\\E044", + "fontColor": "#a074c4" + }, + "_haxe_light": { + "fontCharacter": "\\E045", + "fontColor": "#cc6d2e" + }, + "_haxe": { + "fontCharacter": "\\E045", + "fontColor": "#e37933" + }, + "_haxe_1_light": { + "fontCharacter": "\\E045", + "fontColor": "#b7b73b" + }, + "_haxe_1": { + "fontCharacter": "\\E045", + "fontColor": "#cbcb41" + }, + "_haxe_2_light": { + "fontCharacter": "\\E045", + "fontColor": "#498ba7" + }, + "_haxe_2": { + "fontCharacter": "\\E045", + "fontColor": "#519aba" + }, + "_haxe_3_light": { + "fontCharacter": "\\E045", + "fontColor": "#9068b0" + }, + "_haxe_3": { + "fontCharacter": "\\E045", + "fontColor": "#a074c4" + }, + "_heroku_light": { + "fontCharacter": "\\E046", + "fontColor": "#9068b0" + }, + "_heroku": { + "fontCharacter": "\\E046", + "fontColor": "#a074c4" + }, + "_hex_light": { + "fontCharacter": "\\E047", + "fontColor": "#b8383d" + }, + "_hex": { + "fontCharacter": "\\E047", + "fontColor": "#cc3e44" + }, + "_html_light": { + "fontCharacter": "\\E048", + "fontColor": "#498ba7" + }, + "_html": { + "fontCharacter": "\\E048", + "fontColor": "#519aba" + }, + "_html_1_light": { + "fontCharacter": "\\E048", + "fontColor": "#7fae42" + }, + "_html_1": { + "fontCharacter": "\\E048", + "fontColor": "#8dc149" + }, + "_html_2_light": { + "fontCharacter": "\\E048", + "fontColor": "#b7b73b" + }, + "_html_2": { + "fontCharacter": "\\E048", + "fontColor": "#cbcb41" + }, + "_html_3_light": { + "fontCharacter": "\\E048", + "fontColor": "#cc6d2e" + }, + "_html_3": { + "fontCharacter": "\\E048", + "fontColor": "#e37933" + }, + "_html_erb_light": { + "fontCharacter": "\\E049", + "fontColor": "#b8383d" + }, + "_html_erb": { + "fontCharacter": "\\E049", + "fontColor": "#cc3e44" + }, + "_ignored_light": { + "fontCharacter": "\\E04A", + "fontColor": "#3b4b52" + }, + "_ignored": { + "fontCharacter": "\\E04A", + "fontColor": "#41535b" + }, + "_illustrator_light": { + "fontCharacter": "\\E04B", + "fontColor": "#b7b73b" + }, + "_illustrator": { + "fontCharacter": "\\E04B", + "fontColor": "#cbcb41" + }, + "_image_light": { + "fontCharacter": "\\E04C", + "fontColor": "#9068b0" + }, + "_image": { + "fontCharacter": "\\E04C", + "fontColor": "#a074c4" + }, + "_info_light": { + "fontCharacter": "\\E04D", + "fontColor": "#498ba7" + }, + "_info": { + "fontCharacter": "\\E04D", + "fontColor": "#519aba" + }, + "_ionic_light": { + "fontCharacter": "\\E04E", + "fontColor": "#498ba7" + }, + "_ionic": { + "fontCharacter": "\\E04E", + "fontColor": "#519aba" + }, + "_jade_light": { + "fontCharacter": "\\E04F", + "fontColor": "#b8383d" + }, + "_jade": { + "fontCharacter": "\\E04F", + "fontColor": "#cc3e44" + }, + "_java_light": { + "fontCharacter": "\\E050", + "fontColor": "#b8383d" + }, + "_java": { + "fontCharacter": "\\E050", + "fontColor": "#cc3e44" + }, + "_java_1_light": { + "fontCharacter": "\\E050", + "fontColor": "#498ba7" + }, + "_java_1": { + "fontCharacter": "\\E050", + "fontColor": "#519aba" + }, + "_javascript_light": { + "fontCharacter": "\\E051", + "fontColor": "#b7b73b" + }, + "_javascript": { + "fontCharacter": "\\E051", + "fontColor": "#cbcb41" + }, + "_javascript_1_light": { + "fontCharacter": "\\E051", + "fontColor": "#cc6d2e" + }, + "_javascript_1": { + "fontCharacter": "\\E051", + "fontColor": "#e37933" + }, + "_javascript_2_light": { + "fontCharacter": "\\E051", + "fontColor": "#498ba7" + }, + "_javascript_2": { + "fontCharacter": "\\E051", + "fontColor": "#519aba" + }, + "_jenkins_light": { + "fontCharacter": "\\E052", + "fontColor": "#b8383d" + }, + "_jenkins": { + "fontCharacter": "\\E052", + "fontColor": "#cc3e44" + }, + "_jinja_light": { + "fontCharacter": "\\E053", + "fontColor": "#b8383d" + }, + "_jinja": { + "fontCharacter": "\\E053", + "fontColor": "#cc3e44" + }, + "_json_light": { + "fontCharacter": "\\E055", + "fontColor": "#b7b73b" + }, + "_json": { + "fontCharacter": "\\E055", + "fontColor": "#cbcb41" + }, + "_json_1_light": { + "fontCharacter": "\\E055", + "fontColor": "#7fae42" + }, + "_json_1": { + "fontCharacter": "\\E055", + "fontColor": "#8dc149" + }, + "_julia_light": { + "fontCharacter": "\\E056", + "fontColor": "#9068b0" + }, + "_julia": { + "fontCharacter": "\\E056", + "fontColor": "#a074c4" + }, + "_karma_light": { + "fontCharacter": "\\E057", + "fontColor": "#7fae42" + }, + "_karma": { + "fontCharacter": "\\E057", + "fontColor": "#8dc149" + }, + "_kotlin_light": { + "fontCharacter": "\\E058", + "fontColor": "#cc6d2e" + }, + "_kotlin": { + "fontCharacter": "\\E058", + "fontColor": "#e37933" + }, + "_less_light": { + "fontCharacter": "\\E059", + "fontColor": "#498ba7" + }, + "_less": { + "fontCharacter": "\\E059", + "fontColor": "#519aba" + }, + "_license_light": { + "fontCharacter": "\\E05A", + "fontColor": "#b7b73b" + }, + "_license": { + "fontCharacter": "\\E05A", + "fontColor": "#cbcb41" + }, + "_license_1_light": { + "fontCharacter": "\\E05A", + "fontColor": "#cc6d2e" + }, + "_license_1": { + "fontCharacter": "\\E05A", + "fontColor": "#e37933" + }, + "_license_2_light": { + "fontCharacter": "\\E05A", + "fontColor": "#b8383d" + }, + "_license_2": { + "fontCharacter": "\\E05A", + "fontColor": "#cc3e44" + }, + "_liquid_light": { + "fontCharacter": "\\E05B", + "fontColor": "#7fae42" + }, + "_liquid": { + "fontCharacter": "\\E05B", + "fontColor": "#8dc149" + }, + "_livescript_light": { + "fontCharacter": "\\E05C", + "fontColor": "#498ba7" + }, + "_livescript": { + "fontCharacter": "\\E05C", + "fontColor": "#519aba" + }, + "_lock_light": { + "fontCharacter": "\\E05D", + "fontColor": "#7fae42" + }, + "_lock": { + "fontCharacter": "\\E05D", + "fontColor": "#8dc149" + }, + "_lua_light": { + "fontCharacter": "\\E05E", + "fontColor": "#498ba7" + }, + "_lua": { + "fontCharacter": "\\E05E", + "fontColor": "#519aba" + }, + "_makefile_light": { + "fontCharacter": "\\E05F", + "fontColor": "#cc6d2e" + }, + "_makefile": { + "fontCharacter": "\\E05F", + "fontColor": "#e37933" + }, + "_makefile_1_light": { + "fontCharacter": "\\E05F", + "fontColor": "#9068b0" + }, + "_makefile_1": { + "fontCharacter": "\\E05F", + "fontColor": "#a074c4" + }, + "_makefile_2_light": { + "fontCharacter": "\\E05F", + "fontColor": "#627379" + }, + "_makefile_2": { + "fontCharacter": "\\E05F", + "fontColor": "#6d8086" + }, + "_makefile_3_light": { + "fontCharacter": "\\E05F", + "fontColor": "#498ba7" + }, + "_makefile_3": { + "fontCharacter": "\\E05F", + "fontColor": "#519aba" + }, + "_markdown_light": { + "fontCharacter": "\\E060", + "fontColor": "#498ba7" + }, + "_markdown": { + "fontCharacter": "\\E060", + "fontColor": "#519aba" + }, + "_maven_light": { + "fontCharacter": "\\E061", + "fontColor": "#b8383d" + }, + "_maven": { + "fontCharacter": "\\E061", + "fontColor": "#cc3e44" + }, + "_mdo_light": { + "fontCharacter": "\\E062", + "fontColor": "#b8383d" + }, + "_mdo": { + "fontCharacter": "\\E062", + "fontColor": "#cc3e44" + }, + "_mustache_light": { + "fontCharacter": "\\E063", + "fontColor": "#cc6d2e" + }, + "_mustache": { + "fontCharacter": "\\E063", + "fontColor": "#e37933" + }, + "_nim_light": { + "fontCharacter": "\\E065", + "fontColor": "#b7b73b" + }, + "_nim": { + "fontCharacter": "\\E065", + "fontColor": "#cbcb41" + }, + "_notebook_light": { + "fontCharacter": "\\E066", + "fontColor": "#498ba7" + }, + "_notebook": { + "fontCharacter": "\\E066", + "fontColor": "#519aba" + }, + "_npm_light": { + "fontCharacter": "\\E067", + "fontColor": "#3b4b52" + }, + "_npm": { + "fontCharacter": "\\E067", + "fontColor": "#41535b" + }, + "_npm_1_light": { + "fontCharacter": "\\E067", + "fontColor": "#b8383d" + }, + "_npm_1": { + "fontCharacter": "\\E067", + "fontColor": "#cc3e44" + }, + "_npm_ignored_light": { + "fontCharacter": "\\E068", + "fontColor": "#3b4b52" + }, + "_npm_ignored": { + "fontCharacter": "\\E068", + "fontColor": "#41535b" + }, + "_nunjucks_light": { + "fontCharacter": "\\E069", + "fontColor": "#7fae42" + }, + "_nunjucks": { + "fontCharacter": "\\E069", + "fontColor": "#8dc149" + }, + "_ocaml_light": { + "fontCharacter": "\\E06A", + "fontColor": "#cc6d2e" + }, + "_ocaml": { + "fontCharacter": "\\E06A", + "fontColor": "#e37933" + }, + "_odata_light": { + "fontCharacter": "\\E06B", + "fontColor": "#cc6d2e" + }, + "_odata": { + "fontCharacter": "\\E06B", + "fontColor": "#e37933" + }, + "_pddl_light": { + "fontCharacter": "\\E06C", + "fontColor": "#9068b0" + }, + "_pddl": { + "fontCharacter": "\\E06C", + "fontColor": "#a074c4" + }, + "_pdf_light": { + "fontCharacter": "\\E06D", + "fontColor": "#b8383d" + }, + "_pdf": { + "fontCharacter": "\\E06D", + "fontColor": "#cc3e44" + }, + "_perl_light": { + "fontCharacter": "\\E06E", + "fontColor": "#498ba7" + }, + "_perl": { + "fontCharacter": "\\E06E", + "fontColor": "#519aba" + }, + "_photoshop_light": { + "fontCharacter": "\\E06F", + "fontColor": "#498ba7" + }, + "_photoshop": { + "fontCharacter": "\\E06F", + "fontColor": "#519aba" + }, + "_php_light": { + "fontCharacter": "\\E070", + "fontColor": "#9068b0" + }, + "_php": { + "fontCharacter": "\\E070", + "fontColor": "#a074c4" + }, + "_pipeline_light": { + "fontCharacter": "\\E071", + "fontColor": "#cc6d2e" + }, + "_pipeline": { + "fontCharacter": "\\E071", + "fontColor": "#e37933" + }, + "_plan_light": { + "fontCharacter": "\\E072", + "fontColor": "#7fae42" + }, + "_plan": { + "fontCharacter": "\\E072", + "fontColor": "#8dc149" + }, + "_platformio_light": { + "fontCharacter": "\\E073", + "fontColor": "#cc6d2e" + }, + "_platformio": { + "fontCharacter": "\\E073", + "fontColor": "#e37933" + }, + "_powershell_light": { + "fontCharacter": "\\E074", + "fontColor": "#498ba7" + }, + "_powershell": { + "fontCharacter": "\\E074", + "fontColor": "#519aba" + }, + "_prisma_light": { + "fontCharacter": "\\E075", + "fontColor": "#498ba7" + }, + "_prisma": { + "fontCharacter": "\\E075", + "fontColor": "#519aba" + }, + "_prolog_light": { + "fontCharacter": "\\E077", + "fontColor": "#cc6d2e" + }, + "_prolog": { + "fontCharacter": "\\E077", + "fontColor": "#e37933" + }, + "_pug_light": { + "fontCharacter": "\\E078", + "fontColor": "#b8383d" + }, + "_pug": { + "fontCharacter": "\\E078", + "fontColor": "#cc3e44" + }, + "_puppet_light": { + "fontCharacter": "\\E079", + "fontColor": "#b7b73b" + }, + "_puppet": { + "fontCharacter": "\\E079", + "fontColor": "#cbcb41" + }, + "_purescript_light": { + "fontCharacter": "\\E07A", + "fontColor": "#bfc2c1" + }, + "_purescript": { + "fontCharacter": "\\E07A", + "fontColor": "#d4d7d6" + }, + "_python_light": { + "fontCharacter": "\\E07B", + "fontColor": "#498ba7" + }, + "_python": { + "fontCharacter": "\\E07B", + "fontColor": "#519aba" + }, + "_react_light": { + "fontCharacter": "\\E07D", + "fontColor": "#498ba7" + }, + "_react": { + "fontCharacter": "\\E07D", + "fontColor": "#519aba" + }, + "_react_1_light": { + "fontCharacter": "\\E07D", + "fontColor": "#cc6d2e" + }, + "_react_1": { + "fontCharacter": "\\E07D", + "fontColor": "#e37933" + }, + "_reasonml_light": { + "fontCharacter": "\\E07E", + "fontColor": "#b8383d" + }, + "_reasonml": { + "fontCharacter": "\\E07E", + "fontColor": "#cc3e44" + }, + "_rescript_light": { + "fontCharacter": "\\E07F", + "fontColor": "#b8383d" + }, + "_rescript": { + "fontCharacter": "\\E07F", + "fontColor": "#cc3e44" + }, + "_rescript_1_light": { + "fontCharacter": "\\E07F", + "fontColor": "#dd4b78" + }, + "_rescript_1": { + "fontCharacter": "\\E07F", + "fontColor": "#f55385" + }, + "_rollup_light": { + "fontCharacter": "\\E080", + "fontColor": "#b8383d" + }, + "_rollup": { + "fontCharacter": "\\E080", + "fontColor": "#cc3e44" + }, + "_ruby_light": { + "fontCharacter": "\\E081", + "fontColor": "#b8383d" + }, + "_ruby": { + "fontCharacter": "\\E081", + "fontColor": "#cc3e44" + }, + "_rust_light": { + "fontCharacter": "\\E082", + "fontColor": "#627379" + }, + "_rust": { + "fontCharacter": "\\E082", + "fontColor": "#6d8086" + }, + "_salesforce_light": { + "fontCharacter": "\\E083", + "fontColor": "#498ba7" + }, + "_salesforce": { + "fontCharacter": "\\E083", + "fontColor": "#519aba" + }, + "_sass_light": { + "fontCharacter": "\\E084", + "fontColor": "#dd4b78" + }, + "_sass": { + "fontCharacter": "\\E084", + "fontColor": "#f55385" + }, + "_sbt_light": { + "fontCharacter": "\\E085", + "fontColor": "#498ba7" + }, + "_sbt": { + "fontCharacter": "\\E085", + "fontColor": "#519aba" + }, + "_scala_light": { + "fontCharacter": "\\E086", + "fontColor": "#b8383d" + }, + "_scala": { + "fontCharacter": "\\E086", + "fontColor": "#cc3e44" + }, + "_shell_light": { + "fontCharacter": "\\E089", + "fontColor": "#7fae42" + }, + "_shell": { + "fontCharacter": "\\E089", + "fontColor": "#8dc149" + }, + "_slim_light": { + "fontCharacter": "\\E08A", + "fontColor": "#cc6d2e" + }, + "_slim": { + "fontCharacter": "\\E08A", + "fontColor": "#e37933" + }, + "_smarty_light": { + "fontCharacter": "\\E08B", + "fontColor": "#b7b73b" + }, + "_smarty": { + "fontCharacter": "\\E08B", + "fontColor": "#cbcb41" + }, + "_spring_light": { + "fontCharacter": "\\E08C", + "fontColor": "#7fae42" + }, + "_spring": { + "fontCharacter": "\\E08C", + "fontColor": "#8dc149" + }, + "_stylelint_light": { + "fontCharacter": "\\E08D", + "fontColor": "#bfc2c1" + }, + "_stylelint": { + "fontCharacter": "\\E08D", + "fontColor": "#d4d7d6" + }, + "_stylelint_1_light": { + "fontCharacter": "\\E08D", + "fontColor": "#455155" + }, + "_stylelint_1": { + "fontCharacter": "\\E08D", + "fontColor": "#4d5a5e" + }, + "_stylus_light": { + "fontCharacter": "\\E08E", + "fontColor": "#7fae42" + }, + "_stylus": { + "fontCharacter": "\\E08E", + "fontColor": "#8dc149" + }, + "_sublime_light": { + "fontCharacter": "\\E08F", + "fontColor": "#cc6d2e" + }, + "_sublime": { + "fontCharacter": "\\E08F", + "fontColor": "#e37933" + }, + "_svelte_light": { + "fontCharacter": "\\E090", + "fontColor": "#b8383d" + }, + "_svelte": { + "fontCharacter": "\\E090", + "fontColor": "#cc3e44" + }, + "_svg_light": { + "fontCharacter": "\\E091", + "fontColor": "#9068b0" + }, + "_svg": { + "fontCharacter": "\\E091", + "fontColor": "#a074c4" + }, + "_svg_1_light": { + "fontCharacter": "\\E091", + "fontColor": "#498ba7" + }, + "_svg_1": { + "fontCharacter": "\\E091", + "fontColor": "#519aba" + }, + "_swift_light": { + "fontCharacter": "\\E092", + "fontColor": "#cc6d2e" + }, + "_swift": { + "fontCharacter": "\\E092", + "fontColor": "#e37933" + }, + "_terraform_light": { + "fontCharacter": "\\E093", + "fontColor": "#9068b0" + }, + "_terraform": { + "fontCharacter": "\\E093", + "fontColor": "#a074c4" + }, + "_tex_light": { + "fontCharacter": "\\E094", + "fontColor": "#498ba7" + }, + "_tex": { + "fontCharacter": "\\E094", + "fontColor": "#519aba" + }, + "_tex_1_light": { + "fontCharacter": "\\E094", + "fontColor": "#b7b73b" + }, + "_tex_1": { + "fontCharacter": "\\E094", + "fontColor": "#cbcb41" + }, + "_tex_2_light": { + "fontCharacter": "\\E094", + "fontColor": "#cc6d2e" + }, + "_tex_2": { + "fontCharacter": "\\E094", + "fontColor": "#e37933" + }, + "_tex_3_light": { + "fontCharacter": "\\E094", + "fontColor": "#bfc2c1" + }, + "_tex_3": { + "fontCharacter": "\\E094", + "fontColor": "#d4d7d6" + }, + "_todo": { + "fontCharacter": "\\E096" + }, + "_tsconfig_light": { + "fontCharacter": "\\E097", + "fontColor": "#498ba7" + }, + "_tsconfig": { + "fontCharacter": "\\E097", + "fontColor": "#519aba" + }, + "_twig_light": { + "fontCharacter": "\\E098", + "fontColor": "#7fae42" + }, + "_twig": { + "fontCharacter": "\\E098", + "fontColor": "#8dc149" + }, + "_typescript_light": { + "fontCharacter": "\\E099", + "fontColor": "#498ba7" + }, + "_typescript": { + "fontCharacter": "\\E099", + "fontColor": "#519aba" + }, + "_typescript_1_light": { + "fontCharacter": "\\E099", + "fontColor": "#cc6d2e" + }, + "_typescript_1": { + "fontCharacter": "\\E099", + "fontColor": "#e37933" + }, + "_vala_light": { + "fontCharacter": "\\E09A", + "fontColor": "#627379" + }, + "_vala": { + "fontCharacter": "\\E09A", + "fontColor": "#6d8086" + }, + "_video_light": { + "fontCharacter": "\\E09B", + "fontColor": "#dd4b78" + }, + "_video": { + "fontCharacter": "\\E09B", + "fontColor": "#f55385" + }, + "_vite_light": { + "fontCharacter": "\\E09C", + "fontColor": "#b7b73b" + }, + "_vite": { + "fontCharacter": "\\E09C", + "fontColor": "#cbcb41" + }, + "_vue_light": { + "fontCharacter": "\\E09D", + "fontColor": "#7fae42" + }, + "_vue": { + "fontCharacter": "\\E09D", + "fontColor": "#8dc149" + }, + "_wasm_light": { + "fontCharacter": "\\E09E", + "fontColor": "#9068b0" + }, + "_wasm": { + "fontCharacter": "\\E09E", + "fontColor": "#a074c4" + }, + "_wat_light": { + "fontCharacter": "\\E09F", + "fontColor": "#9068b0" + }, + "_wat": { + "fontCharacter": "\\E09F", + "fontColor": "#a074c4" + }, + "_webpack_light": { + "fontCharacter": "\\E0A0", + "fontColor": "#498ba7" + }, + "_webpack": { + "fontCharacter": "\\E0A0", + "fontColor": "#519aba" + }, + "_wgt_light": { + "fontCharacter": "\\E0A1", + "fontColor": "#498ba7" + }, + "_wgt": { + "fontCharacter": "\\E0A1", + "fontColor": "#519aba" + }, + "_windows_light": { + "fontCharacter": "\\E0A2", + "fontColor": "#498ba7" + }, + "_windows": { + "fontCharacter": "\\E0A2", + "fontColor": "#519aba" + }, + "_word_light": { + "fontCharacter": "\\E0A3", + "fontColor": "#498ba7" + }, + "_word": { + "fontCharacter": "\\E0A3", + "fontColor": "#519aba" + }, + "_xls_light": { + "fontCharacter": "\\E0A4", + "fontColor": "#7fae42" + }, + "_xls": { + "fontCharacter": "\\E0A4", + "fontColor": "#8dc149" + }, + "_xml_light": { + "fontCharacter": "\\E0A5", + "fontColor": "#cc6d2e" + }, + "_xml": { + "fontCharacter": "\\E0A5", + "fontColor": "#e37933" + }, + "_yarn_light": { + "fontCharacter": "\\E0A6", + "fontColor": "#498ba7" + }, + "_yarn": { + "fontCharacter": "\\E0A6", + "fontColor": "#519aba" + }, + "_yml_light": { + "fontCharacter": "\\E0A7", + "fontColor": "#9068b0" + }, + "_yml": { + "fontCharacter": "\\E0A7", + "fontColor": "#a074c4" + }, + "_zig_light": { + "fontCharacter": "\\E0A8", + "fontColor": "#cc6d2e" + }, + "_zig": { + "fontCharacter": "\\E0A8", + "fontColor": "#e37933" + }, + "_zip_light": { + "fontCharacter": "\\E0A9", + "fontColor": "#b8383d" + }, + "_zip": { + "fontCharacter": "\\E0A9", + "fontColor": "#cc3e44" + }, + "_zip_1_light": { + "fontCharacter": "\\E0A9", + "fontColor": "#627379" + }, + "_zip_1": { + "fontCharacter": "\\E0A9", + "fontColor": "#6d8086" + } + }, + "file": "_default", + "fileExtensions": { + "bsl": "_bsl", + "mdo": "_mdo", + "cls": "_salesforce", + "apex": "_salesforce", + "asm": "_asm", + "s": "_asm", + "bicep": "_bicep", + "bzl": "_bazel", + "bazel": "_bazel", + "build": "_bazel", + "workspace": "_bazel", + "bazelignore": "_bazel", + "bazelversion": "_bazel", + "h": "_c_1", + "aspx": "_html", + "ascx": "_html_1", + "asax": "_html_2", + "master": "_html_2", + "hh": "_cpp_1", + "hpp": "_cpp_1", + "hxx": "_cpp_1", + "h++": "_cpp_1", + "edn": "_clojure_1", + "cfc": "_coldfusion", + "cfm": "_coldfusion", + "litcoffee": "_coffee", + "config": "_config", + "cr": "_crystal", + "ecr": "_crystal_embedded", + "slang": "_crystal_embedded", + "cson": "_json", + "css.map": "_css", + "sss": "_css", + "csv": "_csv", + "xls": "_xls", + "xlsx": "_xls", + "cuh": "_cu_1", + "hu": "_cu_1", + "cake": "_cake", + "ctp": "_cake_php", + "d": "_d", + "doc": "_word", + "docx": "_word", + "ejs": "_ejs", + "ex": "_elixir", + "exs": "_elixir_script", + "elm": "_elm", + "ico": "_favicon", + "gitconfig": "_git", + "gitkeep": "_git", + "gitattributes": "_git", + "gitmodules": "_git", + "slide": "_go", + "article": "_go", + "gd": "_godot", + "godot": "_godot_1", + "tres": "_godot_2", + "tscn": "_godot_3", + "gradle": "_gradle", + "gsp": "_grails", + "gql": "_graphql", + "graphql": "_graphql", + "graphqls": "_graphql", + "hack": "_hacklang", + "haml": "_haml", + "hs": "_haskell", + "lhs": "_haskell", + "hx": "_haxe", + "hxs": "_haxe_1", + "hxp": "_haxe_2", + "hxml": "_haxe_3", + "jade": "_jade", + "class": "_java_1", + "classpath": "_java", + "js.map": "_javascript", + "cjs.map": "_javascript", + "mjs.map": "_javascript", + "spec.js": "_javascript_1", + "spec.cjs": "_javascript_1", + "spec.mjs": "_javascript_1", + "test.js": "_javascript_1", + "test.cjs": "_javascript_1", + "test.mjs": "_javascript_1", + "es": "_javascript", + "es5": "_javascript", + "es7": "_javascript", + "jinja": "_jinja", + "jinja2": "_jinja", + "kt": "_kotlin", + "kts": "_kotlin", + "liquid": "_liquid", + "ls": "_livescript", + "argdown": "_argdown", + "ad": "_argdown", + "mustache": "_mustache", + "stache": "_mustache", + "nim": "_nim", + "nims": "_nim", + "github-issues": "_github", + "ipynb": "_notebook", + "njk": "_nunjucks", + "nunjucks": "_nunjucks", + "nunjs": "_nunjucks", + "nunj": "_nunjucks", + "njs": "_nunjucks", + "nj": "_nunjucks", + "npm-debug.log": "_npm", + "npmignore": "_npm_1", + "npmrc": "_npm_1", + "ml": "_ocaml", + "mli": "_ocaml", + "cmx": "_ocaml", + "cmxa": "_ocaml", + "odata": "_odata", + "php.inc": "_php", + "pipeline": "_pipeline", + "pddl": "_pddl", + "plan": "_plan", + "happenings": "_happenings", + "prisma": "_prisma", + "pp": "_puppet", + "epp": "_puppet", + "purs": "_purescript", + "spec.jsx": "_react_1", + "test.jsx": "_react_1", + "cjsx": "_react", + "tsx": "_react_1", + "spec.tsx": "_react_1", + "test.tsx": "_react_1", + "re": "_reasonml", + "res": "_rescript", + "resi": "_rescript_1", + "r": "_R", + "rmd": "_R", + "erb": "_html_erb", + "erb.html": "_html_erb", + "html.erb": "_html_erb", + "sass": "_sass", + "springbeans": "_spring", + "slim": "_slim", + "smarty.tpl": "_smarty", + "tpl": "_smarty", + "sbt": "_sbt", + "scala": "_scala", + "sol": "_ethereum", + "styl": "_stylus", + "svelte": "_svelte", + "soql": "_db_1", + "tf": "_terraform", + "tf.json": "_terraform", + "tfvars": "_terraform", + "tfvars.json": "_terraform", + "dtx": "_tex_2", + "ins": "_tex_3", + "toml": "_config", + "twig": "_twig", + "ts": "_typescript", + "spec.ts": "_typescript_1", + "test.ts": "_typescript_1", + "vala": "_vala", + "vapi": "_vala", + "component": "_html_3", + "vue": "_vue", + "wasm": "_wasm", + "wat": "_wat", + "pro": "_prolog", + "zig": "_zig", + "jar": "_zip", + "zip": "_zip_1", + "wgt": "_wgt", + "ai": "_illustrator", + "psd": "_photoshop", + "pdf": "_pdf", + "eot": "_font", + "ttf": "_font", + "woff": "_font", + "woff2": "_font", + "otf": "_font", + "avif": "_image", + "gif": "_image", + "jpg": "_image", + "jpeg": "_image", + "png": "_image", + "pxm": "_image", + "svg": "_svg", + "svgx": "_image", + "tiff": "_image", + "webp": "_image", + "sublime-project": "_sublime", + "sublime-workspace": "_sublime", + "mov": "_video", + "ogv": "_video", + "webm": "_video", + "avi": "_video", + "mpg": "_video", + "mp4": "_video", + "mp3": "_audio", + "ogg": "_audio", + "wav": "_audio", + "flac": "_audio", + "3ds": "_svg_1", + "3dm": "_svg_1", + "stl": "_svg_1", + "obj": "_svg_1", + "dae": "_svg_1", + "babelrc": "_babel", + "babelrc.js": "_babel", + "babelrc.cjs": "_babel", + "bazelrc": "_bazel_1", + "bowerrc": "_bower", + "dockerignore": "_docker_1", + "codeclimate.yml": "_code-climate", + "eslintrc": "_eslint", + "eslintrc.js": "_eslint", + "eslintrc.cjs": "_eslint", + "eslintrc.yaml": "_eslint", + "eslintrc.yml": "_eslint", + "eslintrc.json": "_eslint", + "eslintignore": "_eslint_1", + "firebaserc": "_firebase", + "gitlab-ci.yml": "_gitlab", + "jshintrc": "_javascript_2", + "jscsrc": "_javascript_2", + "stylelintrc": "_stylelint", + "stylelintrc.json": "_stylelint", + "stylelintrc.yaml": "_stylelint", + "stylelintrc.yml": "_stylelint", + "stylelintrc.js": "_stylelint", + "stylelintignore": "_stylelint_1", + "direnv": "_config", + "static": "_config", + "slugignore": "_config", + "tmp": "_clock_1", + "htaccess": "_config", + "key": "_lock", + "cert": "_lock", + "cer": "_lock", + "crt": "_lock", + "pem": "_lock", + "ds_store": "_ignored" + }, + "fileNames": { + "mix": "_hex", + "karma.conf.js": "_karma", + "karma.conf.cjs": "_karma", + "karma.conf.mjs": "_karma", + "karma.conf.coffee": "_karma", + "readme.md": "_info", + "readme.txt": "_info", + "readme": "_info", + "changelog.md": "_clock", + "changelog.txt": "_clock", + "changelog": "_clock", + "changes.md": "_clock", + "changes.txt": "_clock", + "changes": "_clock", + "version.md": "_clock", + "version.txt": "_clock", + "version": "_clock", + "mvnw": "_maven", + "pom.xml": "_maven", + "tsconfig.json": "_tsconfig", + "vite.config.js": "_vite", + "vite.config.ts": "_vite", + "vite.config.mjs": "_vite", + "vite.config.mts": "_vite", + "vite.config.cjs": "_vite", + "vite.config.cts": "_vite", + "swagger.json": "_json_1", + "swagger.yml": "_json_1", + "swagger.yaml": "_json_1", + "mime.types": "_config", + "jenkinsfile": "_jenkins", + "babel.config.js": "_babel", + "babel.config.json": "_babel", + "babel.config.cjs": "_babel", + "build": "_bazel", + "build.bazel": "_bazel", + "workspace": "_bazel", + "workspace.bazel": "_bazel", + "bower.json": "_bower", + "docker-healthcheck": "_docker_2", + "eslint.config.js": "_eslint", + "firebase.json": "_firebase", + "geckodriver": "_firefox", + "gruntfile.js": "_grunt", + "gruntfile.babel.js": "_grunt", + "gruntfile.coffee": "_grunt", + "gulpfile": "_gulp", + "gulpfile.js": "_gulp", + "ionic.config.json": "_ionic", + "ionic.project": "_ionic", + "platformio.ini": "_platformio", + "rollup.config.js": "_rollup", + "sass-lint.yml": "_sass", + "stylelint.config.js": "_stylelint", + "stylelint.config.cjs": "_stylelint", + "stylelint.config.mjs": "_stylelint", + "yarn.clean": "_yarn", + "yarn.lock": "_yarn", + "webpack.config.js": "_webpack", + "webpack.config.cjs": "_webpack", + "webpack.config.mjs": "_webpack", + "webpack.config.ts": "_webpack", + "webpack.config.build.js": "_webpack", + "webpack.config.build.cjs": "_webpack", + "webpack.config.build.mjs": "_webpack", + "webpack.config.build.ts": "_webpack", + "webpack.common.js": "_webpack", + "webpack.common.cjs": "_webpack", + "webpack.common.mjs": "_webpack", + "webpack.common.ts": "_webpack", + "webpack.dev.js": "_webpack", + "webpack.dev.cjs": "_webpack", + "webpack.dev.mjs": "_webpack", + "webpack.dev.ts": "_webpack", + "webpack.prod.js": "_webpack", + "webpack.prod.cjs": "_webpack", + "webpack.prod.mjs": "_webpack", + "webpack.prod.ts": "_webpack", + "license": "_license", + "licence": "_license", + "license.txt": "_license", + "licence.txt": "_license", + "license.md": "_license", + "licence.md": "_license", + "copying": "_license", + "copying.txt": "_license", + "copying.md": "_license", + "compiling": "_license_1", + "compiling.txt": "_license_1", + "compiling.md": "_license_1", + "contributing": "_license_2", + "contributing.txt": "_license_2", + "contributing.md": "_license_2", + "qmakefile": "_makefile_1", + "omakefile": "_makefile_2", + "cmakelists.txt": "_makefile_3", + "procfile": "_heroku", + "todo": "_todo", + "todo.txt": "_todo", + "todo.md": "_todo", + "npm-debug.log": "_npm_ignored" + }, + "languageIds": { + "bat": "_windows", + "clojure": "_clojure", + "coffeescript": "_coffee", + "jsonc": "_json", + "json": "_json", + "c": "_c", + "cpp": "_cpp", + "cuda-cpp": "_cu", + "csharp": "_c-sharp", + "css": "_css", + "dart": "_dart", + "dockerfile": "_docker", + "dotenv": "_config", + "ignore": "_git", + "fsharp": "_f-sharp", + "git-commit": "_git", + "go": "_go2", + "groovy": "_grails", + "handlebars": "_mustache", + "html": "_html_3", + "properties": "_config", + "java": "_java", + "javascriptreact": "_react", + "javascript": "_javascript", + "julia": "_julia", + "tex": "_tex_1", + "latex": "_tex", + "less": "_less", + "lua": "_lua", + "makefile": "_makefile", + "markdown": "_markdown", + "objective-c": "_c_2", + "objective-cpp": "_cpp_2", + "perl": "_perl", + "php": "_php", + "powershell": "_powershell", + "jade": "_pug", + "python": "_python", + "r": "_R", + "razor": "_html", + "ruby": "_ruby", + "rust": "_rust", + "scss": "_sass", + "search-result": "_code-search", + "shellscript": "_shell", + "sql": "_db", + "swift": "_swift", + "typescript": "_typescript", + "typescriptreact": "_react", + "xml": "_xml", + "dockercompose": "_docker_3", + "yaml": "_yml", + "argdown": "_argdown", + "bicep": "_bicep", + "elixir": "_elixir", + "elm": "_elm", + "erb": "_html_erb", + "github-issues": "_github", + "gradle": "_gradle", + "godot": "_godot", + "haml": "_haml", + "haskell": "_haskell", + "haxe": "_haxe", + "jinja": "_jinja", + "kotlin": "_kotlin", + "mustache": "_mustache", + "nunjucks": "_nunjucks", + "ocaml": "_ocaml", + "rescript": "_rescript", + "sass": "_sass", + "stylus": "_stylus", + "terraform": "_terraform", + "todo": "_todo", + "vala": "_vala", + "vue": "_vue", + "jsonl": "_json", + "postcss": "_css", + "django-html": "_html_3", + "blade": "_php" + }, + "light": { + "file": "_default_light", + "fileExtensions": { + "bsl": "_bsl_light", + "mdo": "_mdo_light", + "cls": "_salesforce_light", + "apex": "_salesforce_light", + "asm": "_asm_light", + "s": "_asm_light", + "bicep": "_bicep_light", + "bzl": "_bazel_light", + "bazel": "_bazel_light", + "build": "_bazel_light", + "workspace": "_bazel_light", + "bazelignore": "_bazel_light", + "bazelversion": "_bazel_light", + "h": "_c_1_light", + "aspx": "_html_light", + "ascx": "_html_1_light", + "asax": "_html_2_light", + "master": "_html_2_light", + "hh": "_cpp_1_light", + "hpp": "_cpp_1_light", + "hxx": "_cpp_1_light", + "h++": "_cpp_1_light", + "edn": "_clojure_1_light", + "cfc": "_coldfusion_light", + "cfm": "_coldfusion_light", + "litcoffee": "_coffee_light", + "config": "_config_light", + "cr": "_crystal_light", + "ecr": "_crystal_embedded_light", + "slang": "_crystal_embedded_light", + "cson": "_json_light", + "css.map": "_css_light", + "sss": "_css_light", + "csv": "_csv_light", + "xls": "_xls_light", + "xlsx": "_xls_light", + "cuh": "_cu_1_light", + "hu": "_cu_1_light", + "cake": "_cake_light", + "ctp": "_cake_php_light", + "d": "_d_light", + "doc": "_word_light", + "docx": "_word_light", + "ejs": "_ejs_light", + "ex": "_elixir_light", + "exs": "_elixir_script_light", + "elm": "_elm_light", + "ico": "_favicon_light", + "gitconfig": "_git_light", + "gitkeep": "_git_light", + "gitattributes": "_git_light", + "gitmodules": "_git_light", + "slide": "_go_light", + "article": "_go_light", + "gd": "_godot_light", + "godot": "_godot_1_light", + "tres": "_godot_2_light", + "tscn": "_godot_3_light", + "gradle": "_gradle_light", + "gsp": "_grails_light", + "gql": "_graphql_light", + "graphql": "_graphql_light", + "graphqls": "_graphql_light", + "hack": "_hacklang_light", + "haml": "_haml_light", + "hs": "_haskell_light", + "lhs": "_haskell_light", + "hx": "_haxe_light", + "hxs": "_haxe_1_light", + "hxp": "_haxe_2_light", + "hxml": "_haxe_3_light", + "jade": "_jade_light", + "class": "_java_1_light", + "classpath": "_java_light", + "js.map": "_javascript_light", + "cjs.map": "_javascript_light", + "mjs.map": "_javascript_light", + "spec.js": "_javascript_1_light", + "spec.cjs": "_javascript_1_light", + "spec.mjs": "_javascript_1_light", + "test.js": "_javascript_1_light", + "test.cjs": "_javascript_1_light", + "test.mjs": "_javascript_1_light", + "es": "_javascript_light", + "es5": "_javascript_light", + "es7": "_javascript_light", + "jinja": "_jinja_light", + "jinja2": "_jinja_light", + "kt": "_kotlin_light", + "kts": "_kotlin_light", + "liquid": "_liquid_light", + "ls": "_livescript_light", + "argdown": "_argdown_light", + "ad": "_argdown_light", + "mustache": "_mustache_light", + "stache": "_mustache_light", + "nim": "_nim_light", + "nims": "_nim_light", + "github-issues": "_github_light", + "ipynb": "_notebook_light", + "njk": "_nunjucks_light", + "nunjucks": "_nunjucks_light", + "nunjs": "_nunjucks_light", + "nunj": "_nunjucks_light", + "njs": "_nunjucks_light", + "nj": "_nunjucks_light", + "npm-debug.log": "_npm_light", + "npmignore": "_npm_1_light", + "npmrc": "_npm_1_light", + "ml": "_ocaml_light", + "mli": "_ocaml_light", + "cmx": "_ocaml_light", + "cmxa": "_ocaml_light", + "odata": "_odata_light", + "php.inc": "_php_light", + "pipeline": "_pipeline_light", + "pddl": "_pddl_light", + "plan": "_plan_light", + "happenings": "_happenings_light", + "prisma": "_prisma_light", + "pp": "_puppet_light", + "epp": "_puppet_light", + "purs": "_purescript_light", + "spec.jsx": "_react_1_light", + "test.jsx": "_react_1_light", + "cjsx": "_react_light", + "spec.tsx": "_react_1_light", + "test.tsx": "_react_1_light", + "re": "_reasonml_light", + "res": "_rescript_light", + "resi": "_rescript_1_light", + "r": "_R_light", + "rmd": "_R_light", + "erb": "_html_erb_light", + "erb.html": "_html_erb_light", + "html.erb": "_html_erb_light", + "sass": "_sass_light", + "springbeans": "_spring_light", + "slim": "_slim_light", + "smarty.tpl": "_smarty_light", + "tpl": "_smarty_light", + "sbt": "_sbt_light", + "scala": "_scala_light", + "sol": "_ethereum_light", + "styl": "_stylus_light", + "svelte": "_svelte_light", + "soql": "_db_1_light", + "tf": "_terraform_light", + "tf.json": "_terraform_light", + "tfvars": "_terraform_light", + "tfvars.json": "_terraform_light", + "dtx": "_tex_2_light", + "ins": "_tex_3_light", + "toml": "_config_light", + "twig": "_twig_light", + "spec.ts": "_typescript_1_light", + "test.ts": "_typescript_1_light", + "vala": "_vala_light", + "vapi": "_vala_light", + "component": "_html_3_light", + "vue": "_vue_light", + "wasm": "_wasm_light", + "wat": "_wat_light", + "pro": "_prolog_light", + "zig": "_zig_light", + "jar": "_zip_light", + "zip": "_zip_1_light", + "wgt": "_wgt_light", + "ai": "_illustrator_light", + "psd": "_photoshop_light", + "pdf": "_pdf_light", + "eot": "_font_light", + "ttf": "_font_light", + "woff": "_font_light", + "woff2": "_font_light", + "otf": "_font_light", + "avif": "_image_light", + "gif": "_image_light", + "jpg": "_image_light", + "jpeg": "_image_light", + "png": "_image_light", + "pxm": "_image_light", + "svg": "_svg_light", + "svgx": "_image_light", + "tiff": "_image_light", + "webp": "_image_light", + "sublime-project": "_sublime_light", + "sublime-workspace": "_sublime_light", + "mov": "_video_light", + "ogv": "_video_light", + "webm": "_video_light", + "avi": "_video_light", + "mpg": "_video_light", + "mp4": "_video_light", + "mp3": "_audio_light", + "ogg": "_audio_light", + "wav": "_audio_light", + "flac": "_audio_light", + "3ds": "_svg_1_light", + "3dm": "_svg_1_light", + "stl": "_svg_1_light", + "obj": "_svg_1_light", + "dae": "_svg_1_light", + "babelrc": "_babel_light", + "babelrc.js": "_babel_light", + "babelrc.cjs": "_babel_light", + "bazelrc": "_bazel_1_light", + "bowerrc": "_bower_light", + "dockerignore": "_docker_1_light", + "codeclimate.yml": "_code-climate_light", + "eslintrc": "_eslint_light", + "eslintrc.js": "_eslint_light", + "eslintrc.cjs": "_eslint_light", + "eslintrc.yaml": "_eslint_light", + "eslintrc.yml": "_eslint_light", + "eslintrc.json": "_eslint_light", + "eslintignore": "_eslint_1_light", + "firebaserc": "_firebase_light", + "gitlab-ci.yml": "_gitlab_light", + "jshintrc": "_javascript_2_light", + "jscsrc": "_javascript_2_light", + "stylelintrc": "_stylelint_light", + "stylelintrc.json": "_stylelint_light", + "stylelintrc.yaml": "_stylelint_light", + "stylelintrc.yml": "_stylelint_light", + "stylelintrc.js": "_stylelint_light", + "stylelintignore": "_stylelint_1_light", + "direnv": "_config_light", + "static": "_config_light", + "slugignore": "_config_light", + "tmp": "_clock_1_light", + "htaccess": "_config_light", + "key": "_lock_light", + "cert": "_lock_light", + "cer": "_lock_light", + "crt": "_lock_light", + "pem": "_lock_light", + "ds_store": "_ignored_light" + }, + "languageIds": { + "bat": "_windows_light", + "clojure": "_clojure_light", + "coffeescript": "_coffee_light", + "jsonc": "_json_light", + "json": "_json_light", + "c": "_c_light", + "cpp": "_cpp_light", + "cuda-cpp": "_cu_light", + "csharp": "_c-sharp_light", + "css": "_css_light", + "dart": "_dart_light", + "dockerfile": "_docker_light", + "dotenv": "_config_light", + "ignore": "_git_light", + "fsharp": "_f-sharp_light", + "git-commit": "_git_light", + "go": "_go2_light", + "groovy": "_grails_light", + "handlebars": "_mustache_light", + "html": "_html_3_light", + "properties": "_config_light", + "java": "_java_light", + "javascriptreact": "_react_light", + "javascript": "_javascript_light", + "julia": "_julia_light", + "tex": "_tex_1_light", + "latex": "_tex_light", + "less": "_less_light", + "lua": "_lua_light", + "makefile": "_makefile_light", + "markdown": "_markdown_light", + "objective-c": "_c_2_light", + "objective-cpp": "_cpp_2_light", + "perl": "_perl_light", + "php": "_php_light", + "powershell": "_powershell_light", + "jade": "_pug_light", + "python": "_python_light", + "r": "_R_light", + "razor": "_html_light", + "ruby": "_ruby_light", + "rust": "_rust_light", + "scss": "_sass_light", + "search-result": "_code-search_light", + "shellscript": "_shell_light", + "sql": "_db_light", + "swift": "_swift_light", + "typescript": "_typescript_light", + "typescriptreact": "_react_light", + "xml": "_xml_light", + "dockercompose": "_docker_3_light", + "yaml": "_yml_light", + "argdown": "_argdown_light", + "bicep": "_bicep_light", + "elixir": "_elixir_light", + "elm": "_elm_light", + "erb": "_html_erb_light", + "github-issues": "_github_light", + "gradle": "_gradle_light", + "godot": "_godot_light", + "haml": "_haml_light", + "haskell": "_haskell_light", + "haxe": "_haxe_light", + "jinja": "_jinja_light", + "kotlin": "_kotlin_light", + "mustache": "_mustache_light", + "nunjucks": "_nunjucks_light", + "ocaml": "_ocaml_light", + "rescript": "_rescript_light", + "sass": "_sass_light", + "stylus": "_stylus_light", + "terraform": "_terraform_light", + "vala": "_vala_light", + "vue": "_vue_light", + "jsonl": "_json_light", + "postcss": "_css_light", + "django-html": "_html_3_light", + "blade": "_php_light" + }, + "fileNames": { + "mix": "_hex_light", + "karma.conf.js": "_karma_light", + "karma.conf.cjs": "_karma_light", + "karma.conf.mjs": "_karma_light", + "karma.conf.coffee": "_karma_light", + "readme.md": "_info_light", + "readme.txt": "_info_light", + "readme": "_info_light", + "changelog.md": "_clock_light", + "changelog.txt": "_clock_light", + "changelog": "_clock_light", + "changes.md": "_clock_light", + "changes.txt": "_clock_light", + "changes": "_clock_light", + "version.md": "_clock_light", + "version.txt": "_clock_light", + "version": "_clock_light", + "mvnw": "_maven_light", + "pom.xml": "_maven_light", + "tsconfig.json": "_tsconfig_light", + "vite.config.js": "_vite_light", + "vite.config.ts": "_vite_light", + "vite.config.mjs": "_vite_light", + "vite.config.mts": "_vite_light", + "vite.config.cjs": "_vite_light", + "vite.config.cts": "_vite_light", + "swagger.json": "_json_1_light", + "swagger.yml": "_json_1_light", + "swagger.yaml": "_json_1_light", + "mime.types": "_config_light", + "jenkinsfile": "_jenkins_light", + "babel.config.js": "_babel_light", + "babel.config.json": "_babel_light", + "babel.config.cjs": "_babel_light", + "build": "_bazel_light", + "build.bazel": "_bazel_light", + "workspace": "_bazel_light", + "workspace.bazel": "_bazel_light", + "bower.json": "_bower_light", + "docker-healthcheck": "_docker_2_light", + "eslint.config.js": "_eslint_light", + "firebase.json": "_firebase_light", + "geckodriver": "_firefox_light", + "gruntfile.js": "_grunt_light", + "gruntfile.babel.js": "_grunt_light", + "gruntfile.coffee": "_grunt_light", + "gulpfile": "_gulp_light", + "gulpfile.js": "_gulp_light", + "ionic.config.json": "_ionic_light", + "ionic.project": "_ionic_light", + "platformio.ini": "_platformio_light", + "rollup.config.js": "_rollup_light", + "sass-lint.yml": "_sass_light", + "stylelint.config.js": "_stylelint_light", + "stylelint.config.cjs": "_stylelint_light", + "stylelint.config.mjs": "_stylelint_light", + "yarn.clean": "_yarn_light", + "yarn.lock": "_yarn_light", + "webpack.config.js": "_webpack_light", + "webpack.config.cjs": "_webpack_light", + "webpack.config.mjs": "_webpack_light", + "webpack.config.ts": "_webpack_light", + "webpack.config.build.js": "_webpack_light", + "webpack.config.build.cjs": "_webpack_light", + "webpack.config.build.mjs": "_webpack_light", + "webpack.config.build.ts": "_webpack_light", + "webpack.common.js": "_webpack_light", + "webpack.common.cjs": "_webpack_light", + "webpack.common.mjs": "_webpack_light", + "webpack.common.ts": "_webpack_light", + "webpack.dev.js": "_webpack_light", + "webpack.dev.cjs": "_webpack_light", + "webpack.dev.mjs": "_webpack_light", + "webpack.dev.ts": "_webpack_light", + "webpack.prod.js": "_webpack_light", + "webpack.prod.cjs": "_webpack_light", + "webpack.prod.mjs": "_webpack_light", + "webpack.prod.ts": "_webpack_light", + "license": "_license_light", + "licence": "_license_light", + "license.txt": "_license_light", + "licence.txt": "_license_light", + "license.md": "_license_light", + "licence.md": "_license_light", + "copying": "_license_light", + "copying.txt": "_license_light", + "copying.md": "_license_light", + "compiling": "_license_1_light", + "compiling.txt": "_license_1_light", + "compiling.md": "_license_1_light", + "contributing": "_license_2_light", + "contributing.txt": "_license_2_light", + "contributing.md": "_license_2_light", + "qmakefile": "_makefile_1_light", + "omakefile": "_makefile_2_light", + "cmakelists.txt": "_makefile_3_light", + "procfile": "_heroku_light", + "npm-debug.log": "_npm_ignored_light" + } + }, + "version": "https://github.com/jesseweed/seti-ui/commit/2d6c5e68b4ded73c92dac291845ee44e1182d511" +} diff --git a/site/src/components/ai-elements/tool/utils.ts b/site/src/components/ai-elements/tool/utils.ts index 44f909e599..be9d43da7e 100644 --- a/site/src/components/ai-elements/tool/utils.ts +++ b/site/src/components/ai-elements/tool/utils.ts @@ -190,6 +190,8 @@ export function getFileViewerOptionsMinimal(isDark: boolean) { } export const DIFFS_FONT_STYLE = { + "--diffs-font-family": '"Geist Mono Variable", monospace, monospace', + "--diffs-header-font-family": '"Geist Variable", system-ui, sans-serif', "--diffs-font-size": "11px", "--diffs-line-height": "1.5", } as React.CSSProperties; diff --git a/site/src/index.css b/site/src/index.css index d049df80b3..3a2bda9811 100644 --- a/site/src/index.css +++ b/site/src/index.css @@ -7,6 +7,14 @@ @tailwind components; @tailwind utilities; +@font-face { + font-family: "seti"; + src: url("/seti.woff") format("woff"); + font-weight: normal; + font-style: normal; + font-display: swap; +} + @layer base { :root, .light { diff --git a/site/src/pages/AgentsPage/AgentDetail.tsx b/site/src/pages/AgentsPage/AgentDetail.tsx index eb47a16dbc..428c21470a 100644 --- a/site/src/pages/AgentsPage/AgentDetail.tsx +++ b/site/src/pages/AgentsPage/AgentDetail.tsx @@ -64,6 +64,7 @@ import { buildStreamTools } from "./AgentDetail/streamState"; import { AgentDetailTopBar } from "./AgentDetail/TopBar"; import { useMessageWindow } from "./AgentDetail/useMessageWindow"; import type { AgentsOutletContext } from "./AgentsPage"; +import { DiffStatBadge } from "./DiffStats"; import { FilesChangedPanel } from "./FilesChangedPanel"; import { getModelCatalogStatusMessage, @@ -975,7 +976,7 @@ const AgentDetail: FC = () => { onToggleSidebarCollapsed={onToggleSidebarCollapsed} /> {isArchived && ( -
+
This agent has been archived and is read-only.
@@ -1047,9 +1048,17 @@ const AgentDetail: FC = () => { onToggleSidebarCollapsed={onToggleSidebarCollapsed} onVisualExpandedChange={setDragVisualExpanded} tabContent={{ - git: , + git: ( + + ), }} - />{" "} + tabMeta={{ + git: , + }} + />
); }; diff --git a/site/src/pages/AgentsPage/AgentDetail/TopBar.tsx b/site/src/pages/AgentsPage/AgentDetail/TopBar.tsx index d311a98fdd..9d820323dc 100644 --- a/site/src/pages/AgentsPage/AgentDetail/TopBar.tsx +++ b/site/src/pages/AgentsPage/AgentDetail/TopBar.tsx @@ -26,29 +26,7 @@ import { import type { FC } from "react"; import { useNavigate } from "react-router"; import { toast } from "sonner"; - -const DiffStatsInline: FC<{ - status: ChatDiffStatusResponse; - onClick: () => void; -}> = ({ status, onClick }) => { - const additions = status.additions ?? 0; - const deletions = status.deletions ?? 0; - - return ( - - ); -}; +import { DiffStatsInline } from "../DiffStats"; interface DiffPanelState { hasDiffStatus: boolean; @@ -141,7 +119,7 @@ export const AgentDetailTopBar: FC = ({ {chatTitle} - {diff.hasDiffStatus && diff.diffStatus && ( + {diff.hasDiffStatus && diff.diffStatus && !diff.showDiffPanel && ( = ({ )} - )}{" "} - + )} + {" "} ); }; diff --git a/site/src/pages/AgentsPage/AgentsSidebar.tsx b/site/src/pages/AgentsPage/AgentsSidebar.tsx index cbceed2846..4fa19e8cc5 100644 --- a/site/src/pages/AgentsPage/AgentsSidebar.tsx +++ b/site/src/pages/AgentsPage/AgentsSidebar.tsx @@ -433,13 +433,15 @@ const ChatTreeNode = memo(({ chat, isChildNode }) => {
{hasLinkedDiffStatus && hasLineStats && ( - +{additions} - - -{deletions} - + {additions > 0 && ( + +{additions} + )} + {deletions > 0 && ( + −{deletions} + )} )}
(({ chat, isChildNode }) => { ) : ( <> - {" "} = (props) => { ); if (groupChats.length === 0) return null; return ( -
+
{group}
@@ -814,13 +818,14 @@ export const AgentsSidebar: FC = (props) => {
{" "} +
); }; diff --git a/site/src/pages/AgentsPage/DiffStats.tsx b/site/src/pages/AgentsPage/DiffStats.tsx new file mode 100644 index 0000000000..5d9f838557 --- /dev/null +++ b/site/src/pages/AgentsPage/DiffStats.tsx @@ -0,0 +1,94 @@ +import type { ChatDiffStatusResponse } from "api/api"; +import type { FC } from "react"; +import { cn } from "utils/cn"; + +interface DiffStatsProps { + additions: number; + deletions: number; + className?: string; +} + +/** + * Renders +N / −N counters for diff additions and deletions. + * Returns null when both values are zero. + */ +const DiffStatNumbers: FC = ({ + additions, + deletions, + className, +}) => { + if (additions === 0 && deletions === 0) { + return null; + } + return ( + + {additions > 0 && ( + +{additions} + )} + {deletions > 0 && ( + + −{deletions} + + )} + + ); +}; + +/** + * Pill-styled diff stats badge with coloured backgrounds, + * used inside the Git tab header. + */ +export const DiffStatBadge: FC<{ diffStatus?: ChatDiffStatusResponse }> = ({ + diffStatus, +}) => { + const additions = diffStatus?.additions ?? 0; + const deletions = diffStatus?.deletions ?? 0; + if (additions === 0 && deletions === 0) { + return null; + } + return ( + + {additions > 0 && ( + + +{additions} + + )} + {deletions > 0 && ( + + −{deletions} + + )} + + ); +}; + +/** + * Clickable inline diff stats shown in the top bar when the + * diff panel is closed. + */ +export const DiffStatsInline: FC<{ + status: ChatDiffStatusResponse; + onClick: () => void; +}> = ({ status, onClick }) => { + const additions = status.additions ?? 0; + const deletions = status.deletions ?? 0; + + if (additions === 0 && deletions === 0) { + return null; + } + + return ( + + ); +}; diff --git a/site/src/pages/AgentsPage/FilesChangedPanel.tsx b/site/src/pages/AgentsPage/FilesChangedPanel.tsx index bf945de827..82a08c13b1 100644 --- a/site/src/pages/AgentsPage/FilesChangedPanel.tsx +++ b/site/src/pages/AgentsPage/FilesChangedPanel.tsx @@ -1,5 +1,5 @@ import { useTheme } from "@emotion/react"; -import type { FileDiffMetadata } from "@pierre/diffs"; +import type { ChangeTypes, FileDiffMetadata } from "@pierre/diffs"; import { parsePatchFiles } from "@pierre/diffs"; import { FileDiff } from "@pierre/diffs/react"; import { chatDiffContents, chatDiffStatus } from "api/queries/chats"; @@ -8,55 +8,327 @@ import { DIFFS_FONT_STYLE, getDiffViewerOptions, } from "components/ai-elements/tool/utils"; +import { Button } from "components/Button/Button"; +import { FileIcon } from "components/FileIcon/FileIcon"; import { ScrollArea } from "components/ScrollArea/ScrollArea"; import { Skeleton } from "components/Skeleton/Skeleton"; import { + ChevronRightIcon, + Columns2Icon, ExternalLinkIcon, GitBranchIcon, GitPullRequestIcon, + Rows3Icon, } from "lucide-react"; import { type ComponentProps, type FC, + useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { useQuery } from "react-query"; +import { cn } from "utils/cn"; interface FilesChangedPanelProps { chatId: string; + isExpanded?: boolean; } /** - * Extracts a short label like "owner/repo#123" from a GitHub PR URL. - * Falls back to the raw URL if parsing fails. + * Minimum container width (px) at which the file tree sidebar + * is shown alongside the diff list. */ -function formatPullRequestLabel(url: string): string { +const FILE_TREE_THRESHOLD = 1000; + +/** + * Extra CSS injected via the diff viewer's `unsafeCSS` option to make + * file headers sticky and adjust metadata layout. + */ +const STICKY_HEADER_CSS = [ + "[data-diffs-header] {", + " position: sticky; top: 0; z-index: 10;", + " font-size: 13px;", + " border-bottom: 1px solid hsl(var(--border-default));", + " background-color: hsl(var(--surface-quaternary)) !important;", + "}", + "[data-diffs-header] [data-metadata] { flex-direction: row-reverse; }", + "@media (prefers-color-scheme: dark) {", + " [data-diffs-header] { background-color: hsl(var(--surface-secondary)) !important; }", + "}", +].join(" "); + +type DiffStyle = "unified" | "split"; +const DIFF_STYLE_KEY = "agents.diff-view-style"; + +function loadDiffStyle(): DiffStyle { + if (typeof window === "undefined") { + return "unified"; + } + const stored = localStorage.getItem(DIFF_STYLE_KEY); + if (stored === "split" || stored === "unified") { + return stored; + } + return "unified"; +} + +/** + * Width of the file tree sidebar in pixels. + */ +const FILE_TREE_WIDTH = 300; + +/** + * Parses a GitHub PR URL into its components. + * Returns null if parsing fails. + */ +function parsePullRequestUrl(url: string): { + owner: string; + repo: string; + number: string; +} | null { try { const match = url.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/); if (match) { - return `${match[1]}/${match[2]}#${match[3]}`; + return { owner: match[1], repo: match[2], number: match[3] }; } } catch { - // Fall through to return the raw URL. + // Fall through. } - return url; + return null; } -export const FilesChangedPanel: FC = ({ chatId }) => { +// ------------------------------------------------------------------- +// File tree data model +// ------------------------------------------------------------------- + +/** Maps a diff change type to a Tailwind text-color class. */ +function changeColor(type?: ChangeTypes): string | undefined { + switch (type) { + case "new": + return "text-green-700 dark:text-green-300"; + case "deleted": + return "text-red-700 dark:text-red-300"; + case "rename-pure": + case "rename-changed": + return "text-orange-700 dark:text-orange-300"; + case "change": + return "text-orange-700 dark:text-orange-300"; + default: + return undefined; + } +} + +/** Short letter shown after the filename, matching VS Code style. */ +function changeLabel(type: ChangeTypes): string { + switch (type) { + case "new": + return "A"; + case "deleted": + return "D"; + case "rename-pure": + case "rename-changed": + return "R"; + case "change": + return "M"; + default: + return ""; + } +} + +interface FileTreeNode { + name: string; + fullPath: string; + type: "file" | "directory"; + children: FileTreeNode[]; + fileDiff?: FileDiffMetadata; +} + +/** + * Builds a nested tree from a flat list of file diffs. Directory + * nodes are created for every intermediate path segment. The + * result is sorted with directories first, then alphabetically. + * Single-child directory chains are collapsed so that e.g. + * `src/pages/AgentsPage` renders as one row. + */ +function buildFileTree(files: FileDiffMetadata[]): FileTreeNode[] { + const root: FileTreeNode[] = []; + + for (const file of files) { + const segments = file.name.split("/"); + let children = root; + + // Walk / create intermediate directory nodes. + for (let i = 0; i < segments.length - 1; i++) { + const seg = segments[i]; + let dir = children.find((n) => n.type === "directory" && n.name === seg); + if (!dir) { + dir = { + name: seg, + fullPath: segments.slice(0, i + 1).join("/"), + type: "directory", + children: [], + }; + children.push(dir); + } + children = dir.children; + } + + // Leaf file node. + const fileName = segments[segments.length - 1]; + children.push({ + name: fileName, + fullPath: file.name, + type: "file", + children: [], + fileDiff: file, + }); + } + + const sortNodes = (nodes: FileTreeNode[]): FileTreeNode[] => { + for (const node of nodes) { + if (node.children.length > 0) { + node.children = sortNodes(node.children); + } + } + return nodes.sort((a, b) => { + if (a.type !== b.type) { + return a.type === "directory" ? -1 : 1; + } + return a.name.localeCompare(b.name); + }); + }; + + // Collapse single-child directory chains into one node whose + // name uses path separators, e.g. "src/pages/AgentsPage". + const collapse = (nodes: FileTreeNode[]): FileTreeNode[] => { + for (const node of nodes) { + if (node.type === "directory") { + node.children = collapse(node.children); + // If this directory has exactly one child and it is also + // a directory, merge them. + while ( + node.children.length === 1 && + node.children[0].type === "directory" + ) { + const child = node.children[0]; + node.name = `${node.name}/${child.name}`; + node.fullPath = child.fullPath; + node.children = child.children; + } + } + } + return nodes; + }; + + return collapse(sortNodes(root)); +} + +// ------------------------------------------------------------------- +// Tree node renderer +// ------------------------------------------------------------------- + +const FileTreeNodeView: FC<{ + node: FileTreeNode; + depth: number; + activeFile: string | null; + onFileClick: (fullPath: string) => void; +}> = ({ node, depth, activeFile, onFileClick }) => { + const [expanded, setExpanded] = useState(true); + + if (node.type === "directory") { + return ( +
+ + {expanded && + node.children.map((child) => ( + + ))} +
+ ); + } + + const isActive = activeFile === node.fullPath; + + return ( + + ); +}; + +export const FilesChangedPanel: FC = ({ + chatId, + isExpanded, +}) => { const theme = useTheme(); const isDark = theme.palette.mode === "dark"; + const [diffStyle, setDiffStyle] = useState(loadDiffStyle); + const handleSetDiffStyle = useCallback((style: DiffStyle) => { + setDiffStyle(style); + localStorage.setItem(DIFF_STYLE_KEY, style); + }, []); + const diffOptions = useMemo(() => { const base = getDiffViewerOptions(isDark); return { ...base, + diffStyle, // Extend the base CSS to make file headers sticky so they // remain visible while scrolling through long diffs. - unsafeCSS: `${base.unsafeCSS ?? ""} [data-diffs-header] { position: sticky; top: 0; z-index: 10; background-color: hsl(var(--surface-primary)) !important; }`, + unsafeCSS: `${base.unsafeCSS ?? ""} ${STICKY_HEADER_CSS}`, }; - }, [isDark]); + }, [isDark, diffStyle]); // Memoize the per-file options object so every // receives the same reference and avoids re-highlighting @@ -97,18 +369,156 @@ export const FilesChangedPanel: FC = ({ chatId }) => { } }, [diffContentsQuery.data?.diff, chatId]); + const fileTree = useMemo(() => buildFileTree(parsedFiles), [parsedFiles]); + + // Sort diff blocks in the same order the file tree displays them + // (directories first, then alphabetical) so the rendering is + // consistent regardless of whether the sidebar is visible. + const sortedFiles = useMemo(() => { + const order = new Map(); + let idx = 0; + const walk = (nodes: FileTreeNode[]) => { + for (const node of nodes) { + if (node.type === "file") { + order.set(node.fullPath, idx++); + } else { + walk(node.children); + } + } + }; + walk(fileTree); + return [...parsedFiles].sort( + (a, b) => (order.get(a.name) ?? 0) - (order.get(b.name) ?? 0), + ); + }, [fileTree, parsedFiles]); + const pullRequestUrl = diffStatusQuery.data?.url; - const pullRequestLabel = pullRequestUrl - ? formatPullRequestLabel(pullRequestUrl) - : undefined; + const parsedPr = pullRequestUrl ? parsePullRequestUrl(pullRequestUrl) : null; + + // --------------------------------------------------------------- + // Container width measurement via ResizeObserver so we can decide + // whether to show the file tree sidebar without a prop from the + // parent. + // --------------------------------------------------------------- + const [containerWidth, setContainerWidth] = useState(0); + const roRef = useRef(null); + const containerRef = useCallback((el: HTMLDivElement | null) => { + if (roRef.current) { + roRef.current.disconnect(); + roRef.current = null; + } + if (!el) { + return; + } + setContainerWidth(el.getBoundingClientRect().width); + const ro = new ResizeObserver(([entry]) => { + setContainerWidth(entry.contentRect.width); + }); + ro.observe(el); + roRef.current = ro; + }, []); + + const showTree = + (isExpanded || containerWidth >= FILE_TREE_THRESHOLD) && + sortedFiles.length > 0; + + // --------------------------------------------------------------- + // Refs for each file diff wrapper so we can scroll-to and track + // which file is currently visible. + // --------------------------------------------------------------- + const fileRefs = useRef>(new Map()); + const [activeFile, setActiveFile] = useState(null); + + // Keep a ref callback that sets up per-file refs. + const setFileRef = useCallback((name: string, el: HTMLDivElement | null) => { + if (el) { + fileRefs.current.set(name, el); + } else { + fileRefs.current.delete(name); + } + }, []); + + // Track which file is at the top of the diff scroll area by + // listening to scroll events on the viewport. The active file + // is whichever file wrapper's top edge is closest to (but not + // below) the container's top — i.e. the one whose sticky + // header would be showing. + const diffViewportRef = useRef(null); + + useEffect(() => { + if (!showTree || sortedFiles.length === 0) { + return; + } + + const viewport = diffViewportRef.current; + if (!viewport) { + return; + } + + let rafId = 0; + const onScroll = () => { + cancelAnimationFrame(rafId); + rafId = requestAnimationFrame(() => { + const containerTop = viewport.getBoundingClientRect().top; + let bestName: string | null = null; + let bestDistance = Number.POSITIVE_INFINITY; + + for (const [name, el] of fileRefs.current.entries()) { + const rect = el.getBoundingClientRect(); + // The file "owns" the scroll position when its top + // is at or above the container top and its bottom is + // still below it. + if (rect.bottom > containerTop && rect.top <= containerTop + 1) { + const distance = Math.abs(rect.top - containerTop); + if (distance < bestDistance) { + bestDistance = distance; + bestName = name; + } + } + } + + // If nothing is at the top (e.g. scrolled to the very top + // with padding), pick the first file whose top is closest + // to the container top. + if (!bestName) { + for (const [name, el] of fileRefs.current.entries()) { + const dist = Math.abs( + el.getBoundingClientRect().top - containerTop, + ); + if (dist < bestDistance) { + bestDistance = dist; + bestName = name; + } + } + } + + if (bestName) { + setActiveFile(bestName); + } + }); + }; + + // Fire once to set initial state. + onScroll(); + + viewport.addEventListener("scroll", onScroll, { passive: true }); + return () => { + cancelAnimationFrame(rafId); + viewport.removeEventListener("scroll", onScroll); + }; + }, [showTree, sortedFiles.length]); + + const handleFileClick = useCallback((name: string) => { + const el = fileRefs.current.get(name); + if (el) { + el.scrollIntoView({ block: "start" }); + setActiveFile(name); + } + }, []); if (diffContentsQuery.isLoading || diffStatusQuery.isLoading) { return ( -
-
- - -
+
{Array.from({ length: 3 }, (_, i) => (
@@ -132,57 +542,130 @@ export const FilesChangedPanel: FC = ({ chatId }) => { } return ( -
+
{/* Header */} -
-
- {pullRequestUrl ? ( - <> - - - {pullRequestLabel} - - - ) : ( - <> - - - Files Changed - - - )} -
- - {pullRequestUrl && ( +
+ {pullRequestUrl && parsedPr ? ( - View PR - + + + + {parsedPr.owner}/{parsedPr.repo} + + #{parsedPr.number} + + + ) : pullRequestUrl ? ( + + + {pullRequestUrl} + + + ) : ( +
+ + Uncommitted changes +
)} + {/* Diff style toggle */} +
+ + +
- {/* Diff contents */} - {parsedFiles.length === 0 ? ( + {sortedFiles.length === 0 ? (
No file changes to display.
) : ( - -
- {parsedFiles.map((fileDiff) => ( - - ))} -
-
+
+ {/* File tree sidebar */} + {showTree && ( + + + + )} + {/* Diff list */} + { + const vp = node?.querySelector( + "[data-radix-scroll-area-viewport]", + ); + diffViewportRef.current = vp ?? null; + }} + > +
+ {sortedFiles.map((fileDiff) => ( +
setFileRef(fileDiff.name, el)} + > + +
+ ))} + {/* Spacer so the last file can scroll fully to the top. */} +
+
+ +
)}
); @@ -249,7 +732,13 @@ const LazyFileDiff: FC<{
+ className="p-4 space-y-2" + > + + + + +
); } diff --git a/site/src/pages/AgentsPage/RightPanel.tsx b/site/src/pages/AgentsPage/RightPanel.tsx index c1c92a8d1e..adfea92b8e 100644 --- a/site/src/pages/AgentsPage/RightPanel.tsx +++ b/site/src/pages/AgentsPage/RightPanel.tsx @@ -49,7 +49,9 @@ interface RightPanelProps { chatTitle?: string; isSidebarCollapsed?: boolean; onToggleSidebarCollapsed?: () => void; - tabContent: Record; + tabContent: Partial>; + /** Optional extra info per tab (e.g. diff stats). */ + tabMeta?: Partial>; /** Fires during drag with the live visual expanded state, and * null when the drag ends so the parent falls back to the * committed isExpanded prop. */ @@ -211,6 +213,7 @@ export const RightPanel = ({ isSidebarCollapsed, onToggleSidebarCollapsed, tabContent, + tabMeta, onVisualExpandedChange, }: RightPanelProps) => { const [activeTab, setActiveTab] = useState("git"); @@ -294,10 +297,10 @@ export const RightPanel = ({ "absolute top-0 left-0 z-20 hidden h-full w-1 cursor-col-resize select-none transition-colors hover:bg-content-link xl:block", visualExpanded && "-left-1", )} - />{" "} + />
{/* Tabbed header */} -
+
{/* Left side: sidebar toggle (expanded + collapsed only) + tabs */}
{visualExpanded && @@ -320,15 +323,17 @@ export const RightPanel = ({ size="lg" onClick={() => setActiveTab(tab.id)} className={cn( - "min-w-0 h-6 px-3", + "min-w-0 h-6 px-3 gap-3 py-0", activeTab === tab.id && "bg-surface-secondary", + tabMeta?.[tab.id] && "pr-0 items-stretch", )} > {tab.label} + {tabMeta?.[tab.id]} ))} -
{" "} - {/* Center: chat title */} +
+ {/* Center: chat title */}{" "}
{visualExpanded && chatTitle && ( diff --git a/site/static/seti.woff b/site/static/seti.woff new file mode 100644 index 0000000000..6a8a3da7cf Binary files /dev/null and b/site/static/seti.woff differ