In short: IconThemeService.loadTheme reads workbench.iconTheme, finds the
extension that contributes that theme, and parses the theme file with jsonc-parser into an
IconTheme. getFileIcon(name) then resolves the name in this order: exact file
name, the file name's language, the extension, the extension's language, and finally the default icon.
The resolved icon definition becomes a vscode-webview:// URI for an image, or a font URI plus
glyph and colour for a font icon. The result is cached per theme ID. At this pin nothing outside the
directory and its test imports the service, so the commit graph doesn't show file icons yet.
1. Files
| File | Lines | What it holds |
|---|---|---|
| icon_theme_json_parser.ts | 63 | The theme file's types and a one-line parser. |
| icon_theme.ts | 194 | The IconTheme model: locating the theme, merging the light variant, resolving icon IDs. |
| icon_theme_service.ts | 145 | IconThemeService: the per-webview cache and the FileIcon it returns. |
| icon_theme_service_test.ts | 164 | Two Jasmine tests, one image theme and one font theme. |
| BUILD.bazel | 45 | The ts_library and its jasmine_test. |
2. Finding the active theme
loadTheme reads the theme ID from workbench.iconTheme, falling back to
vs-seti when the setting is empty (source).
IconTheme.load then asks getIconThemeExtension where that theme lives
(source):
- It walks every installed extension and looks at
packageJSON.contributes.iconThemes. An entry matches when itsidequals the theme ID, or, for an entry with noid, when itslabeldoes. The first match wins and supplies the theme file'spath(source). - With no match, it falls back to the built-in
vscode.theme-setiextension and the hard-coded path./icons/vs-seti-icon-theme.json. If even that extension is missing it throwsCannot find built-in VS Code Seti theme(source).
"Every installed extension" is getAllExtensions
(source). vscode.extensions.all only lists
extensions in the caller's own extension host, so a theme installed in another host (the UI side of a
remote window, for instance) would be missed. The function therefore first tries the proposed
allAcrossExtensionHosts API. It remembers in a module-level flag whether reading the
property succeeded, and uses extensions.all when it threw. The comment above the
function says the proposal is meant for internal builds. This repo's package.json has no
enabledApiProposals, so a public build relies on the read throwing and takes the fallback.
The test fake defines allAcrossExtensionHosts as a getter that returns all
(source), so the tests always take the first branch.
The theme file is read with vscode.workspace.fs.readFile, decoded as UTF-8, and parsed.
themeDir, the directory holding the file, is kept because every path inside a theme is
relative to it (source).
3. Parsing the theme file
parseIconThemeJson is a single call to jsonc-parser's parse, cast
to IconThemeDocument (source). JSONC matters because
theme files, Seti's included, may contain comments and trailing commas, which JSON.parse
rejects. Nothing is validated. A missing or mistyped field simply reads as undefined later.
The types follow VS Code's file icon theme format, which the file links to.
IconThemeDocument and IconThemeVariant
icon_theme_json_parser.ts, lines 45–59
| Field | Type | Meaning | Source |
|---|---|---|---|
file | string? | Icon ID for files that match nothing else. | L46 |
folder, folderExpanded | string? | Folder icon IDs. Parsed, never read. | L47–48 |
fileNames | Record<string, string>? | Exact (lower-case) file name → icon ID. | L49 |
fileExtensions | Record<string, string>? | Extension without the dot → icon ID. | L50 |
languageIds | Record<string, string>? | VS Code language ID → icon ID. | L51 |
iconDefinitions | Record<string, IconDefinition>? | Icon ID → how to draw it. | L52 |
fonts | ThemeFont[]? | Document only. Fonts that font-glyph icons draw with. | L56 |
light | IconThemeVariant? | Document only. Overrides used under a light colour theme. | L57 |
highContrast | IconThemeVariant? | Document only. Parsed, never read. | L58 |
IconDefinition
icon_theme_json_parser.ts, lines 24–30
| Field | Type | Meaning | Source |
|---|---|---|---|
iconPath | string? | Image path, relative to the theme file. Takes priority over a glyph. | L25 |
fontCharacter | string? | Glyph code, for example \E029. | L26 |
fontColor | string? | Glyph colour. The service needs it for a font icon. | L27 |
fontId | string? | Which ThemeFont to use. The first font if absent. | L28 |
fontSize | string? | Parsed, never read. | L29 |
ThemeFont and ThemeFontSrc
icon_theme_json_parser.ts, lines 32–43
| Field | Type | Meaning | Source |
|---|---|---|---|
id | string? | The name fontId refers to. A font without one is skipped. | L38 |
src | ThemeFontSrc[]? | Font files. Only the first entry's path is used. | L39 |
weight, style, size | string? | Parsed, never read. | L40–42 |
ThemeFontSrc.path | string | Font file, relative to the theme file. | L33 |
ThemeFontSrc.format | string? | For example woff. Parsed, never read. | L34 |
4. The IconTheme model
The private constructor flattens the document into lookup tables once
(source). When activeColorTheme.kind is exactly
Light, the light variant's three maps are spread over the base ones and its
file replaces the base default. Otherwise the base is used as is. The default icon ID falls
back to _default when neither sets file.
| Field | Type | Meaning | Source |
|---|---|---|---|
themeId | string | The ID it was loaded for. The cache key. | L40 |
themeExtension | vscode.Extension | The extension that contributes the theme. Its URI is added to the webview's resource roots. | L41 |
themeDir | vscode.Uri | Directory of the theme file, the base for icon and font paths. | L43 |
fileNameToIconId, fileExtToIconId, languageIdToIconId | Record<string, string> | The theme's maps, with the light variant merged in. | L28–30 |
defaultIconId | string | Used when nothing matches. | L33 |
fileNameToLanguageId, fileExtensionToLanguageId | Record<string, string> | Built from every installed extension's contributes.languages filenames and extensions, lower-cased, leading dot stripped. A later extension overwrites an earlier one. | L149–166 |
Resolving a file name
getIconId lower-cases the name and stops at the first hit (source):
fileNames[name].- The language registered for that exact file name, then
languageIds[language]. fileExtensions[ext], whereextis the text after the last dot.- The language registered for that extension, then
languageIds[language]. defaultIconId.
getIconDefinition then looks the ID up in the document's top-level
iconDefinitions, and getFonts returns fonts or an empty list
(source).
5. Serving icons to a webview
An IconThemeService is built for one vscode.Webview
(source). Webviews can only load local files under their
localResourceRoots, so when a theme loads, addExtensionUriToWebview appends the
theme extension's URI to that list unless it is already there, by reassigning webview.options
(source). Each font's first src path is then turned
into a webview URI, keyed by font ID, and the first font's ID becomes the default
(source).
getFileIcon(name) is synchronous and returns {} until a theme has been loaded
(source). With a theme, an iconPath definition gives
{iconUri}. A fontCharacter definition gives {font}, but only when a
font family, its URI and a fontColor are all known. Anything else gives {}.
FileIcon
icon_theme_service.ts, lines 20–27
| Field | Type | Meaning | Source |
|---|---|---|---|
iconUri | string? | Webview URI of an image icon. | L23 |
font | FileFont? | Set instead when the theme draws with a font. At most one of the two is set. Both are absent when nothing resolved. | L26 |
FileFont
icon_theme_service.ts, lines 29–42
| Field | Type | Meaning | Source |
|---|---|---|---|
fontUri | string | Webview URI of the font file. | L32 |
fontCharacter | string | The glyph code. | L35 |
fontColor | string | The glyph colour. | L38 |
fontFamily | string | The font's ID, meant to be used as a CSS font family name. | L41 |
Both interfaces' doc comments call them duplicates of FileIcon and FileFont in
src/ui/commit_graph/api/types.ts, kept separate to avoid a dependency. Those types aren't
there at this pin: the protocol's only file type is
File, which has just a
path.
6. Caching and invalidation
The cache is one slot per service: the IconTheme, its font URIs and the default font ID
(source). loadTheme returns it unchanged when the
configured theme ID matches the cached themeId. Otherwise it loads the new theme and
replaces the slot (source). There is no event listener: the cache
is only refreshed when someone calls loadTheme again. Because the key is only the theme ID,
some changes are never picked up while the theme stays the same:
- Switching between light and dark colour themes. The light variant is merged in the constructor.
- Installing or removing an extension that contributes languages. The language maps are built in the constructor.
- An update to the theme extension that changes its JSON.
7. Tests
Both tests install the fake VS Code API from src/testing/, register a FakeExtension contributing one icon theme, set
workbench.iconTheme, write the theme JSON into the fake file system and load it into a
service bound to a FakeWebview.
| Test | Checks | Source |
|---|---|---|
| resolves image file icons | file.ts resolves through fileExtensions to the TypeScript SVG. other.txt falls back to file. Both are vscode-webview:// URIs under the theme directory. | 25–81 |
| resolves font-based file icons | file.ts gets the glyph with its explicit fontId. other.txt gets a definition without fontId and so uses the first font. Both share the same font URI. | 83–163 |
Not covered: file-name and language-ID lookups, the light variant, the Seti fallback, label-only theme entries, and the resource-root update.
8. Loose ends at this pin
- Not wired in. Nothing outside this directory imports
IconThemeService. The Bazel target is visible to//src/ui/commit_graph_provider:__subpackages__, but that directory has noBUILD.bazelat this pin (source). The target also depends on//src/ui/commit_graph/apiwithout importing it. - Extensions with dots. Only the text after the last dot is tried, so a theme's
d.tsortest.tsentries never match. VS Code itself tries the longer extensions first. - Names with a path or no dot. The whole argument is looked up as a file name, so a
path like
src/a.tsnever hitsfileNames. A name with no dot is also tried as an extension. - Variants.
highContrastis ignored, and so islightunder the high-contrast light kind. A variant's owniconDefinitionsare never consulted. - Folders and font details. Folder icons,
fontSize, font weight, style, format and everysrcafter the first are dropped. A font glyph withoutfontColorresolves to nothing, though the format lets it inherit the text colour. - The Seti fallback path is hard-coded, so it would break if VS Code moved that file.