terminal/src/cascadia/TerminalSettingsModel/IInheritable.h
Leonard Hecker 168d28b036
Reduce usage of Json::Value throughout Terminal.Settings.Model (#11184)
This commit reduces the code surface that interacts with raw JSON data,
reducing code complexity and improving maintainability.
Files that needed to be changed drastically were additionally
cleaned up to remove any code cruft that has accrued over time.

In order to facility this the following changes were made:
* Move JSON handling from `CascadiaSettings` into `SettingsLoader`
  This allows us to use STL containers for data model instances.
  For instance profiles are now added to a hashmap for O(1) lookup.
* JSON parsing within `SettingsLoader` doesn't differentiate between user,
  inbox and fragment JSON data, reducing code complexity and size.
  It also centralizes common concerns, like profile deduplication and
  ensuring that all profiles are assigned a GUID.
* Direct JSON modification, like the insertion of dynamic profiles into
  settings.json were removed. This vastly reduces code complexity,
  but unfortunately removes support for comments in JSON on first start.
* `ColorScheme`s cannot be layered. As such its `LayerJson` API was replaced
  with `FromJson`, allowing us to remove JSON-based color scheme validation.
* `Profile`s used to test their wish to layer using `ShouldBeLayered`, which
  was replaced with a GUID-based hashmap lookup on previously parsed profiles.

Further changes were made as improvements upon the previous changes:
* Compact the JSON files embedded binary, saving 28kB
* Prevent double-initialization of the color table in `ColorScheme`
* Making `til::color` getters `constexpr`, allow better optimizations

The result is a reduction of:
* 48kB binary size for the Settings.Model.dll
* 5-10% startup duration
* 26% code for the `CascadiaSettings` class
* 1% overall code in this project

Furthermore this results in the following breaking changes:
* The long deprecated "globals" settings object will not be detected and no
  warning will be created during load.
* The initial creation of a new settings.json will not produce helpful comments.

Both cases are caused by the removal of manual JSON handling and the
move to representing the settings file with model objects instead

## PR Checklist
* [x] Closes #5276
* [x] Closes #7421
* [x] I work here
* [x] Tests added/passed

## Validation Steps Performed

* Out-of-box-experience is identical to before ✔️
  (Except for the settings.json file lacking comments.)
* Existing user settings load correctly ✔️
* New WSL instances are added to user settings ✔️
* New fragments are added to user settings ✔️
* All profiles are assigned GUIDs ✔️
2021-09-22 16:27:31 +00:00

289 lines
18 KiB
C++

/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- IInheritable.h
Abstract:
- An interface allowing settings objects to inherit settings from a parent
Author(s):
- Carlos Zamora - October 2020
--*/
#pragma once
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
template<typename T>
struct IInheritable
{
public:
// Method Description:
// - Create a new instance of T, but set its parent to this instance
// Arguments:
// - <none>
// Return Value:
// - a new instance of T with this instance set as its parent
com_ptr<T> CreateChild() const
{
auto child{ winrt::make_self<T>() };
// set "this" as the parent.
// However, "this" is an IInheritable, so we need to cast it as T (the impl winrt type)
// to pass ownership over to the com_ptr.
com_ptr<T> parent;
winrt::copy_from_abi(parent, const_cast<T*>(static_cast<const T*>(this)));
child->InsertParent(parent);
child->_FinalizeInheritance();
return child;
}
void ClearParents()
{
_parents.clear();
}
void InsertParent(com_ptr<T> parent)
{
_parents.emplace_back(std::move(parent));
}
void InsertParent(size_t index, com_ptr<T> parent)
{
auto pos{ _parents.begin() + index };
_parents.emplace(pos, std::move(parent));
}
const std::vector<com_ptr<T>>& Parents()
{
return _parents;
}
protected:
std::vector<com_ptr<T>> _parents{};
// Method Description:
// - Actions to be performed after a child was created. Generally used to set
// any extraneous data from the parent into the child.
// Arguments:
// - <none>
// Return Value:
// - <none>
virtual void _FinalizeInheritance() {}
};
// This is like std::optional, but we can use it in inheritance to determine whether the user explicitly cleared it
template<typename T>
using NullableSetting = std::optional<std::optional<T>>;
}
// Use this macro to quickly implement both getters and the setter for an
// inheritable setting property. This is similar to the WINRT_PROPERTY macro, except...
// - Has(): checks if the user explicitly set a value for this setting
// - SourceGetter(): return the object that provides the resolved value
// - Getter(): return the resolved value
// - Setter(): set the value directly
// - Clear(): clear the user set value
// - the setting is saved as an optional, where nullopt means
// that we must inherit the value from our parent
#define INHERITABLE_SETTING(projectedType, type, name, ...) \
public: \
/* Returns true if the user explicitly set the value, false otherwise*/ \
bool Has##name() const \
{ \
return _##name.has_value(); \
} \
\
projectedType name##OverrideSource() \
{ \
/*user set value was not set*/ \
/*iterate through parents to find one with a value*/ \
for (auto& parent : _parents) \
{ \
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
{ \
return source; \
} \
} \
\
/*no value was found*/ \
return nullptr; \
} \
\
/* Returns the resolved value for this setting */ \
/* fallback: user set value --> inherited value --> system set value */ \
type name() const \
{ \
const auto val{ _get##name##Impl() }; \
return val ? *val : type{ __VA_ARGS__ }; \
} \
\
/* Overwrite the user set value */ \
void name(const type& value) \
{ \
_##name = value; \
} \
\
/* Clear the user set value */ \
void Clear##name() \
{ \
_##name = std::nullopt; \
} \
\
private: \
std::optional<type> _##name{ std::nullopt }; \
std::optional<type> _get##name##Impl() const \
{ \
/*return user set value*/ \
if (_##name) \
{ \
return _##name; \
} \
\
/*user set value was not set*/ \
/*iterate through parents to find a value*/ \
for (const auto& parent : _parents) \
{ \
if (auto val{ parent->_get##name##Impl() }) \
{ \
return val; \
} \
} \
\
/*no value was found*/ \
return std::nullopt; \
} \
projectedType _get##name##OverrideSourceImpl() const \
{ \
/*we have a value*/ \
if (_##name) \
{ \
return *this; \
} \
\
/*user set value was not set*/ \
/*iterate through parents to find one with a value*/ \
for (const auto& parent : _parents) \
{ \
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
{ \
return source; \
} \
} \
\
/*no value was found*/ \
return nullptr; \
}
// This macro is similar to the one above, but is reserved for optional settings
// like Profile.Foreground (where null is interpreted
// as an acceptable value, rather than "inherit")
// "type" is exposed as an IReference
#define INHERITABLE_NULLABLE_SETTING(projectedType, type, name, ...) \
public: \
/* Returns true if the user explicitly set the value, false otherwise*/ \
bool Has##name() const \
{ \
return _##name.has_value(); \
} \
\
projectedType name##OverrideSource() \
{ \
/*user set value was not set*/ \
/*iterate through parents to find one with a value*/ \
for (const auto& parent : _parents) \
{ \
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
{ \
return source; \
} \
} \
\
/*no source was found*/ \
return nullptr; \
} \
\
/* Returns the resolved value for this setting */ \
/* fallback: user set value --> inherited value --> system set value */ \
winrt::Windows::Foundation::IReference<type> name() const \
{ \
const auto val{ _get##name##Impl() }; \
if (val) \
{ \
if (*val) \
{ \
return **val; \
} \
return nullptr; \
} \
return winrt::Windows::Foundation::IReference<type>{ __VA_ARGS__ }; \
} \
\
/* Overwrite the user set value */ \
void name(const winrt::Windows::Foundation::IReference<type>& value) \
{ \
if (value) /*set value is different*/ \
{ \
_##name = std::optional<type>{ value.Value() }; \
} \
else \
{ \
/* note we're setting the _inner_ value */ \
_##name = std::optional<type>{ std::nullopt }; \
} \
} \
\
/* Clear the user set value */ \
void Clear##name() \
{ \
_##name = std::nullopt; \
} \
\
private: \
NullableSetting<type> _##name{}; \
NullableSetting<type> _get##name##Impl() const \
{ \
/*return user set value*/ \
if (_##name) \
{ \
return _##name; \
} \
\
/*user set value was not set*/ \
/*iterate through parents to find a value*/ \
for (const auto& parent : _parents) \
{ \
if (auto val{ parent->_get##name##Impl() }) \
{ \
return val; \
} \
} \
\
/*no value was found*/ \
return std::nullopt; \
} \
projectedType _get##name##OverrideSourceImpl() const \
{ \
/*we have a value*/ \
if (_##name) \
{ \
return *this; \
} \
\
/*user set value was not set*/ \
/*iterate through parents to find one with a value*/ \
for (const auto& parent : _parents) \
{ \
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
{ \
return source; \
} \
} \
\
/*no value was found*/ \
return nullptr; \
}