Skip to content

Commit 9b588cc

Browse files
committed
en version for configuration service part 1
1 parent 959a16a commit 9b588cc

File tree

2 files changed

+265
-2
lines changed

2 files changed

+265
-2
lines changed

3. Systems/2023-08-19-VSCode配置系统 1:Configuration Service.md renamed to 3. Systems/2023-08-19-[CN] VSCode系列「系统篇」:配置系统 copy.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
layout: post
3-
title: "Configuration Service"
3+
title: "[CN] VSCode系列「系统篇」:配置系统"
44
categories: [VSCode, Systems]
55
---
66

@@ -266,4 +266,3 @@ export class Configuration {
266266
* 不支持修改默认配置.
267267
* `writeConfigurationValue`里会初始化一个`ConfigurationEditing`并调用其API.
268268
* 该类只有一个API叫做`writeConfiguration`.
269-
*
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
---
2+
layout: post
3+
title: "[EN] VSCode Series [System]: Configuration System"
4+
categories: [VSCode, Systems]
5+
---
6+
7+
# Configuration System
8+
9+
> 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.
10+
>
11+
> 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.
12+
>
13+
> 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 <u>not intuitive</u>, showcasing various design patterns and coding styles.
14+
>
15+
> VSCode's configuration system includes numerous functionalities. Besides the most apparent "reading native configuration files," it also involves:
16+
>
17+
> - Cross-process configuration read/write handling,
18+
> - Configuration registration: how different processes register configurations, how they are managed, categorized, and unified, including handling overrides and identifying plugin configurations,
19+
> - Reading non-native configuration files (related to remote features),
20+
> - And more...
21+
>
22+
> However, most of these aspects won't be covered here 😅.
23+
>
24+
> 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.
25+
>
26+
> - For example, I overlooked parts like `PolicyConfiguration`, `WorkspaceConfiguration`, `RemoteUserConfiguration`, and `FolderConfiguration` in `ConfigurationService` because 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.
27+
> - On the other hand, general concepts like cross-process configuration communication, default/user configuration registration, and integration are more practical and worth learning from.
28+
>
29+
> 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.
30+
31+
I divided the configuration system into the following critical components:
32+
33+
1. `ConfigurationRegistry` class - Registers configurations for different software components.
34+
2. `ConfigurationService` - Main process microservice.
35+
3. `WorkspaceService` - Renderer process microservice.
36+
37+
## 1. Configuration Registration
38+
39+
> Prerequisites: Familiarity with `IJSONSchema` and the concept of `Registry`.
40+
41+
Let’s first introduce some basic interfaces.
42+
43+
### `IConfigurationPropertySchema` Interface
44+
45+
- `IConfigurationPropertySchema` extends `IJSONSchema` by adding new fields.
46+
- Each `IConfigurationPropertySchema` can be understood as a single configuration item.
47+
- For instance, `font size: 12px` is a single configuration.
48+
49+
### `IConfigurationNode` Interface
50+
51+
- Each node contains a set of `IConfigurationPropertySchema` (stored in the `properties` field). Thus, a node can be seen as a group of schemas or a configuration set.
52+
- Nodes can also be nested through the `allOf` field.
53+
- `IConfigurationNode` is the fundamental unit for registering default configurations in `ConfigurationRegistry`. The APIs in `ConfigurationRegistry` generally take this interface as input.
54+
55+
### `ConfigurationRegistry` Class
56+
57+
- VSCode uses `ConfigurationRegistry` to register configurations (specifically `IConfigurationNode`).
58+
- **Note:** Configurations stored here are default configurations. The registry does not support updating configurations.
59+
60+
The actual configuration modification microservice in VSCode is called `ConfigurationService`.
61+
62+
#### Class Fields
63+
64+
- `configurationProperties`
65+
66+
- Stores all registered configuration properties, including their default values. Related fields like `applicationSettings`, `machineSettings`, and `machineOverridableSettings` categorize properties by scope.
67+
- The `getConfigurationProperties` API returns this field. Other components access configurations through this frequently used API.
68+
69+
- `configurationDefaultsOverrides`
70+
71+
- Stores default configuration overrides. When accessing a property, the system checks for override values in this field and uses them if available.
72+
73+
> This enables extensions or other system parts to alter default behaviors without modifying the underlying code.
74+
75+
- `configurationContributors`
76+
77+
- Tracks registered `IConfigurationNode` instances. Configurations are added via `register` and removed via `deregister`. The `getConfigurations` API provides access to this field, though it’s rarely used.
78+
79+
## 2. `ConfigurationService` Main Process Microservice
80+
81+
- Created early in the program in the **main process** within the `CodeMain` class (though its files are located in the `common` directory).
82+
- `ConfigurationService` has minimal code as most logic is delegated to other classes.
83+
84+
- This microservice does **not support** the `updateValue` API, meaning configuration modifications require reloading or editing the source configuration file (JSON).
85+
86+
- The `WorkspaceService`, introduced later, supports the `updateValue` API.
87+
88+
- Key classes: `DefaultConfiguration`, `UserSettings`, and `Configuration` (I didn’t delve into `IPolicyConfiguration` as it wasn’t critical for understanding the system).
89+
90+
- External programs fetch all configuration data from the `Configuration` class.
91+
92+
- Below is the constructor for `ConfigurationService` to give you a sense of the setup sequence:
93+
94+
```typescript
95+
export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable {
96+
declare readonly _serviceBrand: undefined;
97+
98+
private configuration: Configuration;
99+
private readonly defaultConfiguration: DefaultConfiguration;
100+
private readonly policyConfiguration: IPolicyConfiguration;
101+
private readonly userConfiguration: UserSettings;
102+
private readonly reloadConfigurationScheduler: RunOnceScheduler;
103+
104+
private readonly _onDidChangeConfiguration: Emitter<IConfigurationChangeEvent> = this._register(new Emitter<IConfigurationChangeEvent>());
105+
readonly onDidChangeConfiguration: Event<IConfigurationChangeEvent> = this._onDidChangeConfiguration.event;
106+
107+
constructor(
108+
private readonly settingsResource: URI,
109+
fileService: IFileService,
110+
policyService: IPolicyService,
111+
logService: ILogService,
112+
) {
113+
super();
114+
this.defaultConfiguration = this._register(new DefaultConfiguration());
115+
this.policyConfiguration = policyService instanceof NullPolicyService ?
116+
new NullPolicyConfiguration() :
117+
this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService));
118+
this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, extUriBiasedIgnorePathCase, fileService));
119+
this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel());
120+
121+
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reloadConfiguration(), 50));
122+
this._register(this.defaultConfiguration.onDidChangeConfiguration(({ defaults, properties }) => this.onDidDefaultConfigurationChange(defaults, properties)));
123+
this._register(this.policyConfiguration.onDidChangeConfiguration(model => this.onDidPolicyConfigurationChange(model)));
124+
this._register(this.userConfiguration.onDidChange(() => this.reloadConfigurationScheduler.schedule()));
125+
}
126+
127+
async initialize(): Promise<void> {
128+
const [defaultModel, policyModel, userModel] = await Promise.all([
129+
this.defaultConfiguration.initialize(),
130+
this.policyConfiguration.initialize(),
131+
this.userConfiguration.loadConfiguration()
132+
]);
133+
this.configuration = new Configuration(defaultModel, policyModel, new ConfigurationModel(), userModel);
134+
}
135+
// ...
136+
}
137+
```
138+
139+
> To understand the three classes mentioned earlier, let’s first explore their shared base class: `ConfigurationModel`.
140+
141+
#### `ConfigurationModel` Class
142+
143+
- Stores configurations as an object.
144+
- Adding values requires key-value pairs. Keys follow a predefined format:
145+
- Must be strings separated by `.`.
146+
- Example: `vscode` or `vscode.workspace.language`.
147+
- Data structure after adding values:
148+
149+
```typescript
150+
{
151+
'vscode': {
152+
size: 5,
153+
name: 'hello world',
154+
'workspace': {
155+
'language': {
156+
lang: 'C++',
157+
restricted: true,
158+
}
159+
}
160+
}
161+
}
162+
```
163+
164+
#### `DefaultConfiguration` Class
165+
166+
- Located in `vscode\src\vs\platform\configuration\common\configurations.ts`.
167+
- **`reset` API**
168+
- Fetches all default configurations via `ConfigurationRegistry.getConfigurationProperties()` and creates a `ConfigurationModel` to store these defaults.
169+
- **`initialize` API**
170+
- Calls `reset` and listens to the `ConfigurationRegistry.onDidUpdateConfiguration` event to keep its model updated.
171+
172+
#### `UserSettings` Class
173+
174+
- Takes a `settingsResource` URI and uses an embedded `ConfigurationModelParser` to read the configuration file.
175+
- Parsing ensures data conforms to the schema defined in `ConfigurationRegistry`.
176+
- Invalid fields are filtered out during the parsing process.
177+
- The parsed data is stored in a `ConfigurationModel`.
178+
179+
#### `Configuration` Class
180+
181+
Take a look at the initial lines of this class (I believe renaming it to something like `ConfigurationCollection` or `AllConfiguration` would be more fitting):
182+
183+
```typescript
184+
export class Configuration {
185+
186+
private _workspaceConsolidatedConfiguration: ConfigurationModel | null = null;
187+
private _foldersConsolidatedConfigurations = new ResourceMap<ConfigurationModel>();
188+
189+
constructor(
190+
private _defaultConfiguration: ConfigurationModel,
191+
private _policyConfiguration: ConfigurationModel,
192+
private _applicationConfiguration: ConfigurationModel,
193+
private _localUserConfiguration: ConfigurationModel,
194+
private _remoteUserConfiguration: ConfigurationModel = new ConfigurationModel(),
195+
private _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(),
196+
private _folderConfigurations: ResourceMap<ConfigurationModel> = new ResourceMap<ConfigurationModel>(),
197+
private _memoryConfiguration: ConfigurationModel = new ConfigurationModel(),
198+
private _memoryConfigurationByResource: ResourceMap<ConfigurationModel> = new ResourceMap<ConfigurationModel>()
199+
) {
200+
}
201+
// ...
202+
}
203+
```
204+
205+
- During `ConfigurationService.initialize()`, `DefaultConfiguration` and `UserSettings` provide their `ConfigurationModel` instances to this class.
206+
- The `Configuration` class consolidates all models based on the `DefaultConfiguration` model. Other configurations are merged into it.
207+
- While verbose, most of the code handles integration and merging.
208+
209+
## 3. `WorkspaceService` Renderer Process Microservice
210+
211+
- <u>Only appears in the renderer process</u>, specifically in `web.main.ts` and `desktop.main.ts`.
212+
- Despite its name, `WorkspaceService` is essentially the renderer process version of `ConfigurationService`.
213+
- Unlike `ConfigurationService`, this service supports the `updateValue` API. See the `ConfigurationEditing` class for details.
214+
215+
### `ConfigurationCache` Class
216+
217+
- Since reading/writing configuration files in the renderer process (native or non-native/remote) involves asynchronous operations, all its interfaces return Promises.
218+
219+
```typescript
220+
export type ConfigurationKey = { type: 'defaults' | 'user' | 'workspaces' | 'folder'; key: string; };
221+
222+
export interface IConfigurationCache {
223+
needsCaching(resource: URI): boolean;
224+
read(key: ConfigurationKey): Promise<string>;
225+
write(key: ConfigurationKey, content: string): Promise<void>;
226+
remove(key: ConfigurationKey): Promise<void>;
227+
}
228+
```
229+
230+
- A simple utility class offering `read`, `write`, and `remove` APIs for non-native configuration files.
231+
- Operates on the entire file (`content` in `write` is the full file content).
232+
- Each non-native configuration file is paired with a dedicated queue to ensure read/write operations are sequential.
233+
- Used by classes like `RemoteUserConfiguration`, `WorkspaceConfiguration`, and `FolderConfiguration`.
234+
- `DefaultConfiguration` and `UserConfiguration` rarely use this as their data is usually native.
235+
236+
- <u>Only appears in the renderer process</u>, specifically in `web.main.ts` and `desktop.main.ts`.
237+
238+
- In `desktop.main.ts`, it's constructed like this:
239+
240+
```typescript
241+
const configurationCache = new ConfigurationCache([Schemas.file, Schemas.vscodeUserData] /* Cache all non-native resources */, /* ... */);
242+
const workspaceService = new WorkspaceService({ remoteAuthority: environmentService.remoteAuthority, configurationCache }, /* ... */);
243+
```
244+
245+
### `DefaultConfiguration` Class
246+
247+
- Located in `vscode\src\vs\workbench\services\configuration\browser\configuration.ts`.
248+
- Extends the `DefaultConfiguration` class from `\common\` but with no significant changes worth noting.
249+
250+
### `UserConfiguration` Class
251+
252+
- This class resides in the `browser` directory and primarily wraps the `UserSettings` class from the `common` folder.
253+
- Notable during reloads: the embedded `UserSettings` can be replaced with a `FileServiceBasedConfiguration`. The exact purpose of this was not explored.
254+
255+
### `Configuration` Class
256+
257+
- The renderer process uses the same `Configuration` class as the main process, so there’s no need for repetition here.
258+
259+
### `ConfigurationEditing` Class - How Configurations Are Edited
260+
261+
- `WorkspaceService` enables updating configurations using `updateValue`, which relies on the key-value pair mechanism. The core function is `writeConfigurationValue`.
262+
- Does not support modifying default configurations.
263+
- Internally, `writeConfigurationValue` initializes a `ConfigurationEditing` instance and calls its API.
264+
- `ConfigurationEditing` exposes a single API called `writeConfiguration`.

0 commit comments

Comments
 (0)