File Style Sheets — a CSS-like language for describing how files should be rendered in a tree or panel.
- CSS-like syntax powered by css-tree for robust parsing
- Type selectors:
file,folder - Attribute selectors:
[ext="ts"],[name="Dockerfile"],[lang="typescript"],[vcs-status="modified"] - Attribute operators:
=,^=,$=,~=,!= - Pseudo-classes:
:expanded,:selected,:hovered,:active,:drag-over,:focused :rootpseudo-class:folder:rootmatches the top-level folder:is()grouping:file:is([ext="ts"], [ext="tsx"])- Descendant combinator:
folder[name=".github"] folder[name="workflows"] - Compound extensions:
file[ext="test.ts"]overridesfile[ext="ts"]— specificity scales by segment count - Language selector:
file[lang="typescript"]— match by VS Code language ID @themeblocks:light,dark,high-contrast,high-contrast-light— like VS Code@sortingblock for sort priority/grouping@tableblock for column visibility/width/order- Layered resolution: global → project → nested
.faradayoverrides - Sub-O(N) matching via rule indexing by name, extension, language, and type
- Style caching for amortized O(1) per unique file signature
npm install fss-langimport { parseStylesheet, resolveStyle, createFsNode, StateFlags } from 'fss-lang'
// Parse an FSS stylesheet
const sheet = parseStylesheet(`
file { icon: url(default-file.svg); color: white; }
folder { icon: url(default-folder.svg); }
folder:expanded { icon: url(folder-open.svg); }
file[ext="ts"] { icon: url(ts.svg); color: blue; }
file[ext="test.ts"] { icon: url(test.svg); badge: "T"; }
file[name="Dockerfile"] { icon: url(docker.svg); }
file[lang="typescript"] { color: blue; }
file:is([ext="ts"], [ext="tsx"]) { color: blue; }
file[vcs-status="modified"] { color: orange; }
folder:root { icon: url(project-root.svg); }
folder[name=".github"] folder[name="workflows"] {
icon: url(gh-workflow.svg);
}
@sorting {
file[executable] { priority: 10; }
}
@table {
column(size) { visible: false; }
column(vcs-status) { width: 30; order: 2; }
}
`)
// Create a file node
const node = createFsNode({
type: 'file',
name: 'index.ts',
path: '/src/index.ts',
lang: 'typescript',
meta: { 'vcs-status': 'modified' },
})
// Resolve style
const style = resolveStyle(sheet, node, 'dark')
// → { icon: 'url(ts.svg)', color: '#58a6ff' }For large trees (10k+ nodes), use CachedResolver to avoid recomputing styles for nodes with identical signatures:
import { CachedResolver, parseStylesheet } from 'fss-lang'
const sheet = parseStylesheet(`...`)
const resolver = new CachedResolver(sheet, 'dark')
// Many .ts files share the same style — computed once, cached forever
const style = resolver.resolveStyle(node)
// Switch theme — cache is invalidated automatically
resolver.setTheme('light')Support global config with per-folder overrides (e.g. .faraday/style.fss):
import { LayeredResolver, createLayer, LayerPriority } from 'fss-lang'
const resolver = new LayeredResolver()
// Global defaults
resolver.addLayer(createLayer(`
file { icon: url(file.svg); }
`, '/', LayerPriority.GLOBAL))
// Project-specific overrides
resolver.addLayer(createLayer(`
file[ext="ts"] { icon: url(custom-ts.svg); }
`, '/my-project/', LayerPriority.PROJECT))
// Nested folder overrides (deeper = higher priority)
resolver.addLayer(createLayer(`
file { icon: url(special.svg); }
`, '/my-project/packages/core/', LayerPriority.nestedPriority(1)))
const style = resolver.resolveStyle(node)file { icon: url(file.svg); }
folder { icon: url(folder.svg); }
file[ext="ts"] { icon: url(ts.svg); }
file[name="Dockerfile"] { icon: url(docker.svg); }
file[ext$="d.ts"] { badge: "DT"; }
folder:expanded { icon: url(open.svg); }
file:is([ext="ts"], [ext="tsx"]) { color: blue; }
file[lang="typescript"] { icon: url(ts.svg); }
folder[name=".github"] folder[name="workflows"] { icon: url(gh.svg); }
file[inVcsRepo] { badge: "V"; }
folder:root { icon: url(project-root.svg); }
@theme dark {
file { color: #ccc; }
file[ext="ts"] { color: #58a6ff; }
}
@theme light {
file { color: #333; }
file[ext="ts"] { color: #0366d6; }
}
@theme high-contrast {
file[ext="ts"] { color: #79c0ff; font-weight: bold; }
}
@theme high-contrast-light {
file[ext="ts"] { color: #0969da; font-weight: bold; }
}
Supported kinds: light, dark, high-contrast, high-contrast-light (matching VS Code).
Rules outside @theme apply in all themes. Theme-scoped rules merge on top.
@sorting {
file[executable] { priority: 10; }
folder { group-first: true; }
}
@table {
column(size) { visible: false; }
column(vcs-status) { width: 30; order: 2; }
}
The engine uses rule indexing for sub-O(N) matching:
| Technique | Benefit |
|---|---|
| Bucket indexing by name, ext, type | Only evaluate ~5–20 candidate rules instead of all |
| Bitmask state matching | Single integer AND for pseudo-class checks |
| Style key caching | Amortized O(1) for nodes with identical signatures |
| Pre-sorted rules | No sorting during match phase |
| Candidate caching | WeakMap-based per-sheet cache for nodes sharing the same bucket key |
For a stylesheet with 10,000 rules and 5,000 files in the same directory, the engine evaluates only a small subset of candidate rules per node. Nodes sharing the same extension/type/name pattern reuse a cached candidate list, and CachedResolver further deduplicates resolution for nodes with identical style keys.
Parse FSS source into an indexed, compiled stylesheet.
Resolve all matching style declarations for a node.
When theme is provided, both unscoped and matching-theme rules apply.
resolveSorting(sheet: CompiledStylesheet, node: FsNode, theme?: ThemeKind): Record<string, FssValue>
Resolve sorting declarations for a node.
Create a node with auto-computed extensions.
Style resolver with per-signature caching. Accepts optional theme in constructor.
Call setTheme(theme) to switch — cache is auto-invalidated.
Multi-layer resolver with scope-based priority.
Call setTheme(theme) to switch — cache is auto-invalidated.
Create a scoped style layer from FSS source.
MIT