src/vs/platform/theme/common/iconRegistry.ts

345 LOC · 247 covered · 98 uncovered · 15 ranges · 1133 concepts · 1 introducers · 608 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- iconRegistry.ts ×15
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { RunOnceScheduler } from '../../../base/common/async.js';
7 > import { Codicon } from '../../../base/common/codicons.js';
8 > import { getCodiconFontCharacters } from '../../../base/common/codiconsUtil.js';
9 > import { ThemeIcon, IconIdentifier } from '../../../base/common/themables.js';
10 > import { Emitter, Event } from '../../../base/common/event.js';
11 > import { IJSONSchema, IJSONSchemaMap } from '../../../base/common/jsonSchema.js';
12 > import { isString } from '../../../base/common/types.js';
13 > import { URI } from '../../../base/common/uri.js';
14 > import { localize } from '../../../nls.js';
15 > import { Extensions as JSONExtensions, IJSONContributionRegistry } from '../../jsonschemas/common/jsonContributionRegistry.js';
16 > import * as platform from '../../registry/common/platform.js';
17 > import { Disposable } from '../../../base/common/lifecycle.js';
18 >
19 > // ------ API types
20 >
21 >
22 > // icon registry
23 > export const Extensions = {
24 > IconContribution: 'base.contributions.icons'
25 > };
26 >
27 > export type IconDefaults = ThemeIcon | IconDefinition;
28 >
29 > export interface IconDefinition {
30 > readonly font?: IconFontContribution; // undefined for the default font (codicon)
31 > readonly fontCharacter: string;
32 > }
33 >
34 >
35 > export interface IconContribution {
36 > readonly id: string;
37 > description: string | undefined;
38 > readonly deprecationMessage?: string;
39 > readonly defaults: IconDefaults;
40 > }
41 >
42 > export namespace IconContribution {
43 > export function getDefinition(contribution: IconContribution, registry: IIconRegistry): IconDefinition | undefined {
44 let definition = contribution.defaults;
45 while (ThemeIcon.isThemeIcon(definition)) {
46 const c = iconRegistry.getIcon(definition.id);
47 if (!c) {
48 return undefined;
49 }
50 definition = c.defaults;
51 }
52 return definition;
53 }
55 >
56 > export interface IconFontContribution {
57 > readonly id: string;
58 > readonly definition: IconFontDefinition;
59 > }
60 >
61 > export interface IconFontDefinition {
62 > readonly weight?: string;
63 > readonly style?: string;
64 > readonly src: IconFontSource[];
65 > }
66 >
67 > export namespace IconFontDefinition {
68 > export function toJSONObject(iconFont: IconFontDefinition): any {
69 return {
70 weight: iconFont.weight,
71 style: iconFont.style,
72 src: iconFont.src.map(s => ({ format: s.format, location: s.location.toString() }))
73 };
74 }
75 > export function fromJSONObject(json: any): IconFontDefinition | undefined { iconRegistry.ts ×15
76 const stringOrUndef = (s: any) => isString(s) ? s : undefined;
77 if (json && Array.isArray(json.src) && json.src.every((s: any) => isString(s.format) && isString(s.location))) {
78 return {
79 weight: stringOrUndef(json.weight),
80 style: stringOrUndef(json.style),
81 src: json.src.map((s: any) => ({ format: s.format, location: URI.parse(s.location) }))
82 };
83 }
84 return undefined;
85 }
87 >
88 >
89 > export interface IconFontSource {
90 > readonly location: URI;
91 > readonly format: string;
92 > }
93 >
94 > export interface IIconRegistry {
95 >
96 > readonly onDidChange: Event<void>;
97 >
98 > /**
99 > * Register a icon to the registry.
100 > * @param id The icon id
101 > * @param defaults The default values
102 > * @param description The description
103 > */
104 > registerIcon(id: IconIdentifier, defaults: IconDefaults, description?: string): ThemeIcon;
105 >
106 > /**
107 > * Deregister a icon from the registry.
108 > */
109 > deregisterIcon(id: IconIdentifier): void;
110 >
111 > /**
112 > * Get all icon contributions
113 > */
114 > getIcons(): IconContribution[];
115 >
116 > /**
117 > * Get the icon for the given id
118 > */
119 > getIcon(id: IconIdentifier): IconContribution | undefined;
120 >
121 > /**
122 > * JSON schema for an object to assign icon values to one of the icon contributions.
123 > */
124 > getIconSchema(): IJSONSchema;
125 >
126 > /**
127 > * JSON schema to for a reference to a icon contribution.
128 > */
129 > getIconReferenceSchema(): IJSONSchema;
130 >
131 > /**
132 > * Register a icon font to the registry.
133 > * @param id The icon font id
134 > * @param definition The icon font definition
135 > */
136 > registerIconFont(id: string, definition: IconFontDefinition): IconFontDefinition;
137 >
138 > /**
139 > * Deregister an icon font to the registry.
140 > */
141 > deregisterIconFont(id: string): void;
142 >
143 > /**
144 > * Get the icon font for the given id
145 > */
146 > getIconFont(id: string): IconFontDefinition | undefined;
147 > }
148 >
149 > // regexes for validation of font properties
150 >
151 > export const fontIdRegex = /^([\w_-]+)$/;
152 > export const fontStyleRegex = /^(normal|italic|(oblique[ \w\s-]+))$/;
153 > export const fontWeightRegex = /^(normal|bold|lighter|bolder|(\d{0-1000}))$/;
154 > export const fontSizeRegex = /^([\w_.%+-]+)$/;
155 > export const fontFormatRegex = /^woff|woff2|truetype|opentype|embedded-opentype|svg$/;
156 > export const fontColorRegex = /^#[0-9a-fA-F]{0,6}$/;
157 >
158 > export const fontIdErrorMessage = localize('schema.fontId.formatError', 'The font ID must only contain letters, numbers, underscores and dashes.');
159 >
160 > class IconRegistry extends Disposable implements IIconRegistry {
161 >
162 > private readonly _onDidChange = this._register(new Emitter<void>());
163 > readonly onDidChange: Event<void> = this._onDidChange.event;
164 >
165 > private iconsById: { [key: string]: IconContribution };
166 > private iconSchema: IJSONSchema & { properties: IJSONSchemaMap } = {
167 > definitions: {
168 > icons: {
169 > type: 'object',
170 > properties: {
171 > fontId: { type: 'string', description: localize('iconDefinition.fontId', 'The id of the font to use. If not set, the font that is defined first is used.'), pattern: fontIdRegex.source, patternErrorMessage: fontIdErrorMessage },
172 > fontCharacter: { type: 'string', description: localize('iconDefinition.fontCharacter', 'The font character associated with the icon definition.') }
173 > },
174 > additionalProperties: false,
175 > defaultSnippets: [{ body: { fontCharacter: '\\\\e030' } }]
176 > }
177 > },
178 > type: 'object',
179 > properties: {}
180 > };
181 > private iconReferenceSchema: IJSONSchema & { enum: string[]; enumDescriptions: string[] } = { type: 'string', pattern: `^${ThemeIcon.iconNameExpression}$`, enum: [], enumDescriptions: [] };
182 >
183 > private iconFontsById: { [key: string]: IconFontDefinition };
184 >
185 > constructor() {
186 > super();
187 > this.iconsById = {};
188 > this.iconFontsById = {};
189 > }
190 >
191 > public registerIcon(id: string, defaults: IconDefaults, description?: string, deprecationMessage?: string): ThemeIcon {
192 > const existing = this.iconsById[id];
193 > if (existing) {
194 if (description && !existing.description) {
195 existing.description = description;
196 this.iconSchema.properties[id].markdownDescription = `${description} $(${id})`;
197 const enumIndex = this.iconReferenceSchema.enum.indexOf(id);
198 if (enumIndex !== -1) {
199 this.iconReferenceSchema.enumDescriptions[enumIndex] = description;
200 }
201 this._onDidChange.fire();
202 }
203 return existing;
204 }
205 > const iconContribution: IconContribution = { id, description, defaults, deprecationMessage }; iconRegistry.ts ×15
206 > this.iconsById[id] = iconContribution;
207 > const propertySchema: IJSONSchema = { $ref: '#/definitions/icons' };
208 > if (deprecationMessage) {
209 propertySchema.deprecationMessage = deprecationMessage;
210 }
211 > if (description) { iconRegistry.ts ×15
212 > propertySchema.markdownDescription = `${description}: $(${id})`;
213 > }
214 > this.iconSchema.properties[id] = propertySchema;
215 > this.iconReferenceSchema.enum.push(id);
216 > this.iconReferenceSchema.enumDescriptions.push(description || '');
217 >
218 > this._onDidChange.fire();
219 > return { id };
220 > }
221 >
222 >
223 > public deregisterIcon(id: string): void {
224 delete this.iconsById[id];
225 delete this.iconSchema.properties[id];
226 const index = this.iconReferenceSchema.enum.indexOf(id);
227 if (index !== -1) {
228 this.iconReferenceSchema.enum.splice(index, 1);
229 this.iconReferenceSchema.enumDescriptions.splice(index, 1);
230 }
231 this._onDidChange.fire();
232 }
234 > public getIcons(): IconContribution[] {
235 return Object.keys(this.iconsById).map(id => this.iconsById[id]);
236 }
238 > public getIcon(id: string): IconContribution | undefined {
239 return this.iconsById[id];
240 }
242 > public getIconSchema(): IJSONSchema {
243 > return this.iconSchema;
244 > }
245 >
246 > public getIconReferenceSchema(): IJSONSchema {
247 return this.iconReferenceSchema;
248 }
250 > public registerIconFont(id: string, definition: IconFontDefinition): IconFontDefinition {
251 const existing = this.iconFontsById[id];
252 if (existing) {
253 return existing;
254 }
255 this.iconFontsById[id] = definition;
256 this._onDidChange.fire();
257 return definition;
258 }
260 > public deregisterIconFont(id: string): void {
261 delete this.iconFontsById[id];
262 }
264 > public getIconFont(id: string): IconFontDefinition | undefined {
265 return this.iconFontsById[id];
266 }
268 > public override toString() {
269 const sorter = (i1: IconContribution, i2: IconContribution) => {
270 return i1.id.localeCompare(i2.id);
271 };
272 const classNames = (i: IconContribution) => {
273 while (ThemeIcon.isThemeIcon(i.defaults)) {
274 i = this.iconsById[i.defaults.id];
275 }
276 return `codicon codicon-${i ? i.id : ''}`;
277 };
278
279 const reference = [];
280
281 reference.push(`| preview | identifier | default codicon ID | description`);
282 reference.push(`| ----------- | --------------------------------- | --------------------------------- | --------------------------------- |`);
283 const contributions = Object.keys(this.iconsById).map(key => this.iconsById[key]);
284
285 for (const i of contributions.filter(i => !!i.description).sort(sorter)) {
286 reference.push(`|<i class="${classNames(i)}"></i>|${i.id}|${ThemeIcon.isThemeIcon(i.defaults) ? i.defaults.id : i.id}|${i.description || ''}|`);
287 }
288
289 reference.push(`| preview | identifier `);
290 reference.push(`| ----------- | --------------------------------- |`);
291
292 for (const i of contributions.filter(i => !ThemeIcon.isThemeIcon(i.defaults)).sort(sorter)) {
293 reference.push(`|<i class="${classNames(i)}"></i>|${i.id}|`);
294
295 }
296
297 return reference.join('\n');
298 }
300 > }
301 >
302 > const iconRegistry = new IconRegistry();
303 > platform.Registry.add(Extensions.IconContribution, iconRegistry);
304 >
305 > export function registerIcon(id: string, defaults: IconDefaults, description: string, deprecationMessage?: string): ThemeIcon {
306 > return iconRegistry.registerIcon(id, defaults, description, deprecationMessage);
307 > }
308 >
309 > export function getIconRegistry(): IIconRegistry {
310 return iconRegistry;
311 }
313 > function initialize() {
314 > const codiconFontCharacters = getCodiconFontCharacters();
315 > for (const icon in codiconFontCharacters) {
316 > const fontCharacter = '\\' + codiconFontCharacters[icon].toString(16);
317 > iconRegistry.registerIcon(icon, { fontCharacter });
318 > }
319 > }
320 > initialize();
321 >
322 > export const iconsSchemaId = 'vscode://schemas/icons';
323 >
324 > const schemaRegistry = platform.Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
325 > schemaRegistry.registerSchema(iconsSchemaId, iconRegistry.getIconSchema());
326 >
327 > const delayer = new RunOnceScheduler(() => schemaRegistry.notifySchemaChanged(iconsSchemaId), 200);
328 > iconRegistry.onDidChange(() => {
329 > if (!delayer.isScheduled()) {
330 > delayer.schedule();
331 > }
332 > });
333 >
334 > //setTimeout(_ => console.log(iconRegistry.toString()), 5000);
335 >
336 >
337 > // common icons
338 >
339 > export const widgetClose = registerIcon('widget-close', Codicon.close, localize('widgetClose', 'Icon for the close action in widgets.'));
340 >
341 > export const gotoPreviousLocation = registerIcon('goto-previous-location', Codicon.arrowUp, localize('previousChangeIcon', 'Icon for goto previous editor location.'));
342 > export const gotoNextLocation = registerIcon('goto-next-location', Codicon.arrowDown, localize('nextChangeIcon', 'Icon for goto next editor location.'));
343 >
344 > export const syncing = ThemeIcon.modify(Codicon.sync, 'spin');
345 > export const spinningLoading = ThemeIcon.modify(Codicon.loading, 'spin');