Skip to content

Commit 71c2b5f

Browse files
Fixes #4928. Update stale config.md deep-dive documentation (#4929)
1 parent 10cf766 commit 71c2b5f

1 file changed

Lines changed: 149 additions & 48 deletions

File tree

docfx/docs/config.md

Lines changed: 149 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public static bool Force16Colors { get; set; } = false;
9797

9898
**Examples:**
9999
- `Application.DefaultKeyBindings` (e.g. `Command.Quit`) - Default keys for application-level commands
100-
- `Application.Force16Colors` - Force 16-color mode
100+
- `Driver.Force16Colors` - Force 16-color mode
101101
- `Key.Separator` - Character separating keys in key combinations
102102

103103
### 2. ThemeScope
@@ -227,8 +227,12 @@ A **Theme** is a named collection of visual settings bundled together. Terminal.
227227
// Get current theme
228228
ThemeScope currentTheme = ThemeManager.GetCurrentTheme();
229229

230-
// Get all available themes
231-
Dictionary<string, ThemeScope> themes = ThemeManager.GetThemes();
230+
// Get all available themes (null if ConfigurationManager not yet initialized)
231+
ConcurrentDictionary<string, ThemeScope>? themes = ThemeManager.Themes;
232+
if (themes is null)
233+
{
234+
return; // ConfigurationManager not yet initialized
235+
}
232236

233237
// Get theme names
234238
ImmutableList<string> themeNames = ThemeManager.GetThemeNames();
@@ -240,6 +244,7 @@ ConfigurationManager.Apply();
240244
// Listen for theme changes
241245
ThemeManager.ThemeChanged += (sender, e) =>
242246
{
247+
// e.Value is the new theme name
243248
// Update UI based on new theme
244249
};
245250
```
@@ -264,7 +269,7 @@ See the [Scheme Deep Dive](scheme.md) for complete details on the scheme system.
264269

265270
```csharp
266271
// Get all schemes for current theme
267-
Dictionary<string, Scheme> schemes = SchemeManager.GetCurrentSchemes();
272+
Dictionary<string, Scheme?> schemes = SchemeManager.GetSchemesForCurrentTheme();
268273

