[EN] VSCode - Systems: Configuration System
Configuration System
Before diving in, let me just say, I personally believe the configuration system is among the top 10 or even top 5 most complex systems in VSCode.
The business logic is intricate and tedious, and the codebase is extensive. Some parts are so challenging that without GPT-4’s help, it would be hard to comprehend them entirely.
After reading through the basic framework, it feels like this system evolved over time as the project progressed, resulting in its current state. Many names and designs are not intuitive, showcasing various design patterns and coding styles.
VSCode’s configuration system includes numerous functionalities. Besides the most apparent “reading native configuration files,” it also involves:
- Cross-process configuration read/write handling,
- Configuration registration: how different processes register configurations, how they are managed, categorized, and unified, including handling overrides and identifying plugin configurations,
- Reading non-native configuration files (related to remote features),
- And more…
However, most of these aspects won’t be covered here 😅.
Since my learning process was selective, I didn’t read the entire system systematically. Naturally, I skipped parts that I didn’t find immediately relevant.
- For example, I overlooked parts like
PolicyConfiguration,WorkspaceConfiguration,RemoteUserConfiguration, andFolderConfigurationinConfigurationServicebecause they are deeply tied to VSCode as a product. When designing your own configuration system, you can decide how to structure it without necessarily referencing VSCode.- On the other hand, general concepts like cross-process configuration communication, default/user configuration registration, and integration are more practical and worth learning from.
My writing style is technical and focused on low-level details. I didn’t simplify much or summarize broadly, instead describing the code as it is. Apologies if it’s dry—I may write a summary later.
I divided the configuration system into the following critical components:
ConfigurationRegistryclass - Registers configurations for different software components.ConfigurationService- Main process microservice.WorkspaceService- Renderer process microservice.
1. Configuration Registration
Prerequisites: Familiarity with
IJSONSchemaand the concept ofRegistry.
Let’s first introduce some basic interfaces.
IConfigurationPropertySchema Interface
IConfigurationPropertySchemaextendsIJSONSchemaby adding new fields.- Each
IConfigurationPropertySchemacan be understood as a single configuration item.- For instance,
font size: 12pxis a single configuration.
- For instance,
IConfigurationNode Interface
- Each node contains a set of
IConfigurationPropertySchema(stored in thepropertiesfield). Thus, a node can be seen as a group of schemas or a configuration set.- Nodes can also be nested through the
allOffield.
- Nodes can also be nested through the
IConfigurationNodeis the fundamental unit for registering default configurations inConfigurationRegistry. The APIs inConfigurationRegistrygenerally take this interface as input.
ConfigurationRegistry Class
- VSCode uses
ConfigurationRegistryto register configurations (specificallyIConfigurationNode).- Note: Configurations stored here are default configurations. The registry does not support updating configurations.
The actual configuration modification microservice in VSCode is called ConfigurationService.
Class Fields
configurationProperties- Stores all registered configuration properties, including their default values. Related fields like
applicationSettings,machineSettings, andmachineOverridableSettingscategorize properties by scope. - The
getConfigurationPropertiesAPI returns this field. Other components access configurations through this frequently used API.
- Stores all registered configuration properties, including their default values. Related fields like
configurationDefaultsOverrides- Stores default configuration overrides. When accessing a property, the system checks for override values in this field and uses them if available.
This enables extensions or other system parts to alter default behaviors without modifying the underlying code.
configurationContributors- Tracks registered
IConfigurationNodeinstances. Configurations are added viaregisterand removed viaderegister. ThegetConfigurationsAPI provides access to this field, though it’s rarely used.
- Tracks registered
2. ConfigurationService Main Process Microservice
- Created early in the program in the main process within the
CodeMainclass (though its files are located in thecommondirectory). ConfigurationServicehas minimal code as most logic is delegated to other classes.This microservice does not support the
updateValueAPI, meaning configuration modifications require reloading or editing the source configuration file (JSON).- The
WorkspaceService, introduced later, supports theupdateValueAPI.
- The
Key classes:
DefaultConfiguration,UserSettings, andConfiguration(I didn’t delve intoIPolicyConfigurationas it wasn’t critical for understanding the system).- External programs fetch all configuration data from the
Configurationclass.
- External programs fetch all configuration data from the
- Below is the constructor for
ConfigurationServiceto give you a sense of the setup sequence:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable {
declare readonly _serviceBrand: undefined;
private configuration: Configuration;
private readonly defaultConfiguration: DefaultConfiguration;
private readonly policyConfiguration: IPolicyConfiguration;
private readonly userConfiguration: UserSettings;
private readonly reloadConfigurationScheduler: RunOnceScheduler;
private readonly _onDidChangeConfiguration: Emitter<IConfigurationChangeEvent> = this._register(new Emitter<IConfigurationChangeEvent>());
readonly onDidChangeConfiguration: Event<IConfigurationChangeEvent> = this._onDidChangeConfiguration.event;
constructor(
private readonly settingsResource: URI,
fileService: IFileService,
policyService: IPolicyService,
logService: ILogService,
) {
super();
this.defaultConfiguration = this._register(new DefaultConfiguration());
this.policyConfiguration = policyService instanceof NullPolicyService ?
new NullPolicyConfiguration() :
this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService));
this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, extUriBiasedIgnorePathCase, fileService));
this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel());
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reloadConfiguration(), 50));
this._register(this.defaultConfiguration.onDidChangeConfiguration(({ defaults, properties }) => this.onDidDefaultConfigurationChange(defaults, properties)));
this._register(this.policyConfiguration.onDidChangeConfiguration(model => this.onDidPolicyConfigurationChange(model)));
this._register(this.userConfiguration.onDidChange(() => this.reloadConfigurationScheduler.schedule()));
}
async initialize(): Promise<void> {
const [defaultModel, policyModel, userModel] = await Promise.all([
this.defaultConfiguration.initialize(),
this.policyConfiguration.initialize(),
this.userConfiguration.loadConfiguration()
]);
this.configuration = new Configuration(defaultModel, policyModel, new ConfigurationModel(), userModel);
}
// ...
}
To understand the three classes mentioned earlier, let’s first explore their shared base class:
ConfigurationModel.
ConfigurationModel Class
- Stores configurations as an object.
- Adding values requires key-value pairs. Keys follow a predefined format:
- Must be strings separated by
.. - Example:
vscodeorvscode.workspace.language. - Data structure after adding values:
- Must be strings separated by
1
2
3
4
5
6
7
8
9
10
11
12
{
'vscode': {
size: 5,
name: 'hello world',
'workspace': {
'language': {
lang: 'C++',
restricted: true,
}
}
}
}
DefaultConfiguration Class
- Located in
vscode\src\vs\platform\configuration\common\configurations.ts. resetAPI- Fetches all default configurations via
ConfigurationRegistry.getConfigurationProperties()and creates aConfigurationModelto store these defaults.
- Fetches all default configurations via
initializeAPI- Calls
resetand listens to theConfigurationRegistry.onDidUpdateConfigurationevent to keep its model updated.
- Calls
UserSettings Class
- Takes a
settingsResourceURI and uses an embeddedConfigurationModelParserto read the configuration file.- Parsing ensures data conforms to the schema defined in
ConfigurationRegistry. - Invalid fields are filtered out during the parsing process.
- Parsing ensures data conforms to the schema defined in
- The parsed data is stored in a
ConfigurationModel.
Configuration Class
Take a look at the initial lines of this class (I believe renaming it to something like ConfigurationCollection or AllConfiguration would be more fitting):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export class Configuration {
private _workspaceConsolidatedConfiguration: ConfigurationModel | null = null;
private _foldersConsolidatedConfigurations = new ResourceMap<ConfigurationModel>();
constructor(
private _defaultConfiguration: ConfigurationModel,
private _policyConfiguration: ConfigurationModel,
private _applicationConfiguration: ConfigurationModel,
private _localUserConfiguration: ConfigurationModel,
private _remoteUserConfiguration: ConfigurationModel = new ConfigurationModel(),
private _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(),
private _folderConfigurations: ResourceMap<ConfigurationModel> = new ResourceMap<ConfigurationModel>(),
private _memoryConfiguration: ConfigurationModel = new ConfigurationModel(),
private _memoryConfigurationByResource: ResourceMap<ConfigurationModel> = new ResourceMap<ConfigurationModel>()
) {
}
// ...
}
- During
ConfigurationService.initialize(),DefaultConfigurationandUserSettingsprovide theirConfigurationModelinstances to this class. - The
Configurationclass consolidates all models based on theDefaultConfigurationmodel. Other configurations are merged into it. - While verbose, most of the code handles integration and merging.
3. WorkspaceService Renderer Process Microservice
- Only appears in the renderer process, specifically in
web.main.tsanddesktop.main.ts. - Despite its name,
WorkspaceServiceis essentially the renderer process version ofConfigurationService. - Unlike
ConfigurationService, this service supports theupdateValueAPI. See theConfigurationEditingclass for details.
ConfigurationCache Class
- Since reading/writing configuration files in the renderer process (native or non-native/remote) involves asynchronous operations, all its interfaces return Promises.
1
2
3
4
5
6
7
8
export type ConfigurationKey = { type: 'defaults' | 'user' | 'workspaces' | 'folder'; key: string; };
export interface IConfigurationCache {
needsCaching(resource: URI): boolean;
read(key: ConfigurationKey): Promise<string>;
write(key: ConfigurationKey, content: string): Promise<void>;
remove(key: ConfigurationKey): Promise<void>;
}
- A simple utility class offering
read,write, andremoveAPIs for non-native configuration files.- Operates on the entire file (
contentinwriteis the full file content). - Each non-native configuration file is paired with a dedicated queue to ensure read/write operations are sequential.
- Used by classes like
RemoteUserConfiguration,WorkspaceConfiguration, andFolderConfiguration. DefaultConfigurationandUserConfigurationrarely use this as their data is usually native.
- Operates on the entire file (
Only appears in the renderer process, specifically in
web.main.tsanddesktop.main.ts.- In
desktop.main.ts, it’s constructed like this:
- In
1
2
const configurationCache = new ConfigurationCache([Schemas.file, Schemas.vscodeUserData] /* Cache all non-native resources */, /* ... */);
const workspaceService = new WorkspaceService({ remoteAuthority: environmentService.remoteAuthority, configurationCache }, /* ... */);
DefaultConfiguration Class
- Located in
vscode\src\vs\workbench\services\configuration\browser\configuration.ts. - Extends the
DefaultConfigurationclass from\common\but with no significant changes worth noting.
UserConfiguration Class
- This class resides in the
browserdirectory and primarily wraps theUserSettingsclass from thecommonfolder. - Notable during reloads: the embedded
UserSettingscan be replaced with aFileServiceBasedConfiguration. The exact purpose of this was not explored.
Configuration Class
- The renderer process uses the same
Configurationclass as the main process, so there’s no need for repetition here.
ConfigurationEditing Class - How Configurations Are Edited
WorkspaceServiceenables updating configurations usingupdateValue, which relies on the key-value pair mechanism. The core function iswriteConfigurationValue.- Does not support modifying default configurations.
- Internally,
writeConfigurationValueinitializes aConfigurationEditinginstance and calls its API.
ConfigurationEditingexposes a single API calledwriteConfiguration.