Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: Introduce Inheritance Addendum to TSM Spec #7876

Merged
merged 8 commits into from
Oct 26, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,126 @@ This separation leaves `AppKeyBindings` with the responsibility of detecting and
`KeyMapping` handles the (de)serialization and navigation of the key bindings.


### Fallback Value

Cascading settings allows our settings model to be constructed in layers (i.e. settings.json values override defaults.json values). With the upcoming introduction of the Settings UI and serialization, it is important to know where a setting value comes from. Consider a Settings UI displaying the following information:
```json
// <profile>: <color scheme value>
"defaults": "Solarized", // profiles.defaults
"A": "Raspberry", // profile A
"B": "Tango", // profile B
"C": "Solarized" // profile C
```
If `profiles.defaults` gets changed to `"Tango"` via the Settings UI, it is unclear if profile C's value should be updated as well. We need profile C to record if it's value is inherited from profile.defaults or explicitly set by the user.

#### Object Model Inheritance

To start, each settings object will now have a `CreateChild()` function. For `GlobalAppSettings`, it will look something like this:
```c++
GlobalAppSettings GlobalAppSettings::CreateChild() const
{
GlobalAppSettings child {};
child._parent = this;
return child;
}
```
`_parent` serves as a reference for who to ask if a settings value was not provided by the user. `LaunchMode`, for example, will now have a getter/setter that looks similar to this:
```c++
// returns the resolved value for this setting
LaunchMode GlobalAppSettings::LaunchMode()
{
// fallback tree:
// - user set value
// - inherited value
// - system set value
return til::coalesce_value(_LaunchMode, _parent.LaunchMode(), LaunchMode::DefaultMode);
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
}
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved

void GlobalAppSettings::LaunchMode(IReference<LaunchMode> val)
{
// if val is nullptr, we are explicitly saying that we want the inherited value
_LaunchMode = val;
}

// returns the user set value for this setting
// NOTE: This is important for the Settings UI to identify whether the user explicitly or implicitly set the presented value
LaunchMode GlobalAppSettings::ExplicitLaunchMode()
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
{
return _LaunchMode;
}
```

Additionally, each settings object will now have an `ApplyTo()` function. For `Profile`, it will look something like this:
```c++
// layers the Profile settings onto another profile
void Profile::ApplyTo(Profile profile) const
{
if (_fontSize)
{
FontSize(_fontSize);
}

// repeat for all settings
}
```
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved

This functionality will be useful when layering the `profile.defaults` onto dynamic profiles.
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved

As a result, the tracking and functionality of cascading settings is moved into the object model instead of keeping it as a json-only concept.

#### Updates to CascadiaSettings

As `CascadiaSettings` loads the settings model, it will create children for each component of the settings model and layer the new values on top of it. Thus, `LayerJson` will look something like this:
```c++
void CascadiaSettings::LayerJson(const Json::Value& json)
{
_globals = _globals.CreateInheritedProfile();
_globals->LayerJson(json);

// repeat the same for Profiles/ColorSchemes...
}
```
For `defaults.json`, `_globals` will now hold all of the values set in `defaults.json`. If any settings were omitted from the `defaults.json`, `_globals` will fallback to its parent (a `GlobalAppSettings` consisting purely of system-defined values).

For `settings.json`, `_globals` will only hold the values set in `settings.json`. If any settings were omitted from `settings.json`, `_globals` will fallback to its parent (the `GlobalAppSettings` built from `defaults.json).
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved

This process becomes a bit more complex for `Profile` because it can fallback in the following order:
1. `settings.json` profile
2. `settings.json` `profiles.defaults`
3. (if a dynamic profile) the hardcoded value in the dynamic profile generator
4. `defaults.json` profile

`CascadiaSettings` must do the following...
1. load `defaults.json`
- append newly created profiles to `_profiles` (unchanged)
2. load dynamic profiles
- append newly created profiles to `_profiles` (unchanged)
3. load `settings.json` `profiles.defaults`
- layer `profile.defaults` onto `_profiles` (unchanged)
- construct a `Profile` from `profiles.defaults`. Save as `Profile _profileDefaults`.
4. load `settings.json` `profiles.list`
- if a matching profile exists, `CreateInheritedProfile` from the matching profile, and layer the json onto the child.
- otherwise, `CreateInheritedProfile` from `_profileDefaults`, and layer the json onto the clone.

Additionally, `_profileDefaults` will be exposed by `Profile CascadiaSettings::ProfileDefaults()`. This will enable [#7414](https://github.com/microsoft/terminal/pull/7414)'s implementation to spawn incoming commandline app tabs with the "Default" profile (as opposed to the "default profile").


### CreateChild vs Copy

Settings objects will have `CreateChild()` and `Copy()`. `CreateChild()` is responsible for creating a new settings object that inherits undefined values from its parent. `Copy()` is responsible for recreating the contents of the settings object, including a reference to a copied parent.
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved

The Settings UI will use `Copy()` to get a full copy of `CascadiaSettings` and data bind the UI to that copy. Thus, `Copy()` needs to be exposed in the IDL.

`CreateChild()` will only be used during (de)serialization to adequately interpret and update the JSON. `CreateChild()` enables, but is not explicitly used, for retrieving a value from a settings object. It can also be used to enable larger hierarchies for inheritance within the settings model.


### Terminal Settings Model: Serialization and Deserialization

Introducing these `Microsoft.Terminal.Settings.Model` WinRT objects also allow the serialization and deserialization
logic from TerminalApp to be moved to TerminalSettings. `JsonUtils` introduces several quick and easy methods
for setting serialization. This will be moved into the `Microsoft.Terminal.Settings.Model` namespace too.
for setting deserialization. This will be moved into the `Microsoft.Terminal.Settings.Model` namespace too.

Deserialization will be an extension of the existing `JsonUtils` `ConversionTrait` struct template. `ConversionTrait`
Serialization will be an extension of the existing `JsonUtils` `ConversionTrait` struct template. `ConversionTrait`
already includes `FromJson` and `CanConvert`. Serialization would be handled by a `ToJson` function.


Expand Down