269274
// Get specific scheme
270275
Scheme dialogScheme = SchemeManager.GetScheme(Schemes.Dialog);
@@ -278,12 +283,84 @@ SchemeManager.AddScheme("MyScheme", new Scheme
278283
Normal = new Attribute(Color.White, Color.Blue),
279284
Focus = new Attribute(Color.Black, Color.Cyan)
280285
});
286+
```
287+
288+
#### Custom Schemes for Individual Views
289+
290+
Any view can be given a named scheme by setting `View.SchemeName`. When set, `GetScheme()` looks up that name in the active theme and uses it if found. If the name is not found in the current theme, it falls back through the normal resolution chain (SuperView → `"Base"` → hard-coded `"Base"`) rather than throwing.
281291

282-
// Listen for scheme changes
283-
SchemeManager.CollectionChanged += (sender, e) =>
292+
```csharp
293+
// 1. Register the custom scheme (call before Application.Init or after ConfigurationManager.Apply)
294+
SchemeManager.AddScheme("Highlight", new Scheme
284295
{
285-
// Handle scheme changes
286-
};
296+
Normal = new Attribute(Color.Black, Color.BrightYellow),
297+
Focus = new Attribute(Color.White, Color.BrightYellow)
298+
});
299+
300+
// 2. Assign the scheme name to any view
301+
Label warningLabel = new () { Text = "Warning!" };
302+
warningLabel.SchemeName = "Highlight";
303+
```
304+
305+
Custom schemes can also be defined in a config JSON file so they are theme-aware:
306+
307+
```json
308+
{
309+
"Themes": [
310+
{
311+
"Default": {
312+
"Schemes": [
313+
{
314+
"Highlight": {
315+
"Normal": { "Foreground": "Black", "Background": "BrightYellow" },
316+
"Focus": { "Foreground": "White", "Background": "BrightYellow" }
317+
}
318+
}
319+
]
320+
}
321+
}
322+
]
323+
}
324+
```
325+
326+
When the active theme changes, any view with `SchemeName = "Highlight"` automatically picks up the new theme's definition of that scheme.
327+
328+
#### Scheme Resolution Order
329+
330+
`View.GetScheme()` resolves the scheme for a view using the following priority order:
331+
332+
```mermaid
333+
flowchart TD
334+
A([GetScheme called]) --> B{HasScheme?\nexplicit Scheme set}
335+
B -- Yes --> C([Return explicit Scheme])
336+
B -- No --> D{SchemeName set?}
337+
D -- No --> E{SuperView exists?}
338+
D -- Yes --> F{TryGetScheme\nSchemeName found?}
339+
F -- Yes --> G([Return named Scheme])
340+
F -- No --> H[/Logging.Warning emitted/]
341+
H --> E
342+
E -- Yes --> I([Return SuperView.GetScheme])
343+
E -- No --> J{TryGetScheme\n'Base' found?}
344+
J -- Yes --> K([Return 'Base' from active theme])
345+
J -- No --> L([Return hard-coded 'Base'])
346+
```
347+
348+
| Priority | Condition | Result |
349+
|----------|-----------|--------|
350+
| 1 | `HasScheme` is true | Explicit `Scheme` instance used as-is |
351+
| 2 | `SchemeName` is set and found in active theme | Named scheme returned |
352+
| 3 | `SchemeName` is set but **not** found | `Logging.Warning` emitted; fallback continues |
353+
| 4 | `SuperView` exists | `SuperView.GetScheme()` (recursive) |
354+
| 5 | `"Base"` exists in active theme | Active theme's `"Base"` scheme |
355+
| 6 | *(last resort)* | Hard-coded `"Base"` — always available |
356+
357+
When writing code that looks up a scheme by name, prefer `SchemeManager.TryGetScheme()` over `SchemeManager.GetScheme(string)` — the `Try` variant returns `false` instead of throwing `KeyNotFoundException` when the name is not found.
358+
359+
```csharp
360+
if (SchemeManager.TryGetScheme("Highlight", out Scheme? scheme))
361+
{
362+
// use scheme
363+
}
287364
```
288365

289366
#### Scheme Structure
@@ -312,6 +389,26 @@ Each [Scheme](~/api/Terminal.Gui.Drawing.Scheme.yml) maps [VisualRole](~/api/Ter
312389
"Background": "Cyan",
313390
"Style": "Underline"
314391
},
392+
"Active": {
393+
"Foreground": "White",
394+
"Background": "DarkCyan"
395+
},
396+
"HotActive": {
397+
"Foreground": "Yellow",
398+
"Background": "DarkCyan"
399+
},
400+
"Highlight": {
401+
"Foreground": "Black",
402+
"Background": "BrightGreen"
403+
},
404+
"Editable": {
405+
"Foreground": "White",
406+
"Background": "DarkBlue"
407+
},
408+
"ReadOnly": {
409+
"Foreground": "Gray",
410+
"Background": "Black"
411+
},
315412
"Disabled": {
316413
"Foreground": "DarkGray",
317414
"Background": "Black",
@@ -458,35 +555,36 @@ The ConfigurationManager provides events to track configuration changes:
458555
Raised after configuration is applied to the application:
459556

460557
```csharp
461-
ConfigurationManager.Applied += (sender, e) =>
558+
ConfigurationManager.Applied += (_, _) =>
462559
{
463560
// Configuration has been applied
464561
// Update UI or refresh views
465562
};
466563
```
467564

468-
### ThemeChanged Event
565+
### Updated Event
469566

470-
Raised when the active theme changes:
567+
Raised after configuration is loaded from a source or reset (before `Apply()` is called):
471568

472569
```csharp
473-
ThemeManager.ThemeChanged += (sender, e) =>
570+
ConfigurationManager.Updated += (_, _) =>
474571
{
475-
// Theme has changed
476-
// Refresh all views to use new theme
477-
// From within a View, use: App?.Current?.SetNeedsDraw();
478-
// Or access via IApplication instance: app.Current?.SetNeedsDraw();
572+
// Configuration has been loaded or reset
573+
// Inspect ConfigurationManager.Settings if needed
479574
};
480575
```
481576

482-
### CollectionChanged Event
577+
### ThemeChanged Event
483578

484-
Raised when schemes collection changes:
579+
Raised when the active theme changes:
485580

486581
```csharp
487-
SchemeManager.CollectionChanged += (sender, e) =>
582+
ThemeManager.ThemeChanged += (_, e) =>
488583
{
489-
// Schemes have changed
584+
// e.Value is the new theme name
585+
// Refresh all views to use new theme
586+
// From within a View, use: App?.Current?.SetNeedsDraw();
587+
// Or access via IApplication instance: app.Current?.SetNeedsDraw();
490588
};
491589
```
492590

@@ -501,7 +599,7 @@ System-wide settings from [SettingsScope](~/api/Terminal.Gui.Configuration.Setti
501599
```json
502600
{
503601
"Application.DefaultKeyBindings.Quit": "Esc",
504-
"Application.Force16Colors": false,
602+
"Driver.Force16Colors": false,
505603
"Application.IsMouseDisabled": false,
506604
"Application.DefaultKeyBindings.Arrange": "Ctrl+F5",
507605
"Application.DefaultKeyBindings.NextTabStop": "Tab",
@@ -628,14 +726,12 @@ Each entry uses the `PlatformKeyBinding` format with optional `All`, `Windows`,
628726
To find all available configuration properties:
629727

630728
```csharp
631-
// Get hard-coded configuration
632-
SettingsScope hardCoded = ConfigurationManager.GetHardCodedConfig();
729+
// Get hard-coded configuration as a JSON string
730+
string hardCodedJson = ConfigurationManager.GetHardCodedConfig();
731+
Console.WriteLine(hardCodedJson);
633732

634-
// Iterate through all properties
635-
foreach (var property in hardCoded)
636-
{
637-
Console.WriteLine($"{property.Key} = {property.Value}");
638-
}
733+
// Or get an empty configuration skeleton
734+
string emptyJson = ConfigurationManager.GetEmptyConfig();
639735
```
640736

641737
Or search the source code for `[ConfigurationProperty]` attributes.
@@ -892,20 +988,17 @@ Control how JSON parsing errors are handled:
892988
- `false` (default) - Silent failures, errors logged
893989
- `true` - Throws exceptions on JSON parsing errors
894990

895-
### Manually Trigger Updates
991+
### Get Configuration as JSON
896992

897-
Update ConfigurationManager to reflect current static property values:
993+
Retrieve the current hard-coded defaults as a JSON string:
898994

899995
```csharp
900-
// Change a setting programmatically
901-
Application.DefaultKeyBindings[Command.Quit] = Bind.All (Key.Q.WithCtrl);
902-
903-
// Update ConfigurationManager to reflect the change
904-
ConfigurationManager.UpdateToCurrentValues();
905-
906-
// Save to file (if needed)
907-
string json = ConfigurationManager.Serialize();
996+
// Get the hard-coded configuration as JSON
997+
string json = ConfigurationManager.GetHardCodedConfig();
908998
File.WriteAllText("my-config.json", json);
999+
1000+
// Get an empty configuration skeleton (just the $schema tag)
1001+
string empty = ConfigurationManager.GetEmptyConfig();
9091002
```
9101003

9111004
### Disable ConfigurationManager
@@ -949,22 +1042,31 @@ using Terminal.Gui;
9491042
using Terminal.Gui.Configuration;
9501043

9511044
ConfigurationManager.Enable(ConfigLocations.All);
952-
Application.Init();
9531045

954-
var themeSelector = new OptionSelector<string>
1046+
using IApplication app = Application.Create();
1047+
app.Init();
1048+
1049+
OptionSelector themeSelector = new ()
9551050
{
9561051
X = 1,
9571052
Y = 1,
1053+
Labels = ThemeManager.GetThemeNames()
9581054
};
959-
themeSelector.SetSource(ThemeManager.GetThemeNames());
960-
themeSelector.SelectedItemChanged += (s, e) =>
1055+
themeSelector.ValueChanged += (_, e) =>
9611056
{
962-
ThemeManager.Theme = e.Value.ToString();
1057+
IReadOnlyList<string>? labels = themeSelector.Labels;
1058+
if (labels is null || e.NewValue is null)
1059+
{
1060+
return;
1061+
}
1062+
1063+
ThemeManager.Theme = labels[e.NewValue.Value];
9631064
ConfigurationManager.Apply();
9641065
};
9651066

966-
Application.Run(new Window { Title = "Theme Demo" }).Add(themeSelector);
967-
Application.Shutdown();
1067+
Window win = new () { Title = "Theme Demo" };
1068+
win.Add(themeSelector);
1069+
app.Run(win);
9681070
```
9691071

9701072
### Example 2: Custom Application Settings
@@ -992,9 +1094,8 @@ var window = new Window
9921094
Height = MyApp.WindowHeight
9931095
};
9941096

995-
// Later, save updated settings
996-
MyApp.WindowWidth = 100;
997-
ConfigurationManager.UpdateToCurrentValues();
1097+
// Later, retrieve the hard-coded config as JSON
1098+
string json = ConfigurationManager.GetHardCodedConfig();
9981099
// Could save to file here
9991100
```
10001101

0 commit comments

Comments
 (0)