Merge the file-icon theme shell from #594 (feat/file-icon-theme)
Brings in the theme plumbing around #531's API — the `fileIconTheme` pref thread, the settings row, the 20 dictionaries' keys and the theme loader's test — so the working tree is a complete, green starting point before the colored implementation is replaced. Conflicts (three; resolved by taking this line's side where it had moved on): - src/client/service.ts: the import block keeps `baseName, extOf` (the `isNarrowWidth` import the theme branch needed belongs to its own fresh-session seeding, which main has since rewritten) and the feature-list doc comment keeps main's "v0.19.0 REMOVED 'floatWindows'" note while adopting the sharper `fileIcons` wording (matched by exts / names / folderNames). - tests/plugin-shape.spec.ts and tests/prefs.spec.ts: main DELETED the store seeding tests these files used to hold and rewrote the schema assertions (the `SIDEBAR_PREFS_DEFAULTS` destructuring), so this line's side wins — the theme branch's re-added blocks describe behaviour main no longer has. Next commit removes `fileIconTheme` again along with the 563-entry dataset and its lazy chunk: DSH 0.1.5-rc.2 exports the official `FileTypeIcon` artwork, so the plugin renders the host's icons instead of shipping its own color table.
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
* `browser:<n>` the same way (no quota). The editor IS the files window
|
||||
* (the old standalone explorer merged into it).
|
||||
*/
|
||||
import { IconCodeOutline16, IconFolderOpen16, IconNewChatOutline16, IconPanelLeftOutline16, IconThinkOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import { IconCodeOutline16, IconFolderOpen16, IconFolderOpenOutline16, IconNewChatOutline16, IconPanelLeftOutline16, IconSparkle16, IconThinkOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { Context } from '../../context-types.ts'
|
||||
import { allLeaves, isAgentTabId, type SidebarState } from '../state.ts'
|
||||
import { t } from '../locales.ts'
|
||||
@@ -122,6 +122,28 @@ export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): read
|
||||
key: 'workspaceFence',
|
||||
title: () => t('settingsFenceTitle'),
|
||||
desc: () => t('settingsFenceDesc'),
|
||||
}, {
|
||||
// The file-icon theme: monochrome built-in glyphs (default) vs the
|
||||
// optional colored brand dataset, which is fetched from the lazy
|
||||
// `file-icons` chunk only when picked (see file-icon-theme.ts).
|
||||
key: 'fileIconTheme',
|
||||
type: 'select',
|
||||
title: () => t('settingsFileIconThemeTitle'),
|
||||
desc: () => t('settingsFileIconThemeDesc'),
|
||||
options: [
|
||||
{
|
||||
value: 'builtin',
|
||||
icon: (size: number) => <IconFolderOpenOutline16 size={size} />,
|
||||
title: () => t('settingsFileIconThemeBuiltin'),
|
||||
desc: () => t('settingsFileIconThemeBuiltinDesc'),
|
||||
},
|
||||
{
|
||||
value: 'colored',
|
||||
icon: (size: number) => <IconSparkle16 size={size} />,
|
||||
title: () => t('settingsFileIconThemeColored'),
|
||||
desc: () => t('settingsFileIconThemeColoredDesc'),
|
||||
},
|
||||
],
|
||||
}],
|
||||
render: ({ pluginSettings, updatePluginSetting }) => (
|
||||
<OpenWithSettings pluginSettings={pluginSettings} updatePluginSetting={updatePluginSetting} />
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
* client.js); an edit that does land while a core HMR happens is caught by
|
||||
* the ETag comparison on the next activation.
|
||||
*/
|
||||
export type ChunkName = 'terminal' | 'editor' | 'mermaid' | 'locale'
|
||||
export type ChunkName = 'terminal' | 'editor' | 'mermaid' | 'locale' | 'file-icons'
|
||||
|
||||
/** The module exports a chunk factory provides (namespace-ish record). */
|
||||
export type ChunkExports = Record<string, unknown>
|
||||
|
||||
@@ -0,0 +1,773 @@
|
||||
/**
|
||||
* Lazy chunk entry: the colored file-icon theme dataset — brand and generic
|
||||
* glyphs paired with their display color (extensions / exact file names /
|
||||
* directory names), consumed by `registerColoredFileIcons` in the core half
|
||||
* when the user turns the theme on.
|
||||
*
|
||||
* Data provenance: the tables below come from PR #429 (@fenter,
|
||||
* `feat/file-type-icons`, the 563-entry table); they are reused here as the
|
||||
* plugin's built-in OPTIONAL icon theme. The case-variant folder keys
|
||||
* (`Android`/`android`, `IOS`/`ios`) were folded into one rule because the
|
||||
* resolver matches names case-insensitively.
|
||||
*
|
||||
* Why a chunk: ~160 react-icons glyphs (Simple Icons brands + VSCodicons) and
|
||||
* ~700 lines of table would otherwise sit in the startup bundle for a theme
|
||||
* most users never enable. The core half only fetches this on demand.
|
||||
*
|
||||
* Skin contract: this module is the ONE documented exception to the
|
||||
* token-only rule (guide §12) — brand colors are identity, not chrome, and
|
||||
* the theme is opt-in, so they cannot ride `--dsw-alias-*`. Nothing here is
|
||||
* imported by the core bundle; keep it that way.
|
||||
*/
|
||||
import type { ReactNode } from 'react'
|
||||
import type { BetterSidebarService, FileIconDescriptor } from '../service.ts'
|
||||
import {
|
||||
SiAngular, SiApple, SiAstro, SiBabel, SiBiome, SiBun, SiC, SiClojure,
|
||||
SiCmake, SiCoffeescript, SiComposer, SiConventionalcommits, SiCplusplus,
|
||||
SiCrystal, SiCss, SiCypress, SiDart, SiDjango, SiDocker, SiDocusaurus,
|
||||
SiDotenv, SiDotnet, SiElixir, SiElm, SiErlang, SiEsbuild, SiEslint,
|
||||
SiGatsby, SiGit, SiGithub, SiGitlab, SiGnubash, SiGo, SiGodotengine,
|
||||
SiGooglecloud, SiGradle, SiGraphql, SiGrunt, SiGulp, SiHaskell, SiHelm,
|
||||
SiHtml5, SiJavascript, SiJenkins, SiJest, SiJulia, SiKotlin, SiKubernetes,
|
||||
SiLerna, SiLess, SiLua, SiMarkdown, SiMysql, SiNetlify, SiNextdotjs, SiNim,
|
||||
SiNixos, SiNodedotjs, SiNpm, SiNuxt, SiNx, SiOcaml, SiPerl, SiPhp, SiPnpm,
|
||||
SiPostcss, SiPrettier, SiPrisma, SiPython, SiRacket, SiReact, SiRemix,
|
||||
SiRollupdotjs, SiRuby, SiRust, SiSass, SiScala, SiSolidity, SiSqlite,
|
||||
SiStorybook, SiStylelint, SiSvelte, SiSwc, SiSwift, SiTailwindcss, SiTauri,
|
||||
SiTerraform, SiToml, SiTurbo, SiTypescript, SiVercel, SiVim, SiVite,
|
||||
SiVitepress, SiVitest, SiVuedotjs, SiWebpack, SiYaml, SiYarn, SiZig
|
||||
} from 'react-icons/si'
|
||||
import {
|
||||
VscBook, VscFile, VscFileBinary, VscFileCode, VscFileMedia, VscFilePdf,
|
||||
VscFileText, VscFileZip, VscFolder, VscFolderOpened, VscJson, VscKey,
|
||||
VscMap, VscMusic, VscSettings, VscTerminalCmd, VscTerminalPowershell
|
||||
} from 'react-icons/vsc'
|
||||
import type { IconType } from 'react-icons'
|
||||
|
||||
interface IconEntry {
|
||||
Icon: IconType
|
||||
color: string
|
||||
}
|
||||
|
||||
/** Render one colored icon at the tree's 14px size. */
|
||||
export function colored(entry: IconEntry, size = 14): ReactNode {
|
||||
const { Icon, color } = entry
|
||||
return <Icon size={size} style={{ color }} />
|
||||
}
|
||||
|
||||
// ── Brand file icons (extension → { icon, brand color }) ────────────────
|
||||
|
||||
export const FILE_ICON_BY_EXT: Partial<Record<string, IconEntry>> = {
|
||||
// TypeScript / JavaScript
|
||||
ts: { Icon: SiTypescript, color: '#3178C6' },
|
||||
tsx: { Icon: SiReact, color: '#61DAFB' },
|
||||
js: { Icon: SiJavascript, color: '#F7DF1E' },
|
||||
jsx: { Icon: SiReact, color: '#61DAFB' },
|
||||
mjs: { Icon: SiJavascript, color: '#F7DF1E' },
|
||||
cjs: { Icon: SiJavascript, color: '#F7DF1E' },
|
||||
mtjs: { Icon: SiJavascript, color: '#F7DF1E' },
|
||||
cts: { Icon: SiTypescript, color: '#3178C6' },
|
||||
// Web frameworks
|
||||
vue: { Icon: SiVuedotjs, color: '#42B883' },
|
||||
svelte: { Icon: SiSvelte, color: '#FF3E00' },
|
||||
astro: { Icon: SiAstro, color: '#FF5E00' },
|
||||
html: { Icon: SiHtml5, color: '#E34F26' },
|
||||
htm: { Icon: SiHtml5, color: '#E34F26' },
|
||||
xhtml: { Icon: SiHtml5, color: '#E34F26' },
|
||||
css: { Icon: SiCss, color: '#1572B6' },
|
||||
scss: { Icon: SiSass, color: '#CC6699' },
|
||||
sass: { Icon: SiSass, color: '#CC6699' },
|
||||
less: { Icon: SiLess, color: '#1D365D' },
|
||||
styl: { Icon: VscFileCode, color: '#BF1B1B' },
|
||||
pug: { Icon: VscFileCode, color: '#A86454' },
|
||||
jade: { Icon: VscFileCode, color: '#A86454' },
|
||||
hbs: { Icon: VscFileCode, color: '#F0772B' },
|
||||
handlebars: { Icon: VscFileCode, color: '#F0772B' },
|
||||
ejs: { Icon: VscFileCode, color: '#E44D26' },
|
||||
eta: { Icon: VscFileCode, color: '#E44D26' },
|
||||
njk: { Icon: VscFileCode, color: '#2C7873' },
|
||||
nunjucks: { Icon: VscFileCode, color: '#2C7873' },
|
||||
twig: { Icon: VscFileCode, color: '#41C47B' },
|
||||
liquid: { Icon: VscFileCode, color: '#41C47B' },
|
||||
// Systems
|
||||
py: { Icon: SiPython, color: '#3776AB' },
|
||||
pyi: { Icon: SiPython, color: '#3776AB' },
|
||||
pyw: { Icon: SiPython, color: '#3776AB' },
|
||||
rb: { Icon: SiRuby, color: '#CC342D' },
|
||||
go: { Icon: SiGo, color: '#00ADD8' },
|
||||
rs: { Icon: SiRust, color: '#DEA584' },
|
||||
php: { Icon: SiPhp, color: '#777BB4' },
|
||||
swift: { Icon: SiSwift, color: '#F05138' },
|
||||
kt: { Icon: SiKotlin, color: '#7F52FF' },
|
||||
kts: { Icon: SiKotlin, color: '#7F52FF' },
|
||||
dart: { Icon: SiDart, color: '#0175C2' },
|
||||
lua: { Icon: SiLua, color: '#2C2D72' },
|
||||
scala: { Icon: SiScala, color: '#DC322F' },
|
||||
clj: { Icon: SiClojure, color: '#5F5F5F' },
|
||||
cljs: { Icon: SiClojure, color: '#5F5F5F' },
|
||||
cljc: { Icon: SiClojure, color: '#5F5F5F' },
|
||||
edn: { Icon: SiClojure, color: '#5F5F5F' },
|
||||
pl: { Icon: SiPerl, color: '#393939' },
|
||||
pm: { Icon: SiPerl, color: '#393939' },
|
||||
hs: { Icon: SiHaskell, color: '#5E5086' },
|
||||
erl: { Icon: SiErlang, color: '#A90533' },
|
||||
jl: { Icon: SiJulia, color: '#9558B2' },
|
||||
ex: { Icon: SiElixir, color: '#4B275F' },
|
||||
exs: { Icon: SiElixir, color: '#4B275F' },
|
||||
elm: { Icon: SiElm, color: '#60B5CC' },
|
||||
nim: { Icon: SiNim, color: '#FFE95B' },
|
||||
zig: { Icon: SiZig, color: '#F7A41D' },
|
||||
ml: { Icon: SiOcaml, color: '#EC672F' },
|
||||
mli: { Icon: SiOcaml, color: '#EC672F' },
|
||||
sol: { Icon: SiSolidity, color: '#363636' },
|
||||
rkt: { Icon: SiRacket, color: '#9C1C1C' },
|
||||
vim: { Icon: SiVim, color: '#019733' },
|
||||
viml: { Icon: SiVim, color: '#019733' },
|
||||
ahk: { Icon: VscFileCode, color: '#334455' },
|
||||
feature: { Icon: VscFileCode, color: '#00C356' },
|
||||
groovy: { Icon: VscFileCode, color: '#4298B8' },
|
||||
gradle: { Icon: SiGradle, color: '#02303A' },
|
||||
proto: { Icon: VscFileCode, color: '#4285F4' },
|
||||
thrift: { Icon: VscFileCode, color: '#6B7280' },
|
||||
nix: { Icon: SiNixos, color: '#7EB1D6' },
|
||||
// C / C++
|
||||
c: { Icon: SiC, color: '#A8B9CC' },
|
||||
h: { Icon: SiC, color: '#A8B9CC' },
|
||||
cpp: { Icon: SiCplusplus, color: '#00599C' },
|
||||
cc: { Icon: SiCplusplus, color: '#00599C' },
|
||||
cxx: { Icon: SiCplusplus, color: '#00599C' },
|
||||
hpp: { Icon: SiCplusplus, color: '#00599C' },
|
||||
hxx: { Icon: SiCplusplus, color: '#00599C' },
|
||||
ino: { Icon: VscFileCode, color: '#00878F' },
|
||||
cs: { Icon: SiDotnet, color: '#512BD4' },
|
||||
fs: { Icon: SiDotnet, color: '#512BD4' },
|
||||
fsx: { Icon: SiDotnet, color: '#512BD4' },
|
||||
vb: { Icon: SiDotnet, color: '#512BD4' },
|
||||
// Shell
|
||||
sh: { Icon: SiGnubash, color: '#4EAA25' },
|
||||
bash: { Icon: SiGnubash, color: '#4EAA25' },
|
||||
zsh: { Icon: SiGnubash, color: '#4EAA25' },
|
||||
fish: { Icon: SiGnubash, color: '#4EAA25' },
|
||||
ps1: { Icon: VscTerminalPowershell, color: '#012456' },
|
||||
psd1: { Icon: VscTerminalPowershell, color: '#012456' },
|
||||
psm1: { Icon: VscTerminalPowershell, color: '#012456' },
|
||||
bat: { Icon: VscTerminalCmd, color: '#C8C8C8' },
|
||||
cmd: { Icon: VscTerminalCmd, color: '#C8C8C8' },
|
||||
awk: { Icon: VscFileCode, color: '#4EAA25' },
|
||||
// Data / config
|
||||
json: { Icon: VscJson, color: '#F7DF1E' },
|
||||
jsonc: { Icon: VscJson, color: '#F7DF1E' },
|
||||
json5: { Icon: VscJson, color: '#F7DF1E' },
|
||||
yaml: { Icon: SiYaml, color: '#CB171E' },
|
||||
yml: { Icon: SiYaml, color: '#CB171E' },
|
||||
toml: { Icon: SiToml, color: '#9C4121' },
|
||||
ini: { Icon: VscSettings, color: '#8B5CF6' },
|
||||
conf: { Icon: VscSettings, color: '#8B5CF6' },
|
||||
cfg: { Icon: VscSettings, color: '#8B5CF6' },
|
||||
env: { Icon: SiDotenv, color: '#ECD53F' },
|
||||
xml: { Icon: VscFileCode, color: '#E3792B' },
|
||||
plist: { Icon: VscSettings, color: '#6B7280' },
|
||||
properties: { Icon: VscSettings, color: '#8B5CF6' },
|
||||
csv: { Icon: VscFileText, color: '#2CB553' },
|
||||
tsv: { Icon: VscFileText, color: '#2CB553' },
|
||||
// Docs
|
||||
md: { Icon: SiMarkdown, color: '#0844B8' },
|
||||
markdown: { Icon: SiMarkdown, color: '#0844B8' },
|
||||
mdx: { Icon: SiMarkdown, color: '#0844B8' },
|
||||
txt: { Icon: VscFileText, color: '#6B7280' },
|
||||
log: { Icon: VscFileText, color: '#6B7280' },
|
||||
pdf: { Icon: VscFilePdf, color: '#E53935' },
|
||||
rst: { Icon: VscFileText, color: '#7D3CBE' },
|
||||
adoc: { Icon: VscFileText, color: '#E40046' },
|
||||
asciidoc: { Icon: VscFileText, color: '#E40046' },
|
||||
tex: { Icon: VscFileText, color: '#3D6117' },
|
||||
latex: { Icon: VscFileText, color: '#3D6117' },
|
||||
org: { Icon: VscFileText, color: '#77D0B0' },
|
||||
rtf: { Icon: VscFileText, color: '#6B7280' },
|
||||
// Office
|
||||
doc: { Icon: VscFileText, color: '#2B579A' },
|
||||
docx: { Icon: VscFileText, color: '#2B579A' },
|
||||
xls: { Icon: VscFileText, color: '#217346' },
|
||||
xlsx: { Icon: VscFileText, color: '#217346' },
|
||||
ppt: { Icon: VscFileText, color: '#D24726' },
|
||||
pptx: { Icon: VscFileText, color: '#D24726' },
|
||||
odt: { Icon: VscFileText, color: '#18A303' },
|
||||
ods: { Icon: VscFileText, color: '#18A303' },
|
||||
odp: { Icon: VscFileText, color: '#18A303' },
|
||||
// Images
|
||||
png: { Icon: VscFileMedia, color: '#A855F7' },
|
||||
jpg: { Icon: VscFileMedia, color: '#A855F7' },
|
||||
jpeg: { Icon: VscFileMedia, color: '#A855F7' },
|
||||
gif: { Icon: VscFileMedia, color: '#A855F7' },
|
||||
webp: { Icon: VscFileMedia, color: '#A855F7' },
|
||||
svg: { Icon: VscFileMedia, color: '#FFB13B' },
|
||||
ico: { Icon: VscFileMedia, color: '#A855F7' },
|
||||
bmp: { Icon: VscFileMedia, color: '#A855F7' },
|
||||
avif: { Icon: VscFileMedia, color: '#A855F7' },
|
||||
tiff: { Icon: VscFileMedia, color: '#A855F7' },
|
||||
tif: { Icon: VscFileMedia, color: '#A855F7' },
|
||||
heic: { Icon: VscFileMedia, color: '#A855F7' },
|
||||
heif: { Icon: VscFileMedia, color: '#A855F7' },
|
||||
psd: { Icon: VscFileMedia, color: '#31A8FF' },
|
||||
ai: { Icon: VscFileMedia, color: '#FF9A00' },
|
||||
eps: { Icon: VscFileMedia, color: '#FFB13B' },
|
||||
raw: { Icon: VscFileMedia, color: '#6B7280' },
|
||||
xcf: { Icon: VscFileMedia, color: '#535555' },
|
||||
// Video
|
||||
mp4: { Icon: VscFileMedia, color: '#FF6B6B' },
|
||||
webm: { Icon: VscFileMedia, color: '#FF6B6B' },
|
||||
mov: { Icon: VscFileMedia, color: '#FF6B6B' },
|
||||
mkv: { Icon: VscFileMedia, color: '#FF6B6B' },
|
||||
avi: { Icon: VscFileMedia, color: '#FF6B6B' },
|
||||
wmv: { Icon: VscFileMedia, color: '#FF6B6B' },
|
||||
flv: { Icon: VscFileMedia, color: '#FF6B6B' },
|
||||
m4v: { Icon: VscFileMedia, color: '#FF6B6B' },
|
||||
m3u8: { Icon: VscFileMedia, color: '#FF6B6B' },
|
||||
// Audio
|
||||
mp3: { Icon: VscMusic, color: '#EC4899' },
|
||||
wav: { Icon: VscMusic, color: '#EC4899' },
|
||||
flac: { Icon: VscMusic, color: '#EC4899' },
|
||||
ogg: { Icon: VscMusic, color: '#EC4899' },
|
||||
m4a: { Icon: VscMusic, color: '#EC4899' },
|
||||
aac: { Icon: VscMusic, color: '#EC4899' },
|
||||
opus: { Icon: VscMusic, color: '#EC4899' },
|
||||
midi: { Icon: VscMusic, color: '#EC4899' },
|
||||
mid: { Icon: VscMusic, color: '#EC4899' },
|
||||
wma: { Icon: VscMusic, color: '#EC4899' },
|
||||
aiff: { Icon: VscMusic, color: '#EC4899' },
|
||||
mka: { Icon: VscMusic, color: '#EC4899' },
|
||||
// Archives
|
||||
zip: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
tar: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
gz: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
tgz: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
rar: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
'7z': { Icon: VscFileZip, color: '#F59E0B' },
|
||||
bz2: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
xz: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
iso: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
dmg: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
jar: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
war: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
ear: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
apk: { Icon: VscFileZip, color: '#3DDC84' },
|
||||
deb: { Icon: VscFileZip, color: '#A81D33' },
|
||||
rpm: { Icon: VscFileZip, color: '#000000' },
|
||||
msi: { Icon: VscFileZip, color: '#6B7280' },
|
||||
lz: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
zst: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
cab: { Icon: VscFileZip, color: '#F59E0B' },
|
||||
// Database
|
||||
db: { Icon: SiSqlite, color: '#003B57' },
|
||||
sqlite: { Icon: SiSqlite, color: '#003B57' },
|
||||
sqlite3: { Icon: SiSqlite, color: '#003B57' },
|
||||
sql: { Icon: SiMysql, color: '#4479A1' },
|
||||
// Source maps
|
||||
map: { Icon: VscMap, color: '#6B7280' },
|
||||
// Keys / certs
|
||||
pem: { Icon: VscKey, color: '#10B981' },
|
||||
key: { Icon: VscKey, color: '#10B981' },
|
||||
crt: { Icon: VscKey, color: '#10B981' },
|
||||
pub: { Icon: VscKey, color: '#10B981' },
|
||||
cer: { Icon: VscKey, color: '#10B981' },
|
||||
der: { Icon: VscKey, color: '#10B981' },
|
||||
p12: { Icon: VscKey, color: '#10B981' },
|
||||
pfx: { Icon: VscKey, color: '#10B981' },
|
||||
jks: { Icon: VscKey, color: '#10B981' },
|
||||
keystore: { Icon: VscKey, color: '#10B981' },
|
||||
csr: { Icon: VscKey, color: '#10B981' },
|
||||
ovpn: { Icon: VscKey, color: '#10B981' },
|
||||
// Fonts
|
||||
ttf: { Icon: VscFileBinary, color: '#A855F7' },
|
||||
otf: { Icon: VscFileBinary, color: '#A855F7' },
|
||||
woff: { Icon: VscFileBinary, color: '#A855F7' },
|
||||
woff2: { Icon: VscFileBinary, color: '#A855F7' },
|
||||
eot: { Icon: VscFileBinary, color: '#A855F7' },
|
||||
// GraphQL
|
||||
graphql: { Icon: SiGraphql, color: '#E10098' },
|
||||
gql: { Icon: SiGraphql, color: '#E10098' },
|
||||
// Terraform / IaC
|
||||
tf: { Icon: SiTerraform, color: '#7B42BC' },
|
||||
tfvars: { Icon: SiTerraform, color: '#7B42BC' },
|
||||
tfstate: { Icon: SiTerraform, color: '#7B42BC' },
|
||||
hcl: { Icon: SiTerraform, color: '#7B42BC' },
|
||||
// Godot
|
||||
gd: { Icon: SiGodotengine, color: '#478CBF' },
|
||||
// Crystal
|
||||
cr: { Icon: SiCrystal, color: '#000000' },
|
||||
// CoffeeScript
|
||||
coffee: { Icon: SiCoffeescript, color: '#2F2614' },
|
||||
}
|
||||
|
||||
// ── Special filenames (exact match, case-sensitive) ──────────────────────
|
||||
|
||||
export const FILE_ICON_BY_NAME: Partial<Record<string, IconEntry>> = {
|
||||
// Package managers
|
||||
'package.json': { Icon: SiNpm, color: '#CB3837' },
|
||||
'package-lock.json': { Icon: SiNpm, color: '#CB3837' },
|
||||
'pnpm-lock.yaml': { Icon: SiPnpm, color: '#F69220' },
|
||||
'yarn.lock': { Icon: SiYarn, color: '#2C8EBB' },
|
||||
'Cargo.lock': { Icon: SiRust, color: '#DEA584' },
|
||||
'Cargo.toml': { Icon: SiRust, color: '#DEA584' },
|
||||
'go.sum': { Icon: SiGo, color: '#00ADD8' },
|
||||
'go.mod': { Icon: SiGo, color: '#00ADD8' },
|
||||
'go.work': { Icon: SiGo, color: '#00ADD8' },
|
||||
'composer.json': { Icon: SiComposer, color: '#885630' },
|
||||
'composer.lock': { Icon: SiComposer, color: '#885630' },
|
||||
'poetry.lock': { Icon: SiPython, color: '#3776AB' },
|
||||
'uv.lock': { Icon: SiPython, color: '#3776AB' },
|
||||
'bun.lockb': { Icon: SiBun, color: '#FBF0DF' },
|
||||
'bun.lock': { Icon: SiBun, color: '#FBF0DF' },
|
||||
'mix.lock': { Icon: SiElixir, color: '#4B275F' },
|
||||
'Podfile': { Icon: SiApple, color: '#A6A6A6' },
|
||||
'Podfile.lock': { Icon: SiApple, color: '#A6A6A6' },
|
||||
'Package.resolved': { Icon: SiSwift, color: '#F05138' },
|
||||
'packages.lock.json': { Icon: SiNodedotjs, color: '#339933' },
|
||||
'flake.lock': { Icon: SiNixos, color: '#7EB1D6' },
|
||||
'flake.nix': { Icon: SiNixos, color: '#7EB1D6' },
|
||||
'default.nix': { Icon: SiNixos, color: '#7EB1D6' },
|
||||
'shell.nix': { Icon: SiNixos, color: '#7EB1D6' },
|
||||
// Git
|
||||
'.gitignore': { Icon: SiGit, color: '#F05032' },
|
||||
'.gitattributes': { Icon: SiGit, color: '#F05032' },
|
||||
'.gitmodules': { Icon: SiGit, color: '#F05032' },
|
||||
'.gitconfig': { Icon: SiGit, color: '#F05032' },
|
||||
'.gitmessage': { Icon: SiGit, color: '#F05032' },
|
||||
'.git-blame-ignore-revs': { Icon: SiGit, color: '#F05032' },
|
||||
'.gitkeep': { Icon: VscFile, color: '#6B7280' },
|
||||
'.keep': { Icon: VscFile, color: '#6B7280' },
|
||||
// Docs
|
||||
'LICENSE': { Icon: VscBook, color: '#6B7280' },
|
||||
'LICENSE.md': { Icon: VscBook, color: '#6B7280' },
|
||||
'LICENSE.txt': { Icon: VscBook, color: '#6B7280' },
|
||||
'README.md': { Icon: SiMarkdown, color: '#0844B8' },
|
||||
'CHANGELOG.md': { Icon: SiMarkdown, color: '#0844B8' },
|
||||
'CONTRIBUTING.md': { Icon: VscBook, color: '#6B7280' },
|
||||
'CODE_OF_CONDUCT.md': { Icon: VscBook, color: '#6B7280' },
|
||||
'SECURITY.md': { Icon: VscBook, color: '#6B7280' },
|
||||
'AUTHORS': { Icon: VscBook, color: '#6B7280' },
|
||||
'MAINTAINERS': { Icon: VscBook, color: '#6B7280' },
|
||||
'CODEOWNERS': { Icon: SiGithub, color: '#181717' },
|
||||
// Docker
|
||||
'Dockerfile': { Icon: SiDocker, color: '#2496ED' },
|
||||
'Dockerfile.dev': { Icon: SiDocker, color: '#2496ED' },
|
||||
'Dockerfile.prod': { Icon: SiDocker, color: '#2496ED' },
|
||||
'Dockerfile.test': { Icon: SiDocker, color: '#2496ED' },
|
||||
'docker-compose.yml': { Icon: SiDocker, color: '#2496ED' },
|
||||
'docker-compose.yaml': { Icon: SiDocker, color: '#2496ED' },
|
||||
'docker-compose.override.yml': { Icon: SiDocker, color: '#2496ED' },
|
||||
'Containerfile': { Icon: SiDocker, color: '#2496ED' },
|
||||
'.dockerignore': { Icon: SiDocker, color: '#2496ED' },
|
||||
'.containerignore': { Icon: SiDocker, color: '#2496ED' },
|
||||
// Build
|
||||
'Makefile': { Icon: SiCmake, color: '#064F8C' },
|
||||
'CMakeLists.txt': { Icon: SiCmake, color: '#064F8C' },
|
||||
'.editorconfig': { Icon: VscSettings, color: '#E0EFEF' },
|
||||
// Env
|
||||
'.env': { Icon: SiDotenv, color: '#ECD53F' },
|
||||
'.env.local': { Icon: SiDotenv, color: '#ECD53F' },
|
||||
'.env.development': { Icon: SiDotenv, color: '#ECD53F' },
|
||||
'.env.production': { Icon: SiDotenv, color: '#ECD53F' },
|
||||
'.env.example': { Icon: SiDotenv, color: '#ECD53F' },
|
||||
'.env.test': { Icon: SiDotenv, color: '#ECD53F' },
|
||||
'.env.staging': { Icon: SiDotenv, color: '#ECD53F' },
|
||||
'.env.docker': { Icon: SiDotenv, color: '#ECD53F' },
|
||||
// TypeScript / JS config
|
||||
'tsconfig.json': { Icon: SiTypescript, color: '#3178C6' },
|
||||
'tsconfig.base.json': { Icon: SiTypescript, color: '#3178C6' },
|
||||
'jsconfig.json': { Icon: SiJavascript, color: '#F7DF1E' },
|
||||
// Bundlers / build tools
|
||||
'vite.config.ts': { Icon: SiVite, color: '#646CFF' },
|
||||
'vite.config.js': { Icon: SiVite, color: '#646CFF' },
|
||||
'vite.config.mts': { Icon: SiVite, color: '#646CFF' },
|
||||
'vite.config.mjs': { Icon: SiVite, color: '#646CFF' },
|
||||
'webpack.config.js': { Icon: SiWebpack, color: '#1C78C0' },
|
||||
'webpack.config.ts': { Icon: SiWebpack, color: '#1C78C0' },
|
||||
'rollup.config.js': { Icon: SiRollupdotjs, color: '#C3413D' },
|
||||
'rollup.config.mjs': { Icon: SiRollupdotjs, color: '#C3413D' },
|
||||
'esbuild.config.js': { Icon: SiEsbuild, color: '#FFCF00' },
|
||||
'gulpfile.js': { Icon: SiGulp, color: '#CF4647' },
|
||||
'gulpfile.ts': { Icon: SiGulp, color: '#CF4647' },
|
||||
'gulpfile.mjs': { Icon: SiGulp, color: '#CF4647' },
|
||||
'gruntfile.js': { Icon: SiGrunt, color: '#FBA937' },
|
||||
'tsup.config.ts': { Icon: VscSettings, color: '#646CFF' },
|
||||
'tsdown.config.ts': { Icon: VscSettings, color: '#646CFF' },
|
||||
'unbuild.config.ts': { Icon: VscSettings, color: '#646CFF' },
|
||||
'farm.config.ts': { Icon: VscSettings, color: '#646CFF' },
|
||||
'rspack.config.js': { Icon: VscSettings, color: '#646CFF' },
|
||||
'swc.config.js': { Icon: SiSwc, color: '#E0BD6E' },
|
||||
'.swcrc': { Icon: SiSwc, color: '#E0BD6E' },
|
||||
'turbo.json': { Icon: SiTurbo, color: '#6DD3C6' },
|
||||
'turbo.config.json': { Icon: SiTurbo, color: '#6DD3C6' },
|
||||
'nx.json': { Icon: SiNx, color: '#14305F' },
|
||||
'lerna.json': { Icon: SiLerna, color: '#9333EA' },
|
||||
'biome.json': { Icon: SiBiome, color: '#60A5FA' },
|
||||
// Frameworks
|
||||
'next.config.js': { Icon: SiNextdotjs, color: '#000000' },
|
||||
'next.config.mjs': { Icon: SiNextdotjs, color: '#000000' },
|
||||
'next.config.ts': { Icon: SiNextdotjs, color: '#000000' },
|
||||
'nuxt.config.ts': { Icon: SiNuxt, color: '#00DC82' },
|
||||
'nuxt.config.js': { Icon: SiNuxt, color: '#00DC82' },
|
||||
'angular.json': { Icon: SiAngular, color: '#DD0031' },
|
||||
'svelte.config.js': { Icon: SiSvelte, color: '#FF3E00' },
|
||||
'astro.config.mjs': { Icon: SiAstro, color: '#FF5E00' },
|
||||
'astro.config.ts': { Icon: SiAstro, color: '#FF5E00' },
|
||||
'remix.config.js': { Icon: SiRemix, color: '#3992FF' },
|
||||
'gatsby-config.js': { Icon: SiGatsby, color: '#663399' },
|
||||
'gatsby-config.ts': { Icon: SiGatsby, color: '#663399' },
|
||||
'tailwind.config.js': { Icon: SiTailwindcss, color: '#06B6D4' },
|
||||
'tailwind.config.ts': { Icon: SiTailwindcss, color: '#06B6D4' },
|
||||
'tauri.conf.json': { Icon: SiTauri, color: '#FFC131' },
|
||||
'vercel.json': { Icon: SiVercel, color: '#000000' },
|
||||
'netlify.toml': { Icon: SiNetlify, color: '#00C7B7' },
|
||||
'docusaurus.config.js': { Icon: SiDocusaurus, color: '#3ECCF4' },
|
||||
'docusaurus.config.ts': { Icon: SiDocusaurus, color: '#3ECCF4' },
|
||||
'vitepress.config.ts': { Icon: SiVitepress, color: '#5672CD' },
|
||||
'vitepress.config.js': { Icon: SiVitepress, color: '#5672CD' },
|
||||
// Testing
|
||||
'playwright.config.ts': { Icon: VscSettings, color: '#2EAD33' },
|
||||
'playwright.config.js': { Icon: VscSettings, color: '#2EAD33' },
|
||||
'vitest.config.ts': { Icon: SiVitest, color: '#FCC00B' },
|
||||
'vitest.config.js': { Icon: SiVitest, color: '#FCC00B' },
|
||||
'jest.config.js': { Icon: SiJest, color: '#C21325' },
|
||||
'jest.config.ts': { Icon: SiJest, color: '#C21325' },
|
||||
'cypress.config.ts': { Icon: SiCypress, color: '#17202C' },
|
||||
'cypress.config.js': { Icon: SiCypress, color: '#17202C' },
|
||||
// Linting / formatting
|
||||
'.eslintrc.js': { Icon: SiEslint, color: '#4B32C3' },
|
||||
'.eslintrc.json': { Icon: SiEslint, color: '#4B32C3' },
|
||||
'.eslintrc.cjs': { Icon: SiEslint, color: '#4B32C3' },
|
||||
'.eslintrc.mjs': { Icon: SiEslint, color: '#4B32C3' },
|
||||
'.eslintrc.yml': { Icon: SiEslint, color: '#4B32C3' },
|
||||
'eslint.config.js': { Icon: SiEslint, color: '#4B32C3' },
|
||||
'eslint.config.mjs': { Icon: SiEslint, color: '#4B32C3' },
|
||||
'eslint.config.ts': { Icon: SiEslint, color: '#4B32C3' },
|
||||
'.eslintignore': { Icon: SiEslint, color: '#4B32C3' },
|
||||
'.prettierrc': { Icon: SiPrettier, color: '#F7B93E' },
|
||||
'.prettierrc.json': { Icon: SiPrettier, color: '#F7B93E' },
|
||||
'.prettierrc.js': { Icon: SiPrettier, color: '#F7B93E' },
|
||||
'.prettierrc.cjs': { Icon: SiPrettier, color: '#F7B93E' },
|
||||
'.prettierrc.mjs': { Icon: SiPrettier, color: '#F7B93E' },
|
||||
'.prettierrc.toml': { Icon: SiPrettier, color: '#F7B93E' },
|
||||
'.prettierrc.json5': { Icon: SiPrettier, color: '#F7B93E' },
|
||||
'prettier.config.js': { Icon: SiPrettier, color: '#F7B93E' },
|
||||
'prettier.config.mjs': { Icon: SiPrettier, color: '#F7B93E' },
|
||||
'.prettierignore': { Icon: SiPrettier, color: '#F7B93E' },
|
||||
'.stylelintrc': { Icon: SiStylelint, color: '#263A6F' },
|
||||
'.stylelintrc.json': { Icon: SiStylelint, color: '#263A6F' },
|
||||
'.stylelintrc.js': { Icon: SiStylelint, color: '#263A6F' },
|
||||
'.stylelintignore': { Icon: SiStylelint, color: '#263A6F' },
|
||||
'babel.config.js': { Icon: SiBabel, color: '#F9DC3D' },
|
||||
'babel.config.json': { Icon: SiBabel, color: '#F9DC3D' },
|
||||
'.babelrc': { Icon: SiBabel, color: '#F9DC3D' },
|
||||
'.babelrc.json': { Icon: SiBabel, color: '#F9DC3D' },
|
||||
'.babelrc.cjs': { Icon: SiBabel, color: '#F9DC3D' },
|
||||
'postcss.config.js': { Icon: SiPostcss, color: '#DD3A8C' },
|
||||
'postcss.config.ts': { Icon: SiPostcss, color: '#DD3A8C' },
|
||||
'.postcssrc': { Icon: SiPostcss, color: '#DD3A8C' },
|
||||
'.postcssrc.json': { Icon: SiPostcss, color: '#DD3A8C' },
|
||||
// Other config
|
||||
'.npmrc': { Icon: SiNpm, color: '#CB3837' },
|
||||
'.yarnrc': { Icon: SiYarn, color: '#2C8EBB' },
|
||||
'.pnpmrc': { Icon: SiPnpm, color: '#F69220' },
|
||||
'.npmignore': { Icon: SiNpm, color: '#CB3837' },
|
||||
'.nvmrc': { Icon: SiNodedotjs, color: '#339933' },
|
||||
'.node-version': { Icon: SiNodedotjs, color: '#339933' },
|
||||
'.ruby-version': { Icon: SiRuby, color: '#CC342D' },
|
||||
'.python-version': { Icon: SiPython, color: '#3776AB' },
|
||||
'.lintstagedrc': { Icon: VscSettings, color: '#F7B500' },
|
||||
'.lintstagedrc.json': { Icon: VscSettings, color: '#F7B500' },
|
||||
'.commitlintrc': { Icon: SiConventionalcommits, color: '#FE5197' },
|
||||
'.commitlintrc.json': { Icon: SiConventionalcommits, color: '#FE5197' },
|
||||
'.releaserc': { Icon: VscSettings, color: '#F7B500' },
|
||||
'.releaserc.json': { Icon: VscSettings, color: '#F7B500' },
|
||||
'.czrc': { Icon: VscSettings, color: '#F7B500' },
|
||||
// Ruby / Python
|
||||
'Gemfile': { Icon: SiRuby, color: '#CC342D' },
|
||||
'Gemfile.lock': { Icon: SiRuby, color: '#CC342D' },
|
||||
'Rakefile': { Icon: SiRuby, color: '#CC342D' },
|
||||
'requirements.txt': { Icon: SiPython, color: '#3776AB' },
|
||||
'setup.py': { Icon: SiPython, color: '#3776AB' },
|
||||
'pyproject.toml': { Icon: SiPython, color: '#3776AB' },
|
||||
'manage.py': { Icon: SiDjango, color: '#092E20' },
|
||||
// CI/CD
|
||||
'Jenkinsfile': { Icon: SiJenkins, color: '#D24939' },
|
||||
'.gitlab-ci.yml': { Icon: SiGitlab, color: '#FC6D26' },
|
||||
'.travis.yml': { Icon: VscSettings, color: '#3EAAAF' },
|
||||
'azure-pipelines.yml': { Icon: VscSettings, color: '#0078D4' },
|
||||
'bitbucket-pipelines.yml': { Icon: VscSettings, color: '#0052CC' },
|
||||
'.drone.yml': { Icon: VscSettings, color: '#212121' },
|
||||
'cloudbuild.yaml': { Icon: SiGooglecloud, color: '#4285F4' },
|
||||
// K8s / Helm
|
||||
'Chart.yaml': { Icon: SiHelm, color: '#0F1689' },
|
||||
'values.yaml': { Icon: SiHelm, color: '#0F1689' },
|
||||
'kustomization.yaml': { Icon: SiKubernetes, color: '#326CE5' },
|
||||
'kustomization.yml': { Icon: SiKubernetes, color: '#326CE5' },
|
||||
'skaffold.yaml': { Icon: SiKubernetes, color: '#326CE5' },
|
||||
'.helmignore': { Icon: SiHelm, color: '#0F1689' },
|
||||
// Serverless
|
||||
'serverless.yml': { Icon: VscSettings, color: '#6B7280' },
|
||||
'sam.yaml': { Icon: VscSettings, color: '#6B7280' },
|
||||
'appspec.yml': { Icon: VscSettings, color: '#6B7280' },
|
||||
// Prisma
|
||||
'schema.prisma': { Icon: SiPrisma, color: '#5D8AA8' },
|
||||
// Misc
|
||||
'Procfile': { Icon: SiNodedotjs, color: '#339933' },
|
||||
'.storybook/main.js': { Icon: SiStorybook, color: '#FF4785' },
|
||||
'.storybook/main.ts': { Icon: SiStorybook, color: '#FF4785' },
|
||||
'.storybook/preview.js': { Icon: SiStorybook, color: '#FF4785' },
|
||||
'.storybook/preview.ts': { Icon: SiStorybook, color: '#FF4785' },
|
||||
}
|
||||
|
||||
// ── Special folder colors ───────────────────────────────────────────────
|
||||
|
||||
/** Folder name → tint color (applied to VscFolder / VscFolderOpened). */
|
||||
export const FOLDER_COLOR: Record<string, string> = {
|
||||
// Package / dependency
|
||||
node_modules: '#CB3837',
|
||||
packages: '#CB3837',
|
||||
// Source
|
||||
src: '#3178C6',
|
||||
source: '#3178C6',
|
||||
app: '#3178C6',
|
||||
apps: '#3178C6',
|
||||
core: '#A855F7',
|
||||
common: '#6B7280',
|
||||
shared: '#6B7280',
|
||||
internal: '#6B7280',
|
||||
modules: '#3178C6',
|
||||
plugins: '#42B883',
|
||||
// Git
|
||||
'.git': '#F05032',
|
||||
'.github': '#F05032',
|
||||
'.husky': '#F05032',
|
||||
// Output
|
||||
public: '#42B883',
|
||||
static: '#42B883',
|
||||
dist: '#E34F26',
|
||||
build: '#E34F26',
|
||||
out: '#E34F26',
|
||||
'.output': '#E34F26',
|
||||
'.next': '#000000',
|
||||
'.nuxt': '#00DC82',
|
||||
'.svelte-kit': '#FF3E00',
|
||||
'.astro': '#FF5E00',
|
||||
'.turbo': '#6DD3C6',
|
||||
'.parcel-cache': '#6B7280',
|
||||
'.vercel': '#000000',
|
||||
'.netlify': '#00C7B7',
|
||||
'.serverless': '#6B7280',
|
||||
'.terraform': '#7B42BC',
|
||||
'.terragrunt-cache': '#7B42BC',
|
||||
'.docusaurus': '#3ECCF4',
|
||||
'.cache': '#6B7280',
|
||||
// Testing
|
||||
test: '#F7DF1E',
|
||||
tests: '#F7DF1E',
|
||||
__tests__: '#F7DF1E',
|
||||
__mocks__: '#F7DF1E',
|
||||
__snapshots__: '#F7DF1E',
|
||||
__pacts__: '#F7DF1E',
|
||||
spec: '#F7DF1E',
|
||||
specs: '#F7DF1E',
|
||||
mocks: '#F7DF1E',
|
||||
fixtures: '#F7DF1E',
|
||||
e2e: '#F7DF1E',
|
||||
cypress: '#17202C',
|
||||
'.nyc_output': '#F7DF1E',
|
||||
coverage: '#42B883',
|
||||
// IDE / editor
|
||||
'.vscode': '#3178C6',
|
||||
'.idea': '#3178C6',
|
||||
'.devcontainer': '#3178C6',
|
||||
'.codesandbox': '#6B7280',
|
||||
'.stackblitz': '#6B7280',
|
||||
'.gitpod': '#6B7280',
|
||||
// Assets
|
||||
assets: '#A855F7',
|
||||
images: '#A855F7',
|
||||
img: '#A855F7',
|
||||
icons: '#A855F7',
|
||||
fonts: '#A855F7',
|
||||
media: '#A855F7',
|
||||
resources: '#A855F7',
|
||||
content: '#A855F7',
|
||||
// Styles
|
||||
styles: '#CC6699',
|
||||
stylesheets: '#CC6699',
|
||||
css: '#CC6699',
|
||||
themes: '#CC6699',
|
||||
templates: '#CC6699',
|
||||
partials: '#CC6699',
|
||||
layouts: '#CC6699',
|
||||
// Components
|
||||
components: '#42B883',
|
||||
composables: '#42B883',
|
||||
hooks: '#42B883',
|
||||
// Library
|
||||
lib: '#6B7280',
|
||||
libs: '#6B7280',
|
||||
vendor: '#6B7280',
|
||||
// Docs
|
||||
docs: '#0844B8',
|
||||
documentation: '#0844B8',
|
||||
examples: '#6B7280',
|
||||
playground: '#42B883',
|
||||
benchmarks: '#4EAA25',
|
||||
// Scripts
|
||||
scripts: '#4EAA25',
|
||||
bin: '#4EAA25',
|
||||
tools: '#4EAA25',
|
||||
// Config
|
||||
config: '#F7B500',
|
||||
configs: '#F7B500',
|
||||
'.config': '#F7B500',
|
||||
// i18n
|
||||
locales: '#F7B500',
|
||||
i18n: '#F7B500',
|
||||
translations: '#F7B500',
|
||||
lang: '#F7B500',
|
||||
language: '#F7B500',
|
||||
// Routing
|
||||
pages: '#3178C6',
|
||||
views: '#3178C6',
|
||||
routes: '#3178C6',
|
||||
router: '#3178C6',
|
||||
// State
|
||||
store: '#A855F7',
|
||||
stores: '#A855F7',
|
||||
state: '#A855F7',
|
||||
// Utils
|
||||
utils: '#6B7280',
|
||||
helpers: '#6B7280',
|
||||
types: '#3178C6',
|
||||
typings: '#3178C6',
|
||||
// Backend
|
||||
api: '#42B883',
|
||||
server: '#42B883',
|
||||
services: '#42B883',
|
||||
client: '#3178C6',
|
||||
clients: '#3178C6',
|
||||
middleware: '#CC6699',
|
||||
controllers: '#42B883',
|
||||
models: '#A855F7',
|
||||
entities: '#A855F7',
|
||||
repositories: '#A855F7',
|
||||
migrations: '#4479A1',
|
||||
seeds: '#4479A1',
|
||||
factories: '#4479A1',
|
||||
// Jobs / workers
|
||||
tasks: '#4EAA25',
|
||||
jobs: '#4EAA25',
|
||||
workers: '#4EAA25',
|
||||
queues: '#4EAA25',
|
||||
// Events
|
||||
events: '#F7DF1E',
|
||||
listeners: '#F7DF1E',
|
||||
observers: '#F7DF1E',
|
||||
notifications: '#F7DF1E',
|
||||
mail: '#F7DF1E',
|
||||
emails: '#F7DF1E',
|
||||
channels: '#F7DF1E',
|
||||
commands: '#4EAA25',
|
||||
console: '#4EAA25',
|
||||
cron: '#4EAA25',
|
||||
// Temp / data
|
||||
pub: '#42B883',
|
||||
tmp: '#6B7280',
|
||||
temp: '#6B7280',
|
||||
cache: '#6B7280',
|
||||
logs: '#6B7280',
|
||||
data: '#A855F7',
|
||||
backup: '#6B7280',
|
||||
www: '#42B883',
|
||||
webroot: '#42B883',
|
||||
// CI/CD
|
||||
'.gitlab': '#FC6D26',
|
||||
'.circleci': '#6B7280',
|
||||
'.travis': '#6B7280',
|
||||
'.drone': '#6B7280',
|
||||
'.azure': '#0078D4',
|
||||
// Mobile
|
||||
android: '#3DDC84',
|
||||
ios: '#000000',
|
||||
Platforms: '#3178C6',
|
||||
}
|
||||
|
||||
// ── Registration ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Register the dataset through the public `registerFileIcon` API: one
|
||||
* descriptor per distinct glyph+color (the resolver walks descriptors per
|
||||
* row, so 563 rules collapse into ~250 registrations), plus one per folder
|
||||
* tint. Returns a disposer that removes every registration.
|
||||
*
|
||||
* Folder tints ride `folderNames` only — they claim the directories they
|
||||
* name, never every folder, so unnamed directories keep the built-in glyphs.
|
||||
*/
|
||||
export function registerColoredFileIcons(service: BetterSidebarService): () => void {
|
||||
const disposers: (() => void)[] = []
|
||||
const register = (descriptor: FileIconDescriptor): void => {
|
||||
disposers.push(service.registerFileIcon(descriptor))
|
||||
}
|
||||
let seq = 0
|
||||
|
||||
// Extensions: group the table by (glyph, color) → one descriptor per group.
|
||||
for (const [Icon, byColor] of groupByGlyph(FILE_ICON_BY_EXT)) {
|
||||
for (const [color, exts] of byColor) {
|
||||
register({
|
||||
id: `colored:ext:${seq++}`,
|
||||
exts,
|
||||
icon: (_path, size) => colored({ Icon, color }, size),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Exact file names (package.json, Dockerfile, …): same grouping, name rules.
|
||||
for (const [Icon, byColor] of groupByGlyph(FILE_ICON_BY_NAME)) {
|
||||
for (const [color, names] of byColor) {
|
||||
register({
|
||||
id: `colored:name:${seq++}`,
|
||||
names,
|
||||
icon: (_path, size) => colored({ Icon, color }, size),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Directory tints: VscFolder/VscFolderOpened in the rule's color, grouped
|
||||
// by color (the folder table maps name → color, not name → glyph).
|
||||
const foldersByColor = new Map<string, string[]>()
|
||||
for (const [name, color] of Object.entries(FOLDER_COLOR)) {
|
||||
const names = foldersByColor.get(color)
|
||||
if (names === undefined) foldersByColor.set(color, [name])
|
||||
else names.push(name)
|
||||
}
|
||||
for (const [color, folderNames] of foldersByColor) {
|
||||
register({
|
||||
id: `colored:folder:${seq++}`,
|
||||
folderNames,
|
||||
icon: (_path, size, open) => open === true
|
||||
? <VscFolderOpened size={size} style={{ color }} />
|
||||
: <VscFolder size={size} style={{ color }} />,
|
||||
})
|
||||
}
|
||||
|
||||
return () => { for (const dispose of disposers) dispose() }
|
||||
}
|
||||
|
||||
/** Regroup one name→{Icon,color} table into Icon → color → keys. */
|
||||
function groupByGlyph(table: Partial<Record<string, IconEntry>>): Map<IconType, Map<string, string[]>> {
|
||||
const grouped = new Map<IconType, Map<string, string[]>>()
|
||||
for (const [key, entry] of Object.entries(table)) {
|
||||
if (entry === undefined) continue
|
||||
let byColor = grouped.get(entry.Icon)
|
||||
if (byColor === undefined) {
|
||||
byColor = new Map()
|
||||
grouped.set(entry.Icon, byColor)
|
||||
}
|
||||
const keys = byColor.get(entry.color)
|
||||
if (keys === undefined) byColor.set(entry.color, [key])
|
||||
else keys.push(key)
|
||||
}
|
||||
return grouped
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* The optional colored file-icon theme (pref `fileIconTheme`).
|
||||
*
|
||||
* `'builtin'` (the default) is the monochrome glyph map the tree already
|
||||
* uses — nothing to do. `'colored'` fetches the lazy `file-icons` chunk
|
||||
* (~160 brand glyphs + 563 rules) and registers it through the public
|
||||
* `registerFileIcon` API; switching back disposes those registrations. The
|
||||
* chunk is only fetched when the user picks the theme, so the startup bundle
|
||||
* never carries icon data it may not render.
|
||||
*
|
||||
* The loader is generation-guarded: a fast toggle (or an unload) while the
|
||||
* chunk is still in flight must not register the stale dataset.
|
||||
*/
|
||||
import { loadChunk } from './chunk-loader.ts'
|
||||
import type { BetterSidebarService } from './service.ts'
|
||||
|
||||
/** The pref value selecting the built-in monochrome glyphs (default). */
|
||||
export const FILE_ICON_THEME_BUILTIN = 'builtin'
|
||||
/** The pref value selecting the optional colored brand-icon dataset. */
|
||||
export const FILE_ICON_THEME_COLORED = 'colored'
|
||||
|
||||
/** A registrar exported by the lazy `file-icons` chunk. */
|
||||
type ColoredRegistrar = (service: BetterSidebarService) => () => void
|
||||
|
||||
export interface FileIconThemeLoader {
|
||||
/** Apply a pref value (unknown values behave as the built-in theme). */
|
||||
set(theme: string): void
|
||||
/** Drop any registration and stop reacting to in-flight chunk loads. */
|
||||
dispose(): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind `service` to the `fileIconTheme` pref. Call `set` on activation and on
|
||||
* every prefs change; call `dispose` when the plugin unloads.
|
||||
*/
|
||||
export function createFileIconThemeLoader(service: BetterSidebarService): FileIconThemeLoader {
|
||||
/** Disposer of the currently registered colored dataset, if any. */
|
||||
let unregister: (() => void) | undefined
|
||||
/** Bumped on every set/dispose: a load that lands on a stale generation is dropped. */
|
||||
let generation = 0
|
||||
/** The theme the pref currently asks for (repeated set() calls are no-ops). */
|
||||
let current: string | undefined
|
||||
|
||||
const dropRegistrations = (): void => {
|
||||
const dispose = unregister
|
||||
unregister = undefined
|
||||
if (dispose !== undefined) dispose()
|
||||
}
|
||||
|
||||
const set = (theme: string): void => {
|
||||
if (theme === current) return
|
||||
current = theme
|
||||
const generationAtSet = ++generation
|
||||
if (theme !== FILE_ICON_THEME_COLORED) {
|
||||
dropRegistrations()
|
||||
return
|
||||
}
|
||||
loadChunk('file-icons').then((chunk) => {
|
||||
if (generationAtSet !== generation) return
|
||||
const register = chunk.registerColoredFileIcons as ColoredRegistrar | undefined
|
||||
if (typeof register !== 'function') {
|
||||
console.error('[dsh-better-sidebar] file-icons chunk exports no registerColoredFileIcons')
|
||||
return
|
||||
}
|
||||
unregister = register(service)
|
||||
}).catch((error: unknown) => {
|
||||
if (generationAtSet !== generation) return
|
||||
console.error('[dsh-better-sidebar] file-icons chunk failed to load:', error)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
set,
|
||||
dispose: () => {
|
||||
generation += 1
|
||||
current = undefined
|
||||
dropRegistrations()
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import { registerBottomToggle } from './sidebar/bottom-toggle.tsx'
|
||||
import { createNativeSurface } from './native/surface.ts'
|
||||
import { registerLinkInterception } from './link-intercept.ts'
|
||||
import { registerImeGuard } from './ime-guard.ts'
|
||||
import { createFileIconThemeLoader } from './file-icon-theme.ts'
|
||||
import { registerSettingsNavIcon } from './settings-nav-icon.ts'
|
||||
import { loadBootDecision } from './prefs.ts'
|
||||
import { SideCardSection } from './SideCardSection.tsx'
|
||||
@@ -188,6 +189,21 @@ export function apply(ctx: Context): void {
|
||||
() => registerBuiltins(ctx, service, { terminalTitle: () => terminalTitle }),
|
||||
'dsh-better-sidebar: register built-in tabs and viewers',
|
||||
)
|
||||
// Optional colored file-icon theme: the `fileIconTheme` pref decides whether
|
||||
// the lazy `file-icons` chunk is fetched and its dataset registered through
|
||||
// `registerFileIcon`. The loader reacts to prefs writes and is disposed with
|
||||
// the fiber (HMR-safe); 'builtin' (the default) keeps the startup path free
|
||||
// of icon data.
|
||||
ctx.effect(() => {
|
||||
const loader = createFileIconThemeLoader(service)
|
||||
const sync = (): void => { loader.set(sidebarStore.getPrefs().fileIconTheme) }
|
||||
sync()
|
||||
const unsubscribe = service.subscribeState(sync)
|
||||
return () => {
|
||||
unsubscribe()
|
||||
loader.dispose()
|
||||
}
|
||||
}, 'dsh-better-sidebar: colored file-icon theme')
|
||||
// A failure anywhere in the client lifecycle must never take the app down
|
||||
// silently: log with the plugin prefix and pin a visible diagnostic strip
|
||||
// to the page so a blank panel is never the only symptom. This strip is
|
||||
|
||||
@@ -260,6 +260,12 @@ export const ar: Record<string, string> = {
|
||||
settingsToolsDesc: 'عند التفعيل، يمكن للنموذج إنشاء وتشغيل طرفيات الشريط الجانبي عبر أدوات terminal_* الثمانية (معطّل افتراضياً)',
|
||||
settingsFenceTitle: 'فحص مسارات مساحة العمل',
|
||||
settingsFenceDesc: 'عند التفعيل، تصل وظائف الملفات في الشريط الجانبي إلى مسارات داخل مساحة عمل الجلسة فقط (الافتراضي)؛ عند الإيقاف تصبح ملفات المضيف كلها متاحة — وتحصل سكربتات الصفحة على النفاذ نفسه طوال ذلك',
|
||||
settingsFileIconThemeTitle: 'أيقونات الملفات',
|
||||
settingsFileIconThemeDesc: 'نمط الأيقونات لشجرة الملفات وتبويبات الملفات (تُحمَّل السمة الملوّنة عند الطلب، لا عند بدء التشغيل)',
|
||||
settingsFileIconThemeBuiltin: 'مدمج (أحادي اللون)',
|
||||
settingsFileIconThemeBuiltinDesc: 'أيقونات أحادية اللون تتبع رموز السمة، تغطي أنواع التعليمات البرمجية والمستندات والوسائط الشائعة',
|
||||
settingsFileIconThemeColored: 'أيقونات العلامات الملوّنة',
|
||||
settingsFileIconThemeColoredDesc: 'ألوان العلامات حسب النوع (مثل .tsx → React و package.json → npm)؛ تُحمَّل حزمة الأيقونات عند أول تفعيل',
|
||||
fenceErrorReason: 'هذا المسار خارج مساحة عمل الجلسة وقد حجبه الفحص',
|
||||
fenceDisableAction: 'إيقاف فحص مساحة العمل',
|
||||
settingsBottomTerminalTitle: 'فتح طرفية تلقائياً عند أول توسيع للوحة السفلية',
|
||||
|
||||
@@ -245,6 +245,12 @@ export const de: Record<string, string> = {
|
||||
settingsToolsDesc: 'Wenn aktiviert, kann das Modell über die 8 terminal_*-Werkzeuge Terminale in der Seitenleiste erstellen und steuern (standardmäßig deaktiviert)',
|
||||
settingsFenceTitle: 'Workspace-Pfadkontrolle',
|
||||
settingsFenceDesc: 'Wenn aktiv, erreichen die Dateifunktionen der Seitenleiste nur Pfade innerhalb des Session-Workspaces (Standard); ausgeschaltet sind Dateien überall auf dem Host erreichbar — Skripte der Seite erhalten solange dieselbe Reichweite',
|
||||
settingsFileIconThemeTitle: 'Dateisymbole',
|
||||
settingsFileIconThemeDesc: 'Symbolstil für Dateibaum und Datei-Tabs (das Farbthema lädt bei Bedarf, nie beim Start)',
|
||||
settingsFileIconThemeBuiltin: 'Integriert (einfarbig)',
|
||||
settingsFileIconThemeBuiltinDesc: 'Token-gesteuerte einfarbige Symbole für gängige Code-, Dokument- und Medientypen',
|
||||
settingsFileIconThemeColored: 'Farbige Markensymbole',
|
||||
settingsFileIconThemeColoredDesc: 'Markenfarben je Typ (z. B. .tsx → React, package.json → npm); das Icon-Paket lädt beim ersten Aktivieren',
|
||||
fenceErrorReason: 'Dieser Pfad liegt außerhalb des Session-Workspaces und wurde von der Workspace-Kontrolle blockiert',
|
||||
fenceDisableAction: 'Workspace-Kontrolle ausschalten',
|
||||
settingsBottomTerminalTitle: 'Beim ersten Ausklappen des unteren Panels automatisch ein Terminal öffnen',
|
||||
|
||||
@@ -252,6 +252,12 @@ export const fr: Record<string, string> = {
|
||||
settingsToolsDesc: 'Une fois activé, le modèle peut créer et piloter des terminaux de la barre latérale via les 8 outils terminal_* (désactivé par défaut)',
|
||||
settingsFenceTitle: 'Contrôle des chemins de l\'espace de travail',
|
||||
settingsFenceDesc: 'Activé, les fonctions fichiers de la barre latérale n\'atteignent que les chemins dans l\'espace de travail de la session (par défaut) ; désactivé, les fichiers de tout l\'hôte deviennent accessibles — les scripts de la page obtiennent la même portée pendant ce temps',
|
||||
settingsFileIconThemeTitle: 'Icônes de fichiers',
|
||||
settingsFileIconThemeDesc: 'Style des icônes de l\'arborescence et des onglets de fichiers (le thème coloré se charge à la demande, jamais au démarrage)',
|
||||
settingsFileIconThemeBuiltin: 'Intégré (monochrome)',
|
||||
settingsFileIconThemeBuiltinDesc: 'Icônes monochromes pilotées par les jetons de thème, pour les types code / document / média courants',
|
||||
settingsFileIconThemeColored: 'Icônes de marque colorées',
|
||||
settingsFileIconThemeColoredDesc: 'Couleurs de marque par type (ex. .tsx → React, package.json → npm) ; le pack d\'icônes se charge à la première activation',
|
||||
fenceErrorReason: 'Ce chemin est hors de l\'espace de travail de la session et a été bloqué par le contrôle',
|
||||
fenceDisableAction: 'Désactiver le contrôle de l\'espace de travail',
|
||||
settingsBottomTerminalTitle: 'Ouvrir automatiquement un terminal au premier déploiement du panneau inférieur',
|
||||
|
||||
@@ -259,6 +259,12 @@ export const hi: Record<string, string> = {
|
||||
settingsToolsDesc: 'सक्षम होने पर, मॉडल 8 terminal_* टूल्स से साइडबार टर्मिनल बना और चला सकता है (डिफ़ॉल्ट रूप से बंद)',
|
||||
settingsFenceTitle: 'वर्कस्पेस पाथ जाँच',
|
||||
settingsFenceDesc: 'चालू होने पर साइडबार की फ़ाइल सुविधाएँ केवल सेशन वर्कस्पेस के भीतर के पाथ तक पहुँचती हैं (डिफ़ॉल्ट); बंद करने पर होस्ट की कोई भी फ़ाइल खोली जा सकती है — उस दौरान पेज के स्क्रिप्ट को भी वही पहुँच मिल जाती है',
|
||||
settingsFileIconThemeTitle: 'फ़ाइल आइकन',
|
||||
settingsFileIconThemeDesc: 'फ़ाइल ट्री और फ़ाइल टैब के आइकन की शैली (रंगीन थीम माँग पर लोड होती है, शुरू होते समय नहीं)',
|
||||
settingsFileIconThemeBuiltin: 'अंतर्निहित (एकरंगी)',
|
||||
settingsFileIconThemeBuiltinDesc: 'स्किन टोकन के अनुसार एकरंगी आइकन, सामान्य कोड / दस्तावेज़ / मीडिया प्रकारों के लिए',
|
||||
settingsFileIconThemeColored: 'रंगीन ब्रांड आइकन',
|
||||
settingsFileIconThemeColoredDesc: 'प्रकार के अनुसार ब्रांड रंग (जैसे .tsx → React, package.json → npm); पहली बार चालू करने पर आइकन पैक लोड होता है',
|
||||
fenceErrorReason: 'यह पाथ सेशन वर्कस्पेस के बाहर है और जाँच द्वारा अवरुद्ध किया गया',
|
||||
fenceDisableAction: 'वर्कस्पेस जाँच बंद करें',
|
||||
settingsBottomTerminalTitle: 'निचला पैनल पहली बार खुलने पर स्वतः टर्मिनल खोलें',
|
||||
|
||||
@@ -257,6 +257,12 @@ export const id: Record<string, string> = {
|
||||
settingsToolsDesc: 'Saat diaktifkan, model dapat membuat dan mengendalikan terminal sidebar melalui 8 alat terminal_* (nonaktif secara default)',
|
||||
settingsFenceTitle: 'Pemeriksaan path workspace',
|
||||
settingsFenceDesc: 'Saat aktif, fitur berkas sidebar hanya menjangkau path di dalam workspace sesi (default); saat nonaktif, berkas di mana pun di host dapat diakses — skrip halaman memperoleh jangkauan yang sama selama itu',
|
||||
settingsFileIconThemeTitle: 'Ikon berkas',
|
||||
settingsFileIconThemeDesc: 'Gaya ikon untuk pohon berkas dan tab berkas (tema berwarna dimuat saat diperlukan, tidak saat mulai)',
|
||||
settingsFileIconThemeBuiltin: 'Bawaan (monokrom)',
|
||||
settingsFileIconThemeBuiltinDesc: 'Ikon monokrom mengikuti token tema, mencakup tipe kode / dokumen / media umum',
|
||||
settingsFileIconThemeColored: 'Ikon merek berwarna',
|
||||
settingsFileIconThemeColoredDesc: 'Warna merek per tipe (mis. .tsx → React, package.json → npm); paket ikon dimuat saat pertama diaktifkan',
|
||||
fenceErrorReason: 'Path ini berada di luar workspace sesi dan diblokir oleh pemeriksaan',
|
||||
fenceDisableAction: 'Matikan pemeriksaan workspace',
|
||||
settingsBottomTerminalTitle: 'Buka terminal otomatis saat panel bawah pertama kali dibentangkan',
|
||||
|
||||
@@ -250,6 +250,12 @@ export const it: Record<string, string> = {
|
||||
settingsToolsDesc: 'Se attivato, il modello può creare e gestire terminali della barra laterale attraverso gli 8 strumenti terminal_* (disattivato per impostazione predefinita)',
|
||||
settingsFenceTitle: 'Controllo dei percorsi del workspace',
|
||||
settingsFenceDesc: 'Attivo, le funzioni file della barra laterale raggiungono solo i percorsi nel workspace di sessione (predefinito); disattivo, ogni file dell\'host è raggiungibile — gli script della pagina ottengono la stessa portata nel frattempo',
|
||||
settingsFileIconThemeTitle: 'Icone dei file',
|
||||
settingsFileIconThemeDesc: 'Stile delle icone per l\'albero dei file e le schede (il tema colorato si carica su richiesta, mai all\'avvio)',
|
||||
settingsFileIconThemeBuiltin: 'Integrato (monocromatico)',
|
||||
settingsFileIconThemeBuiltinDesc: 'Icone monocromatiche basate sui token del tema, per i tipi di codice / documento / media più comuni',
|
||||
settingsFileIconThemeColored: 'Icone di marca colorate',
|
||||
settingsFileIconThemeColoredDesc: 'Colori di marca per tipo (es. .tsx → React, package.json → npm); il pacchetto si carica alla prima attivazione',
|
||||
fenceErrorReason: 'Questo percorso è fuori dal workspace di sessione ed è stato bloccato dal controllo',
|
||||
fenceDisableAction: 'Disattiva il controllo del workspace',
|
||||
settingsBottomTerminalTitle: 'Apri automaticamente un terminale alla prima espansione del pannello inferiore',
|
||||
|
||||
@@ -259,6 +259,12 @@ export const ja: Record<string, string> = {
|
||||
settingsToolsDesc: 'オンにすると、モデルが terminal_create 等 8 個のツールでサイドバーターミナルを作成・操作可能(デフォルトオフ)',
|
||||
settingsFenceTitle: 'ワークスペースパスガード',
|
||||
settingsFenceDesc: 'オンの場合、サイドバーのファイル機能はセッションワークスペース内のパスのみアクセス可能(デフォルト)。オフにするとホスト上の任意のファイルにアクセスできるが、オフの間はページ内スクリプトも同じ権限を得る',
|
||||
settingsFileIconThemeTitle: 'ファイルアイコン',
|
||||
settingsFileIconThemeDesc: 'ファイルツリーとファイルタブのアイコンスタイル(カラーテーマは必要時に読み込み、起動時には読み込みません)',
|
||||
settingsFileIconThemeBuiltin: '標準(モノクロ)',
|
||||
settingsFileIconThemeBuiltinDesc: 'スキントークンに従うモノクロアイコン。一般的なコード / ドキュメント / メディア形式に対応',
|
||||
settingsFileIconThemeColored: 'カラーブランドアイコン',
|
||||
settingsFileIconThemeColoredDesc: '種類ごとにブランドカラーで表示(例:.tsx → React、package.json → npm)。初回有効時にアイコンパックを読み込みます',
|
||||
fenceErrorReason: 'このパスはセッションワークスペースの外にあり、ワークスペースガードによりブロックされました',
|
||||
fenceDisableAction: 'ワークスペースガードをオフにする',
|
||||
settingsBottomTerminalTitle: '下パネル初回展開時に自動でターミナルを開く',
|
||||
|
||||
@@ -251,6 +251,12 @@ export const ko: Record<string, string> = {
|
||||
settingsToolsDesc: '켜면 모델이 terminal_create 등 8개 도구로 사이드바 터미널을 만들고 조작할 수 있습니다(기본 꺼짐)',
|
||||
settingsFenceTitle: '워크스페이스 경로 검사',
|
||||
settingsFenceDesc: '켜면 사이드바의 파일 기능이 세션 워크스페이스 내 경로만 접근합니다(기본값). 끄면 호스트의 모든 파일에 접근할 수 있으며, 꺼져 있는 동안 페이지 내 스크립트도 동일한 권한을 얻습니다',
|
||||
settingsFileIconThemeTitle: '파일 아이콘',
|
||||
settingsFileIconThemeDesc: '파일 트리와 파일 탭의 아이콘 스타일 (컬러 테마는 필요할 때 로드되며 시작 시에는 로드되지 않습니다)',
|
||||
settingsFileIconThemeBuiltin: '기본(단색)',
|
||||
settingsFileIconThemeBuiltinDesc: '스킨 토큰을 따르는 단색 아이콘으로 주요 코드 / 문서 / 미디어 형식을 지원합니다',
|
||||
settingsFileIconThemeColored: '컬러 브랜드 아이콘',
|
||||
settingsFileIconThemeColoredDesc: '유형별 브랜드 색상 (예: .tsx → React, package.json → npm), 처음 켤 때 아이콘 팩을 로드합니다',
|
||||
fenceErrorReason: '이 경로는 세션 워크스페이스 밖에 있어 검사에 의해 차단되었습니다',
|
||||
fenceDisableAction: '워크스페이스 검사 끄기',
|
||||
settingsBottomTerminalTitle: '하단 패널을 처음 펼칠 때 터미널 자동 열기',
|
||||
|
||||
@@ -257,6 +257,12 @@ export const nl: Record<string, string> = {
|
||||
settingsToolsDesc: 'Indien ingeschakeld kan het model zijbalk-terminals aanmaken en besturen via de 8 terminal_*-tools (standaard uit)',
|
||||
settingsFenceTitle: 'Workspace-padcontrole',
|
||||
settingsFenceDesc: 'Aan: de bestandsfuncties van de zijbalk bereiken alleen paden binnen de sessie-workspace (standaard); uit: elk bestand op de host is bereikbaar — paginascripts krijgen dat bereik zolang het uit staat',
|
||||
settingsFileIconThemeTitle: 'Bestandspictogrammen',
|
||||
settingsFileIconThemeDesc: 'Pictogramstijl voor de bestandsboom en bestandstabbladen (het kleurenthema wordt op verzoek geladen, nooit bij het starten)',
|
||||
settingsFileIconThemeBuiltin: 'Ingebouwd (monochroom)',
|
||||
settingsFileIconThemeBuiltinDesc: 'Monochrome pictogrammen volgens de token-regels, voor gangbare code-, document- en mediatypen',
|
||||
settingsFileIconThemeColored: 'Gekleurde merkpictogrammen',
|
||||
settingsFileIconThemeColoredDesc: 'Merkkleuren per type (bijv. .tsx → React, package.json → npm); het pictogrammenpakket wordt geladen bij de eerste activering',
|
||||
fenceErrorReason: 'Dit pad ligt buiten de sessie-workspace en is geblokkeerd door de controle',
|
||||
fenceDisableAction: 'Workspace-controle uitzetten',
|
||||
settingsBottomTerminalTitle: 'Automatisch een terminal openen bij de eerste uitklap van het onderpaneel',
|
||||
|
||||
@@ -261,6 +261,12 @@ export const pl: Record<string, string> = {
|
||||
settingsToolsDesc: 'Po włączeniu model może tworzyć i sterować terminalami panelu bocznego przez 8 narzędzi terminal_* (domyślnie wyłączone)',
|
||||
settingsFenceTitle: 'Kontrola ścieżek obszaru roboczego',
|
||||
settingsFenceDesc: 'Włączona: funkcje plików paska bocznego sięgają tylko ścieżek w obszarze roboczym sesji (domyślnie); wyłączona: dostępny jest dowolny plik hosta — skrypty strony zyskują ten sam zasięg przez ten czas',
|
||||
settingsFileIconThemeTitle: 'Ikony plików',
|
||||
settingsFileIconThemeDesc: 'Styl ikon drzewa plików i kart plików (kolorowy motyw wczytuje się na żądanie, nigdy przy starcie)',
|
||||
settingsFileIconThemeBuiltin: 'Wbudowane (monochromatyczne)',
|
||||
settingsFileIconThemeBuiltinDesc: 'Monochromatyczne ikony zgodne z tokenami motywu, dla typowych plików kodu / dokumentów / multimediów',
|
||||
settingsFileIconThemeColored: 'Kolorowe ikony marek',
|
||||
settingsFileIconThemeColoredDesc: 'Kolory marek według typu (np. .tsx → React, package.json → npm); paczka ikon wczytuje się przy pierwszym włączeniu',
|
||||
fenceErrorReason: 'Ta ścieżka jest poza obszarem roboczym sesji i została zablokowana przez kontrolę',
|
||||
fenceDisableAction: 'Wyłącz kontrolę obszaru roboczego',
|
||||
settingsBottomTerminalTitle: 'Automatycznie otwieraj terminal przy pierwszym rozwinięciu panelu dolnego',
|
||||
|
||||
@@ -242,6 +242,12 @@ export const pt: Record<string, string> = {
|
||||
settingsToolsDesc: 'Quando ativado, o modelo pode criar e operar terminais da barra lateral por meio das 8 ferramentas terminal_* (desativado por padrão)',
|
||||
settingsFenceTitle: 'Controle de caminhos do workspace',
|
||||
settingsFenceDesc: 'Ativado, os recursos de arquivos da barra lateral só alcançam caminhos dentro do workspace da sessão (padrão); desativado, arquivos de todo o host ficam acessíveis — scripts da página ganham o mesmo alcance enquanto isso',
|
||||
settingsFileIconThemeTitle: 'Ícones de arquivo',
|
||||
settingsFileIconThemeDesc: 'Estilo dos ícones da árvore de arquivos e das abas (o tema colorido é carregado sob demanda, nunca na inicialização)',
|
||||
settingsFileIconThemeBuiltin: 'Integrado (monocromático)',
|
||||
settingsFileIconThemeBuiltinDesc: 'Ícones monocromáticos guiados por tokens, para tipos comuns de código / documento / mídia',
|
||||
settingsFileIconThemeColored: 'Ícones de marca coloridos',
|
||||
settingsFileIconThemeColoredDesc: 'Cores de marca por tipo (ex.: .tsx → React, package.json → npm); o pacote é carregado ao ativar pela primeira vez',
|
||||
fenceErrorReason: 'Este caminho está fora do workspace da sessão e foi bloqueado pelo controle',
|
||||
fenceDisableAction: 'Desativar o controle do workspace',
|
||||
settingsBottomTerminalTitle: 'Abrir um terminal automaticamente na primeira expansão do painel inferior',
|
||||
|
||||
@@ -256,6 +256,12 @@ export const ru: Record<string, string> = {
|
||||
settingsToolsDesc: 'Если включено, модель может через 8 инструментов (terminal_create и др.) создавать терминалы боковой панели и управлять ими (по умолчанию выключено)',
|
||||
settingsFenceTitle: 'Контроль путей рабочей области',
|
||||
settingsFenceDesc: 'Включено — файловые функции боковой панели достигают только путей внутри рабочей области сессии (по умолчанию); выключено — доступны любые файлы хоста, а скрипты страницы получают тот же доступ всё это время',
|
||||
settingsFileIconThemeTitle: 'Значки файлов',
|
||||
settingsFileIconThemeDesc: 'Стиль значков дерева файлов и вкладок (цветная тема загружается по требованию, а не при запуске)',
|
||||
settingsFileIconThemeBuiltin: 'Встроенные (монохромные)',
|
||||
settingsFileIconThemeBuiltinDesc: 'Монохромные значки на основе токенов темы: код, документы и медиа',
|
||||
settingsFileIconThemeColored: 'Цветные значки брендов',
|
||||
settingsFileIconThemeColoredDesc: 'Цвета брендов по типу (например, .tsx → React, package.json → npm); набор значков загружается при первом включении',
|
||||
fenceErrorReason: 'Этот путь вне рабочей области сессии и заблокирован контролем',
|
||||
fenceDisableAction: 'Отключить контроль рабочей области',
|
||||
settingsBottomTerminalTitle: 'Автооткрытие терминала при первом разворачивании нижней панели',
|
||||
|
||||
@@ -242,6 +242,12 @@ export const sv: Record<string, string> = {
|
||||
settingsToolsDesc: 'När aktiverat kan modellen skapa och styra sidopanelsterminaler via de 8 terminal_*-verktygen (av som standard)',
|
||||
settingsFenceTitle: 'Kontroll av workspace-sökvägar',
|
||||
settingsFenceDesc: 'På: sidopanelens filfunktioner når bara sökvägar i sessionens workspace (standard); av: vilken fil som helst på värden kan nås — sidans skript får samma räckvidd så länge',
|
||||
settingsFileIconThemeTitle: 'Filikoner',
|
||||
settingsFileIconThemeDesc: 'Ikonstil för filträdet och filflikarna (färgtemat laddas vid behov, aldrig vid start)',
|
||||
settingsFileIconThemeBuiltin: 'Inbyggd (monokrom)',
|
||||
settingsFileIconThemeBuiltinDesc: 'Monokroma ikoner som följer temats tokens, för vanliga kod-, dokument- och mediatyper',
|
||||
settingsFileIconThemeColored: 'Färgade varumärkesikoner',
|
||||
settingsFileIconThemeColoredDesc: 'Varumärkesfärger per typ (t.ex. .tsx → React, package.json → npm); ikonpaketet laddas första gången du aktiverar det',
|
||||
fenceErrorReason: 'Denna sökväg ligger utanför sessionens workspace och blockerades av kontrollen',
|
||||
fenceDisableAction: 'Stäng av workspace-kontrollen',
|
||||
settingsBottomTerminalTitle: 'Öppna automatiskt en terminal vid bottenpanelens första expandering',
|
||||
|
||||
@@ -259,6 +259,12 @@ export const th: Record<string, string> = {
|
||||
settingsToolsDesc: 'เมื่อเปิดใช้ โมเดลสามารถสร้างและควบคุมเทอร์มินัลแถบด้านข้างผ่านเครื่องมือ terminal_* 8 ตัว (ปิดเป็นค่าเริ่มต้น)',
|
||||
settingsFenceTitle: 'การตรวจสอบเส้นทางเวิร์กสเปซ',
|
||||
settingsFenceDesc: 'เมื่อเปิด ฟีเจอร์ไฟล์ของแถบข้างเข้าถึงได้เฉพาะเส้นทางในเวิร์กสเปซของเซสชัน (ค่าเริ่มต้น) เมื่อปิด จะเข้าถึงไฟล์ใดก็ได้บนโฮสต์ — สคริปต์ในหน้าก็ได้ขอบเขตเดียวกันตลอดช่วงนั้น',
|
||||
settingsFileIconThemeTitle: 'ไอคอนไฟล์',
|
||||
settingsFileIconThemeDesc: 'สไตล์ไอคอนของแผนผังไฟล์และแท็บไฟล์ (ธีมสีจะโหลดเมื่อต้องการ ไม่โหลดตอนเริ่มต้น)',
|
||||
settingsFileIconThemeBuiltin: 'ในตัว (สีเดียว)',
|
||||
settingsFileIconThemeBuiltinDesc: 'ไอคอนสีเดียวตามโทเค็นธีม ครอบคลุมชนิดโค้ด / เอกสาร / สื่อทั่วไป',
|
||||
settingsFileIconThemeColored: 'ไอคอนแบรนด์แบบมีสี',
|
||||
settingsFileIconThemeColoredDesc: 'สีแบรนด์ตามชนิด (เช่น .tsx → React, package.json → npm) ชุดไอคอนจะโหลดเมื่อเปิดใช้ครั้งแรก',
|
||||
fenceErrorReason: 'เส้นทางนี้อยู่นอกเวิร์กสเปซของเซสชันและถูกบล็อกโดยการตรวจสอบ',
|
||||
fenceDisableAction: 'ปิดการตรวจสอบเวิร์กสเปซ',
|
||||
settingsBottomTerminalTitle: 'เปิดเทอร์มินัลอัตโนมัติเมื่อพาเนลล่างขยายครั้งแรก',
|
||||
|
||||
@@ -259,6 +259,12 @@ export const tr: Record<string, string> = {
|
||||
settingsToolsDesc: 'Etkinleştirildiğinde model, 8 terminal_* aracı üzerinden kenar çubuğu terminalleri oluşturup kullanabilir (varsayılan kapalı)',
|
||||
settingsFenceTitle: 'Çalışma alanı yol denetimi',
|
||||
settingsFenceDesc: 'Açıkken kenar çubuğunun dosya işlevleri yalnızca oturum çalışma alanındaki yollara erişir (varsayılan); kapatıldığında ana makinedeki her dosyaya erişilebilir — o süre boyunca sayfadaki betikler de aynı erişimi kazanır',
|
||||
settingsFileIconThemeTitle: 'Dosya simgeleri',
|
||||
settingsFileIconThemeDesc: 'Dosya ağacı ve dosya sekmeleri için simge stili (renkli tema isteğe bağlı yüklenir, başlangıçta değil)',
|
||||
settingsFileIconThemeBuiltin: 'Yerleşik (tek renk)',
|
||||
settingsFileIconThemeBuiltinDesc: 'Tema belirteçlerine uyan tek renk simgeler; yaygın kod / belge / medya türlerini kapsar',
|
||||
settingsFileIconThemeColored: 'Renkli marka simgeleri',
|
||||
settingsFileIconThemeColoredDesc: 'Türe göre marka renkleri (örn. .tsx → React, package.json → npm); simge paketi ilk açılışta yüklenir',
|
||||
fenceErrorReason: 'Bu yol oturum çalışma alanının dışında ve denetim tarafından engellendi',
|
||||
fenceDisableAction: 'Çalışma alanı denetimini kapat',
|
||||
settingsBottomTerminalTitle: 'Alt panelin ilk genişletilmesinde otomatik terminal aç',
|
||||
|
||||
@@ -259,6 +259,12 @@ export const vi: Record<string, string> = {
|
||||
settingsToolsDesc: 'Khi bật, mô hình có thể tạo và điều khiển terminal thanh bên qua 8 công cụ terminal_* (mặc định tắt)',
|
||||
settingsFenceTitle: 'Kiểm tra đường dẫn workspace',
|
||||
settingsFenceDesc: 'Khi bật, các tính năng tệp của thanh bên chỉ truy cập đường dẫn trong workspace của phiên (mặc định); khi tắt, mọi tệp trên máy chủ đều có thể truy cập — script trong trang cũng có quyền tương ứng trong thời gian đó',
|
||||
settingsFileIconThemeTitle: 'Biểu tượng tệp',
|
||||
settingsFileIconThemeDesc: 'Kiểu biểu tượng cho cây tệp và tab tệp (chủ đề màu được tải khi cần, không tải lúc khởi động)',
|
||||
settingsFileIconThemeBuiltin: 'Tích hợp (đơn sắc)',
|
||||
settingsFileIconThemeBuiltinDesc: 'Biểu tượng đơn sắc theo token giao diện, bao phủ các loại mã / tài liệu / media phổ biến',
|
||||
settingsFileIconThemeColored: 'Biểu tượng thương hiệu nhiều màu',
|
||||
settingsFileIconThemeColoredDesc: 'Màu thương hiệu theo loại (ví dụ .tsx → React, package.json → npm); gói biểu tượng tải trong lần bật đầu tiên',
|
||||
fenceErrorReason: 'Đường dẫn này nằm ngoài workspace của phiên và đã bị chặn bởi kiểm tra',
|
||||
fenceDisableAction: 'Tắt kiểm tra workspace',
|
||||
settingsBottomTerminalTitle: 'Tự động mở terminal khi panel dưới mở rộng lần đầu',
|
||||
|
||||
@@ -274,6 +274,12 @@ export const zhHK: Record<string, string> = {
|
||||
settingsToolsDesc: '開啟後,模型可透過 terminal_create 等 8 個工具建立並操作側邊欄終端(預設關閉)',
|
||||
settingsFenceTitle: '工作區路徑檢測',
|
||||
settingsFenceDesc: '開啟後,側欄的檔案功能僅能存取工作階段工作區內的路徑(預設);關閉後可存取主機上任意檔案——關閉期間頁面內指令碼也將獲得同等存取能力',
|
||||
settingsFileIconThemeTitle: '檔案圖示',
|
||||
settingsFileIconThemeDesc: '檔案樹與檔案分頁的圖示風格(彩色主題按需載入,不影響啟動)',
|
||||
settingsFileIconThemeBuiltin: '內建(單色)',
|
||||
settingsFileIconThemeBuiltinDesc: '跟隨皮膚權杖的單色圖示,覆蓋常見程式碼 / 文件 / 媒體類型',
|
||||
settingsFileIconThemeColored: '彩色品牌圖示',
|
||||
settingsFileIconThemeColoredDesc: '按類型顯示品牌配色圖示(如 .tsx → React、package.json → npm);首次開啟時載入圖示包',
|
||||
fenceErrorReason: '此路徑在工作階段工作區之外,已被工作區檢測攔截',
|
||||
fenceDisableAction: '關閉工作區檢測',
|
||||
settingsBottomTerminalTitle: '底部面板首次展開自動開終端',
|
||||
|
||||
@@ -274,6 +274,12 @@ export const zhMO: Record<string, string> = {
|
||||
settingsToolsDesc: '開啟後,模型可透過 terminal_create 等 8 個工具建立並操作側邊欄終端(預設關閉)',
|
||||
settingsFenceTitle: '工作區路徑檢測',
|
||||
settingsFenceDesc: '開啟後,側欄的檔案功能僅能存取工作階段工作區內的路徑(預設);關閉後可存取主機上任意檔案——關閉期間頁面內指令碼也將獲得同等存取能力',
|
||||
settingsFileIconThemeTitle: '檔案圖示',
|
||||
settingsFileIconThemeDesc: '檔案樹與檔案分頁的圖示風格(彩色主題按需載入,不影響啟動)',
|
||||
settingsFileIconThemeBuiltin: '內建(單色)',
|
||||
settingsFileIconThemeBuiltinDesc: '跟隨皮膚權杖的單色圖示,覆蓋常見程式碼 / 文件 / 媒體類型',
|
||||
settingsFileIconThemeColored: '彩色品牌圖示',
|
||||
settingsFileIconThemeColoredDesc: '按類型顯示品牌配色圖示(如 .tsx → React、package.json → npm);首次開啟時載入圖示包',
|
||||
fenceErrorReason: '此路徑在工作階段工作區之外,已被工作區檢測攔截',
|
||||
fenceDisableAction: '關閉工作區檢測',
|
||||
settingsBottomTerminalTitle: '底部面板首次展開自動開終端',
|
||||
|
||||
@@ -274,6 +274,12 @@ export const zhTW: Record<string, string> = {
|
||||
settingsToolsDesc: '開啟後,模型可透過 terminal_create 等 8 個工具建立並操作側邊欄終端(預設關閉)',
|
||||
settingsFenceTitle: '工作區路徑檢測',
|
||||
settingsFenceDesc: '開啟後,側欄的檔案功能僅能存取工作階段工作區內的路徑(預設);關閉後可存取主機上任意檔案——關閉期間頁面內指令碼也將獲得同等存取能力',
|
||||
settingsFileIconThemeTitle: '檔案圖示',
|
||||
settingsFileIconThemeDesc: '檔案樹與檔案分頁的圖示風格(彩色主題按需載入,不影響啟動)',
|
||||
settingsFileIconThemeBuiltin: '內建(單色)',
|
||||
settingsFileIconThemeBuiltinDesc: '跟隨面板權杖的單色圖示,涵蓋常見程式碼 / 文件 / 媒體類型',
|
||||
settingsFileIconThemeColored: '彩色品牌圖示',
|
||||
settingsFileIconThemeColoredDesc: '依類型顯示品牌配色圖示(如 .tsx → React、package.json → npm);首次開啟時載入圖示套件',
|
||||
fenceErrorReason: '此路徑在工作階段工作區之外,已被工作區檢測攔截',
|
||||
fenceDisableAction: '關閉工作區檢測',
|
||||
settingsBottomTerminalTitle: '底部面板首次展開自動開終端',
|
||||
|
||||
@@ -271,6 +271,12 @@ export const zh = {
|
||||
settingsToolsDesc: '开启后,模型可通过 terminal_create 等 8 个工具创建并操作侧边栏终端(默认关闭)',
|
||||
settingsFenceTitle: '工作区路径检测',
|
||||
settingsFenceDesc: '开启后,侧栏的文件功能仅能访问会话工作区内的路径(默认);关闭后可访问主机上任意文件——关闭期间页面内脚本也将获得同等访问能力',
|
||||
settingsFileIconThemeTitle: '文件图标',
|
||||
settingsFileIconThemeDesc: '文件树与文件 tab 的图标风格(彩色主题按需加载,不影响启动)',
|
||||
settingsFileIconThemeBuiltin: '内置(单色)',
|
||||
settingsFileIconThemeBuiltinDesc: '跟随皮肤令牌的单色图标,覆盖常见代码 / 文档 / 媒体类型',
|
||||
settingsFileIconThemeColored: '彩色品牌图标',
|
||||
settingsFileIconThemeColoredDesc: '按类型显示品牌配色图标(如 .tsx → React、package.json → npm);首次开启时加载图标包',
|
||||
fenceErrorReason: '此路径在会话工作区之外,已被工作区检测拦截',
|
||||
fenceDisableAction: '关闭工作区检测',
|
||||
settingsBottomTerminalTitle: '底部面板首次展开自动开终端',
|
||||
@@ -713,6 +719,12 @@ export const en: Record<keyof typeof zh, string> = {
|
||||
settingsToolsDesc: 'When enabled, the model can create and drive sidebar terminals through the 8 terminal_* tools (off by default)',
|
||||
settingsFenceTitle: 'Workspace path fence',
|
||||
settingsFenceDesc: 'On, the sidebar\'s file features only reach paths inside the session workspace (default); off, any file on the host is reachable — page scripts gain the same reach while it is off',
|
||||
settingsFileIconThemeTitle: 'File icons',
|
||||
settingsFileIconThemeDesc: 'Icon style for the file tree and file tabs (the colored theme loads on demand, never at startup)',
|
||||
settingsFileIconThemeBuiltin: 'Built-in (monochrome)',
|
||||
settingsFileIconThemeBuiltinDesc: 'Token-driven monochrome glyphs covering common code / document / media types',
|
||||
settingsFileIconThemeColored: 'Colored brand icons',
|
||||
settingsFileIconThemeColoredDesc: 'Per-type brand colors (e.g. .tsx → React, package.json → npm); the icon pack loads the first time you enable it',
|
||||
fenceErrorReason: 'This path is outside the session workspace and was blocked by the workspace fence',
|
||||
fenceDisableAction: 'Turn off the workspace fence',
|
||||
settingsBottomTerminalTitle: 'Auto-open a terminal on the bottom panel\'s first expansion',
|
||||
|
||||
@@ -74,6 +74,9 @@ export function parsePrefs(value: unknown): SidebarPrefs {
|
||||
workspaceFence: typeof record.workspaceFence === 'boolean'
|
||||
? record.workspaceFence
|
||||
: SIDEBAR_PREFS_DEFAULTS.workspaceFence,
|
||||
fileIconTheme: typeof record.fileIconTheme === 'string'
|
||||
? record.fileIconTheme
|
||||
: SIDEBAR_PREFS_DEFAULTS.fileIconTheme,
|
||||
// The title-bar scheme (auto | web | preset | custom). The schema
|
||||
// declares the field WITHOUT a default, so documents written by older
|
||||
// plugin versions resolve without it — migrate from the legacy fields:
|
||||
|
||||
+87
-43
@@ -26,7 +26,7 @@ import {
|
||||
leafWithTab, openTabInBottomPane, patchTab, tabOpenIn,
|
||||
type SidebarSnapshot, type SidebarState, type SidebarStore, type SidebarTab, type TabType,
|
||||
} from './state.ts'
|
||||
import { extOf } from './paths.ts'
|
||||
import { baseName, extOf } from './paths.ts'
|
||||
import { builtinFileIconOf, builtinFolderIcon, fallbackFileIcon } from './file-icons.tsx'
|
||||
import type { SessionScope } from './api.ts'
|
||||
import type { SidebarPrefs } from '../prefs-shared.ts'
|
||||
@@ -349,15 +349,36 @@ export interface FileIconDescriptor {
|
||||
* Lowercase extensions without leading dot (`['csv','tsv']`). `[]` = the
|
||||
* global default (catch-all): it only claims files the built-in glyph map
|
||||
* does not cover — registered specifics and built-in glyphs always outrank
|
||||
* it. Two values are RESERVED for directory rows (never matched against
|
||||
* real file extensions): `'folder'` (a closed directory) and
|
||||
* `'folder-open'` (an expanded directory) — see `FOLDER_EXT`.
|
||||
* it. OMITTED = no extension rule at all (a `names`-only registration is
|
||||
* NOT a catch-all). Two values are RESERVED for directory rows (never
|
||||
* matched against real file extensions): `'folder'` (a closed directory)
|
||||
* and `'folder-open'` (an expanded directory) — see `FOLDER_EXT`.
|
||||
*/
|
||||
exts: readonly string[]
|
||||
exts?: readonly string[]
|
||||
/**
|
||||
* Exact FILE names (basename, case-insensitive — `['package.json',
|
||||
* 'Dockerfile']`), the `fileNames` half of an icon theme. Name matches
|
||||
* outrank extension matches, so a theme can color `package.json` apart
|
||||
* from every other `.json`. Omitted/`[]` = no name rule.
|
||||
*/
|
||||
names?: readonly string[]
|
||||
/**
|
||||
* Exact DIRECTORY names (basename, case-insensitive — `['node_modules',
|
||||
* 'src']`), the `folderNames` half of an icon theme. A name match outranks
|
||||
* the reserved `'folder'`/`'folder-open'` exts, and a descriptor with
|
||||
* `folderNames` only claims the directories it names (never every folder —
|
||||
* that is what the reserved exts are for). Omitted/`[]` = no name rule.
|
||||
*/
|
||||
folderNames?: readonly string[]
|
||||
/** Higher wins; default 0. Registered icons always outrank the built-in map. */
|
||||
priority?: number
|
||||
/** Size-aware icon factory (the tree and file tabs render at 14 today). */
|
||||
icon: (path: string, size: number) => ReactNode
|
||||
/**
|
||||
* Size-aware icon factory (the tree and file tabs render at 14 today).
|
||||
* `open` is the directory's expanded state for a DIRECTORY row and
|
||||
* `undefined` for a file row — a folder icon uses it to pick between the
|
||||
* closed and opened glyph.
|
||||
*/
|
||||
icon: (path: string, size: number, open?: boolean) => ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -450,23 +471,26 @@ export interface BetterSidebarService {
|
||||
getFileIcons(): readonly FileIconDescriptor[]
|
||||
/**
|
||||
* Find a SPECIFIC registered file icon for a path (priority desc, then
|
||||
* registration order). Catch-alls (`exts: []`) and folder registrations
|
||||
* (`'folder'`/`'folder-open'`) are not consulted — this answers "did a
|
||||
* registration claim this exact extension". Consumers should prefer
|
||||
* registration order): a `names` match first, then an `exts` match.
|
||||
* Catch-alls (`exts: []`) and folder registrations (`'folder'`/
|
||||
* `'folder-open'`) are not consulted — this answers "did a registration
|
||||
* claim this exact name or extension". Consumers should prefer
|
||||
* `fileIcon`/`folderIcon`, which run the whole fallback chain.
|
||||
*/
|
||||
matchFileIcon(path: string): FileIconDescriptor | undefined
|
||||
/**
|
||||
* Find the registered icon for DIRECTORY rows (priority desc, then
|
||||
* registration order): `open` picks between the `'folder'` and
|
||||
* `'folder-open'` reserved exts. Undefined = fall back to the built-in
|
||||
* VSCodicons folder glyphs.
|
||||
* registration order): a `folderNames` match on `name` first (pass the
|
||||
* directory's basename), then the `'folder'`/`'folder-open'` reserved
|
||||
* exts by `open`. Undefined = fall back to the built-in VSCodicons folder
|
||||
* glyphs.
|
||||
*/
|
||||
matchFolderIcon(open: boolean): FileIconDescriptor | undefined
|
||||
matchFolderIcon(open: boolean, name?: string): FileIconDescriptor | undefined
|
||||
/**
|
||||
* The authoritative FILE icon for a path (feature `fileIcons`), running
|
||||
* the whole chain with per-factory crash isolation:
|
||||
* 1. a specific registered extension (priority desc, registration order),
|
||||
* 1. a specific registered name or extension (priority desc, registration
|
||||
* order),
|
||||
* 2. the built-in monochrome glyph map (md/media/pdf/json/code/...),
|
||||
* 3. the best registered global default (`exts: []`, priority desc),
|
||||
* 4. the generic `VscFile`.
|
||||
@@ -476,9 +500,11 @@ export interface BetterSidebarService {
|
||||
fileIcon(path: string, size: number): ReactNode
|
||||
/**
|
||||
* The authoritative DIRECTORY icon for a tree row: the registered
|
||||
* `'folder'`/`'folder-open'` icon (priority desc), else the built-in
|
||||
* `VscFolder`/`VscFolderOpened`. `path` is the directory's own path (a
|
||||
* theme may vary icons per directory). Same crash isolation as `fileIcon`.
|
||||
* `folderNames`/`'folder'`/`'folder-open'` icon (priority desc), else the
|
||||
* built-in `VscFolder`/`VscFolderOpened`. `path` is the directory's own
|
||||
* path (a theme may vary icons per directory); `open` reaches the factory
|
||||
* so one descriptor can render both states. Same crash isolation as
|
||||
* `fileIcon`.
|
||||
*/
|
||||
folderIcon(path: string, open: boolean, size: number): ReactNode
|
||||
/** Find a tab descriptor by id (undefined if not registered). */
|
||||
@@ -616,7 +642,9 @@ export const SIDEBAR_SERVICE_VERSION = '0.19.1'
|
||||
* - 'urlTarget' (v0.13.0): TabDescriptor.urlTarget (external-link claims)
|
||||
* - 'settingSelect': SidebarSettingToggle type 'select' (options/multi)
|
||||
* - 'fileIcons' (v0.19.0): registerFileIcon/getFileIcons/matchFileIcon —
|
||||
* external per-extension file-tree icons overriding the built-in glyphs.
|
||||
* external file-tree icons overriding the built-in glyphs, matched by
|
||||
* extension (`exts`), exact file name (`names`), or directory name
|
||||
* (`folderNames`).
|
||||
*
|
||||
* v0.19.0 REMOVED 'floatWindows': the free-window feature is gone (DSH 0.1.5
|
||||
* owns the right column, so the plugin keeps only its bottom workbench).
|
||||
@@ -721,45 +749,60 @@ export function createBetterSidebarService(store: SidebarStore): BetterSidebarSe
|
||||
|
||||
// Specific registrations only: catch-alls (`exts: []`) and folder
|
||||
// registrations (`'folder'`/`'folder-open'`) are skipped, and the reserved
|
||||
// folder values never match a real file's extension. The built-in glyph
|
||||
// map is not consulted here — an undefined result IS the "fall through"
|
||||
// signal the `fileIcon` resolver acts on.
|
||||
// folder values never match a real file's extension. Name rules (`names`)
|
||||
// outrank extension rules. The built-in glyph map is not consulted here —
|
||||
// an undefined result IS the "fall through" signal the `fileIcon` resolver
|
||||
// acts on.
|
||||
const matchFileIcon = (path: string): FileIconDescriptor | undefined => {
|
||||
const ext = extOf(path)
|
||||
// Reserved folder values never claim a real file: `x.folder` falls
|
||||
// through to the built-in/catch-all chain like any unknown extension.
|
||||
if (ext === FOLDER_EXT || ext === FOLDER_OPEN_EXT) return undefined
|
||||
for (const d of rankedFileIcons()) {
|
||||
if (d.exts.includes(ext)) return d
|
||||
const reserved = ext === FOLDER_EXT || ext === FOLDER_OPEN_EXT
|
||||
const name = baseName(path).toLowerCase()
|
||||
const ranked = rankedFileIcons()
|
||||
for (const d of ranked) {
|
||||
if (d.names?.some(entry => entry.toLowerCase() === name) === true) return d
|
||||
}
|
||||
if (reserved) return undefined
|
||||
for (const d of ranked) {
|
||||
if (d.exts?.includes(ext) === true) return d
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Directory rows have no extension: the reserved `'folder'`/`'folder-open'`
|
||||
// exts are the only claim surface (a catch-all never claims a directory).
|
||||
const matchFolderIcon = (open: boolean): FileIconDescriptor | undefined => {
|
||||
// Directory rows: a `folderNames` match on the directory's own basename
|
||||
// first, then the reserved `'folder'`/`'folder-open'` exts (a catch-all
|
||||
// never claims a directory).
|
||||
const matchFolderIcon = (open: boolean, name?: string): FileIconDescriptor | undefined => {
|
||||
const ranked = rankedFileIcons()
|
||||
if (name !== undefined) {
|
||||
const wanted = name.toLowerCase()
|
||||
for (const d of ranked) {
|
||||
if (d.folderNames?.some(entry => entry.toLowerCase() === wanted) === true) return d
|
||||
}
|
||||
}
|
||||
const want = open ? FOLDER_OPEN_EXT : FOLDER_EXT
|
||||
for (const d of rankedFileIcons()) {
|
||||
if (d.exts.includes(want)) return d
|
||||
for (const d of ranked) {
|
||||
if (d.exts?.includes(want) === true) return d
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/** Run one registered factory; a throw is logged and returns undefined. */
|
||||
const safeIcon = (d: FileIconDescriptor, path: string, size: number): ReactNode => {
|
||||
const safeIcon = (d: FileIconDescriptor, path: string, size: number, open?: boolean): ReactNode => {
|
||||
try {
|
||||
return d.icon(path, size)
|
||||
return d.icon(path, size, open)
|
||||
} catch (error) {
|
||||
console.error(`[dsh-better-sidebar] file icon factory "${d.id}" error:`, error)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
// The authoritative file-icon chain (see the interface doc): specific
|
||||
// registration → built-in glyph → best catch-all → stock VscFile. The
|
||||
// catch-all ranks by priority desc then registration order (first wins);
|
||||
// an unclaimed extension reaching it is exactly "nothing specified falls
|
||||
// back to the (registered or stock) default".
|
||||
// The authoritative file-icon chain (see the interface doc): specific name
|
||||
// or extension registration → built-in glyph → best catch-all → stock
|
||||
// VscFile. The catch-all ranks by priority desc then registration order
|
||||
// (first wins); an unclaimed extension reaching it is exactly "nothing
|
||||
// specified falls back to the (registered or stock) default".
|
||||
const fileIcon = (path: string, size: number): ReactNode => {
|
||||
const specific = matchFileIcon(path)
|
||||
if (specific !== undefined) {
|
||||
@@ -769,7 +812,7 @@ export function createBetterSidebarService(store: SidebarStore): BetterSidebarSe
|
||||
const builtin = builtinFileIconOf(path)
|
||||
if (builtin !== undefined) return builtin(size)
|
||||
for (const d of rankedFileIcons()) {
|
||||
if (d.exts.length === 0) {
|
||||
if (d.exts !== undefined && d.exts.length === 0) {
|
||||
const icon = safeIcon(d, path, size)
|
||||
if (icon !== undefined) return icon
|
||||
}
|
||||
@@ -777,13 +820,14 @@ export function createBetterSidebarService(store: SidebarStore): BetterSidebarSe
|
||||
return fallbackFileIcon(size)
|
||||
}
|
||||
|
||||
// Directory rows: registered folder/folder-open icon, else the built-in
|
||||
// VSCodicons glyphs. The row's path feeds the factory (a theme may vary
|
||||
// icons per directory).
|
||||
// Directory rows: registered folderNames/folder/folder-open icon, else the
|
||||
// built-in VSCodicons glyphs. The row's path feeds the factory (a theme may
|
||||
// vary icons per directory) and `open` lets one descriptor render both
|
||||
// states.
|
||||
const folderIcon = (path: string, open: boolean, size: number): ReactNode => {
|
||||
const registered = matchFolderIcon(open)
|
||||
const registered = matchFolderIcon(open, baseName(path))
|
||||
if (registered !== undefined) {
|
||||
const icon = safeIcon(registered, path, size)
|
||||
const icon = safeIcon(registered, path, size, open)
|
||||
if (icon !== undefined) return icon
|
||||
}
|
||||
return builtinFolderIcon(open, size)
|
||||
|
||||
Reference in New Issue
Block a user