mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-24 23:22:27 +00:00
Compare commits
4 Commits
dev/migrie
...
dev/cazamo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dc9bb1558 | ||
|
|
45e93cac42 | ||
|
|
9bf994dd20 | ||
|
|
a7380638a8 |
@@ -31,19 +31,18 @@ AppearanceConfig::AppearanceConfig(winrt::weak_ref<Model::Profile> sourceProfile
|
||||
winrt::com_ptr<AppearanceConfig> AppearanceConfig::CopyAppearance(const AppearanceConfig* source, winrt::weak_ref<Model::Profile> sourceProfile)
|
||||
{
|
||||
auto appearance{ winrt::make_self<AppearanceConfig>(std::move(sourceProfile)) };
|
||||
appearance->_Foreground = source->_Foreground;
|
||||
appearance->_Background = source->_Background;
|
||||
appearance->_SelectionBackground = source->_SelectionBackground;
|
||||
appearance->_CursorColor = source->_CursorColor;
|
||||
appearance->_Opacity = source->_Opacity;
|
||||
|
||||
appearance->_DarkColorSchemeName = source->_DarkColorSchemeName;
|
||||
appearance->_LightColorSchemeName = source->_LightColorSchemeName;
|
||||
appearance->_json = source->_json;
|
||||
|
||||
#define APPEARANCE_SETTINGS_COPY(type, name, jsonKey, ...) \
|
||||
appearance->_##name = source->_##name;
|
||||
MTSM_APPEARANCE_SETTINGS(APPEARANCE_SETTINGS_COPY)
|
||||
#undef APPEARANCE_SETTINGS_COPY
|
||||
// JSON-backed settings (Foreground, Background, SelectionBackground, CursorColor,
|
||||
// Opacity, DarkColorSchemeName, LightColorSchemeName, MTSM settings) all live in
|
||||
// _json, which is already deep-copied above.
|
||||
|
||||
// Complex/mutable settings — backing fields for resolution lifecycle.
|
||||
// _json (copied above) is the source of truth; backing fields hold resolved runtime state.
|
||||
appearance->_PixelShaderPath = source->_PixelShaderPath;
|
||||
appearance->_PixelShaderImagePath = source->_PixelShaderImagePath;
|
||||
appearance->_BackgroundImagePath = source->_BackgroundImagePath;
|
||||
|
||||
return appearance;
|
||||
}
|
||||
@@ -52,33 +51,126 @@ Json::Value AppearanceConfig::ToJson() const
|
||||
{
|
||||
Json::Value json{ Json::ValueType::objectValue };
|
||||
|
||||
JsonUtils::SetValueForKey(json, ForegroundKey, _Foreground);
|
||||
JsonUtils::SetValueForKey(json, BackgroundKey, _Background);
|
||||
JsonUtils::SetValueForKey(json, SelectionBackgroundKey, _SelectionBackground);
|
||||
JsonUtils::SetValueForKey(json, CursorColorKey, _CursorColor);
|
||||
JsonUtils::SetValueForKey(json, OpacityKey, _Opacity, JsonUtils::OptionalConverter<float, IntAsFloatPercentConversionTrait>{});
|
||||
if (HasDarkColorSchemeName() || HasLightColorSchemeName())
|
||||
// Nullable color settings: key presence matters (explicit null is valid)
|
||||
for (const auto& key : { ForegroundKey, BackgroundKey, SelectionBackgroundKey, CursorColorKey })
|
||||
{
|
||||
// check if the setting is coming from the UI, if so grab the ColorSchemeName until the settings UI is fixed.
|
||||
if (_LightColorSchemeName != _DarkColorSchemeName)
|
||||
{
|
||||
JsonUtils::SetValueForKey(json["colorScheme"], "dark", _DarkColorSchemeName);
|
||||
JsonUtils::SetValueForKey(json["colorScheme"], "light", _LightColorSchemeName);
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonUtils::SetValueForKey(json, "colorScheme", _DarkColorSchemeName);
|
||||
}
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, key);
|
||||
}
|
||||
|
||||
// Opacity: copy from _json (may be int or float — preserves original form)
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, OpacityKey);
|
||||
|
||||
// ColorScheme: ConversionTrait<ColorSchemeReference> handles string<->object form.
|
||||
// _json already has the correct serialized form from SetValueForKey.
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, ColorSchemeKey);
|
||||
|
||||
// MTSM appearance settings: copy from _json (the source of truth)
|
||||
#define APPEARANCE_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::SetValueForKey(json, jsonKey, _##name);
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, jsonKey);
|
||||
MTSM_APPEARANCE_SETTINGS(APPEARANCE_SETTINGS_TO_JSON)
|
||||
#undef APPEARANCE_SETTINGS_TO_JSON
|
||||
|
||||
// Complex/mutable settings — read from _json (source of truth), not backing fields
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, "experimental.pixelShaderPath");
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, "experimental.pixelShaderImagePath");
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, "backgroundImage");
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
bool AppearanceConfig::HasSetting(AppearanceSettingKey key) const
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _APPEARANCE_HAS_SETTING(type, name, jsonKey, ...) \
|
||||
case AppearanceSettingKey::name: \
|
||||
return Has##name();
|
||||
MTSM_APPEARANCE_SETTINGS(_APPEARANCE_HAS_SETTING)
|
||||
#undef _APPEARANCE_HAS_SETTING
|
||||
case AppearanceSettingKey::_Foreground:
|
||||
return HasForeground();
|
||||
case AppearanceSettingKey::_Background:
|
||||
return HasBackground();
|
||||
case AppearanceSettingKey::_SelectionBackground:
|
||||
return HasSelectionBackground();
|
||||
case AppearanceSettingKey::_CursorColor:
|
||||
return HasCursorColor();
|
||||
case AppearanceSettingKey::_Opacity:
|
||||
return HasOpacity();
|
||||
case AppearanceSettingKey::_DarkColorSchemeName:
|
||||
return HasDarkColorSchemeName();
|
||||
case AppearanceSettingKey::_LightColorSchemeName:
|
||||
return HasLightColorSchemeName();
|
||||
case AppearanceSettingKey::_PixelShaderPath:
|
||||
return HasPixelShaderPath();
|
||||
case AppearanceSettingKey::_PixelShaderImagePath:
|
||||
return HasPixelShaderImagePath();
|
||||
case AppearanceSettingKey::_BackgroundImagePath:
|
||||
return HasBackgroundImagePath();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void AppearanceConfig::ClearSetting(AppearanceSettingKey key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _APPEARANCE_CLEAR_SETTING(type, name, jsonKey, ...) \
|
||||
case AppearanceSettingKey::name: \
|
||||
Clear##name(); \
|
||||
break;
|
||||
MTSM_APPEARANCE_SETTINGS(_APPEARANCE_CLEAR_SETTING)
|
||||
#undef _APPEARANCE_CLEAR_SETTING
|
||||
case AppearanceSettingKey::_Foreground:
|
||||
ClearForeground();
|
||||
break;
|
||||
case AppearanceSettingKey::_Background:
|
||||
ClearBackground();
|
||||
break;
|
||||
case AppearanceSettingKey::_SelectionBackground:
|
||||
ClearSelectionBackground();
|
||||
break;
|
||||
case AppearanceSettingKey::_CursorColor:
|
||||
ClearCursorColor();
|
||||
break;
|
||||
case AppearanceSettingKey::_Opacity:
|
||||
ClearOpacity();
|
||||
break;
|
||||
case AppearanceSettingKey::_DarkColorSchemeName:
|
||||
ClearDarkColorSchemeName();
|
||||
break;
|
||||
case AppearanceSettingKey::_LightColorSchemeName:
|
||||
ClearLightColorSchemeName();
|
||||
break;
|
||||
case AppearanceSettingKey::_PixelShaderPath:
|
||||
ClearPixelShaderPath();
|
||||
break;
|
||||
case AppearanceSettingKey::_PixelShaderImagePath:
|
||||
ClearPixelShaderImagePath();
|
||||
break;
|
||||
case AppearanceSettingKey::_BackgroundImagePath:
|
||||
ClearBackgroundImagePath();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<AppearanceSettingKey> AppearanceConfig::CurrentSettings() const
|
||||
{
|
||||
std::vector<AppearanceSettingKey> result;
|
||||
for (auto i = 0; i < static_cast<int>(AppearanceSettingKey::SETTINGS_SIZE); i++)
|
||||
{
|
||||
const auto key = static_cast<AppearanceSettingKey>(i);
|
||||
if (HasSetting(key))
|
||||
{
|
||||
result.push_back(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Layer values from the given json object on top of the existing properties
|
||||
// of this object. For any keys we're expecting to be able to parse in the
|
||||
@@ -92,45 +184,50 @@ Json::Value AppearanceConfig::ToJson() const
|
||||
// - json: an object which should be a partial serialization of an AppearanceConfig object.
|
||||
void AppearanceConfig::LayerJson(const Json::Value& json)
|
||||
{
|
||||
JsonUtils::GetValueForKey(json, ForegroundKey, _Foreground);
|
||||
_logSettingIfSet(ForegroundKey, _Foreground.has_value());
|
||||
// Merge incoming JSON keys into stored _json (key-wise, not replacement).
|
||||
// AppearanceConfig receives the full profile JSON; we store all keys and
|
||||
// read only appearance-relevant ones from it.
|
||||
JsonUtils::MergeJsonKeys(json, _json);
|
||||
|
||||
JsonUtils::GetValueForKey(json, BackgroundKey, _Background);
|
||||
_logSettingIfSet(BackgroundKey, _Background.has_value());
|
||||
// Nullable color settings are now JSON-backed. Log which were set.
|
||||
_logSettingIfSet(ForegroundKey, HasForeground());
|
||||
_logSettingIfSet(BackgroundKey, HasBackground());
|
||||
_logSettingIfSet(SelectionBackgroundKey, HasSelectionBackground());
|
||||
_logSettingIfSet(CursorColorKey, HasCursorColor());
|
||||
|
||||
JsonUtils::GetValueForKey(json, SelectionBackgroundKey, _SelectionBackground);
|
||||
_logSettingIfSet(SelectionBackgroundKey, _SelectionBackground.has_value());
|
||||
|
||||
JsonUtils::GetValueForKey(json, CursorColorKey, _CursorColor);
|
||||
_logSettingIfSet(CursorColorKey, _CursorColor.has_value());
|
||||
|
||||
JsonUtils::GetValueForKey(json, LegacyAcrylicTransparencyKey, _Opacity);
|
||||
JsonUtils::GetValueForKey(json, OpacityKey, _Opacity, JsonUtils::OptionalConverter<float, IntAsFloatPercentConversionTrait>{});
|
||||
_logSettingIfSet(OpacityKey, _Opacity.has_value());
|
||||
|
||||
if (json["colorScheme"].isString())
|
||||
// Normalize legacy opacity key into canonical _json key
|
||||
if (json.isMember(JsonKey(LegacyAcrylicTransparencyKey)))
|
||||
{
|
||||
// to make the UI happy, set ColorSchemeName.
|
||||
JsonUtils::GetValueForKey(json, ColorSchemeKey, _DarkColorSchemeName);
|
||||
_LightColorSchemeName = _DarkColorSchemeName;
|
||||
_logSettingSet(ColorSchemeKey);
|
||||
_json[JsonKey(OpacityKey)] = json[JsonKey(LegacyAcrylicTransparencyKey)];
|
||||
}
|
||||
else if (json["colorScheme"].isObject())
|
||||
// Normalize integer percent to float (e.g. 50 → 0.5)
|
||||
if (_json.isMember(JsonKey(OpacityKey)) && _json[JsonKey(OpacityKey)].isInt())
|
||||
{
|
||||
// to make the UI happy, set ColorSchemeName to whatever the dark value is.
|
||||
JsonUtils::GetValueForKey(json["colorScheme"], "dark", _DarkColorSchemeName);
|
||||
JsonUtils::GetValueForKey(json["colorScheme"], "light", _LightColorSchemeName);
|
||||
|
||||
_logSettingSet("colorScheme.dark");
|
||||
_logSettingSet("colorScheme.light");
|
||||
_json[JsonKey(OpacityKey)] = _json[JsonKey(OpacityKey)].asInt() / 100.0f;
|
||||
}
|
||||
_logSettingIfSet(OpacityKey, HasOpacity());
|
||||
|
||||
// ColorScheme: ConversionTrait<ColorSchemeReference> handles string↔object normalization.
|
||||
// The raw JSON is stored in _json; the trait interprets it on read.
|
||||
_logSettingIfSet(ColorSchemeKey, HasColorSchemeRef());
|
||||
|
||||
// MTSM settings are now JSON-backed (no backing fields).
|
||||
// Values are already in _json from the merge step above.
|
||||
// We only need to log which settings were set in this layer.
|
||||
#define APPEARANCE_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::GetValueForKey(json, jsonKey, _##name); \
|
||||
_logSettingIfSet(jsonKey, _##name.has_value());
|
||||
_logSettingIfSet(jsonKey, json.isMember(jsonKey) && !json[jsonKey].isNull());
|
||||
|
||||
MTSM_APPEARANCE_SETTINGS(APPEARANCE_SETTINGS_LAYER_JSON)
|
||||
#undef APPEARANCE_SETTINGS_LAYER_JSON
|
||||
|
||||
// Complex/mutable settings — backing fields populated from _json for resolution lifecycle.
|
||||
// _json is the source of truth for serialization; backing fields are for resolution lifecycle.
|
||||
JsonUtils::GetValueForKey(json, "experimental.pixelShaderPath", _PixelShaderPath);
|
||||
_logSettingIfSet("experimental.pixelShaderPath", _PixelShaderPath.has_value());
|
||||
JsonUtils::GetValueForKey(json, "experimental.pixelShaderImagePath", _PixelShaderImagePath);
|
||||
_logSettingIfSet("experimental.pixelShaderImagePath", _PixelShaderImagePath.has_value());
|
||||
JsonUtils::GetValueForKey(json, "backgroundImage", _BackgroundImagePath);
|
||||
_logSettingIfSet("backgroundImage", _BackgroundImagePath.has_value());
|
||||
}
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::Profile AppearanceConfig::SourceProfile()
|
||||
|
||||
@@ -18,11 +18,80 @@ Author(s):
|
||||
|
||||
#include "AppearanceConfig.g.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
#include "IInheritable.h"
|
||||
#include "MTSMSettings.h"
|
||||
#include "MediaResourceSupport.h"
|
||||
#include <DefaultSettings.h>
|
||||
|
||||
// ColorSchemeReference: a pair of color scheme names for dark and light themes.
|
||||
// Represents the polymorphic "colorScheme" JSON key, which can be either:
|
||||
// "colorScheme": "Campbell" → { dark: "Campbell", light: "Campbell" }
|
||||
// "colorScheme": { "dark": "X", "light": "Y" } → { dark: "X", light: "Y" }
|
||||
struct ColorSchemeReference
|
||||
{
|
||||
winrt::hstring dark{ L"Campbell" };
|
||||
winrt::hstring light{ L"Campbell" };
|
||||
|
||||
static ColorSchemeReference Default() { return {}; }
|
||||
bool operator==(const ColorSchemeReference&) const = default;
|
||||
};
|
||||
|
||||
namespace Microsoft::Terminal::Settings::Model::JsonUtils
|
||||
{
|
||||
template<>
|
||||
struct ConversionTrait<ColorSchemeReference>
|
||||
{
|
||||
ColorSchemeReference FromJson(const Json::Value& json)
|
||||
{
|
||||
ColorSchemeReference result;
|
||||
if (json.isString())
|
||||
{
|
||||
// Simple form: both dark and light use the same scheme
|
||||
const auto name = winrt::hstring{ til::u8u16(json.asString()) };
|
||||
result.dark = name;
|
||||
result.light = name;
|
||||
}
|
||||
else if (json.isObject())
|
||||
{
|
||||
// Structured form: { "dark": "...", "light": "..." }
|
||||
if (json.isMember("dark"))
|
||||
{
|
||||
result.dark = winrt::hstring{ til::u8u16(json["dark"].asString()) };
|
||||
}
|
||||
if (json.isMember("light"))
|
||||
{
|
||||
result.light = winrt::hstring{ til::u8u16(json["light"].asString()) };
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json) const
|
||||
{
|
||||
return json.isString() || json.isObject();
|
||||
}
|
||||
|
||||
Json::Value ToJson(const ColorSchemeReference& val)
|
||||
{
|
||||
// Collapse to string when dark == light
|
||||
if (val.dark == val.light)
|
||||
{
|
||||
return til::u16u8(val.dark);
|
||||
}
|
||||
Json::Value obj{ Json::ValueType::objectValue };
|
||||
obj["dark"] = til::u16u8(val.dark);
|
||||
obj["light"] = til::u16u8(val.light);
|
||||
return obj;
|
||||
}
|
||||
|
||||
std::string TypeDescription() const
|
||||
{
|
||||
return "color scheme name (string or {dark, light})";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct AppearanceConfig : AppearanceConfigT<AppearanceConfig, IMediaResourceContainer>, IInheritable<AppearanceConfig>
|
||||
@@ -38,22 +107,65 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
void ResolveMediaResources(const Model::MediaResourceResolver& resolver);
|
||||
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, Foreground, nullptr);
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, Background, nullptr);
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, SelectionBackground, nullptr);
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, CursorColor, nullptr);
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, float, Opacity, 1.0f);
|
||||
// Generic setting access via SettingKey
|
||||
bool HasSetting(AppearanceSettingKey key) const;
|
||||
void ClearSetting(AppearanceSettingKey key);
|
||||
std::vector<AppearanceSettingKey> CurrentSettings() const;
|
||||
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, hstring, DarkColorSchemeName, L"Campbell");
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, hstring, LightColorSchemeName, L"Campbell");
|
||||
// Nullable color settings (JSON-backed)
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, Foreground, "foreground", nullptr)
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, Background, "background", nullptr)
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, SelectionBackground, "selectionBackground", nullptr)
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, CursorColor, "cursorColor", nullptr)
|
||||
|
||||
// Opacity: JSON-backed with normalization (int percent → float in LayerJson)
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, float, Opacity, "opacity", 1.0f)
|
||||
|
||||
// ColorScheme: JSON-backed via INHERITABLE_SETTING with ColorSchemeReference struct.
|
||||
// The polymorphic JSON key ("colorScheme" as string or object) is handled by
|
||||
// ConversionTrait<ColorSchemeReference>. The macro provides Has/Clear/OverrideSource.
|
||||
// Thin wrappers expose the IDL-required DarkColorSchemeName / LightColorSchemeName.
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, ColorSchemeReference, ColorSchemeRef, "colorScheme", ColorSchemeReference::Default())
|
||||
public:
|
||||
hstring DarkColorSchemeName() const { return ColorSchemeRef().dark; }
|
||||
void DarkColorSchemeName(const hstring& value)
|
||||
{
|
||||
auto ref = ColorSchemeRef();
|
||||
ref.dark = value;
|
||||
ColorSchemeRef(ref);
|
||||
}
|
||||
bool HasDarkColorSchemeName() const { return HasColorSchemeRef(); }
|
||||
void ClearDarkColorSchemeName() { ClearColorSchemeRef(); }
|
||||
Model::IAppearanceConfig DarkColorSchemeNameOverrideSource() { return ColorSchemeRefOverrideSource(); }
|
||||
|
||||
hstring LightColorSchemeName() const { return ColorSchemeRef().light; }
|
||||
void LightColorSchemeName(const hstring& value)
|
||||
{
|
||||
auto ref = ColorSchemeRef();
|
||||
ref.light = value;
|
||||
ColorSchemeRef(ref);
|
||||
}
|
||||
bool HasLightColorSchemeName() const { return HasColorSchemeRef(); }
|
||||
void ClearLightColorSchemeName() { ClearColorSchemeRef(); }
|
||||
Model::IAppearanceConfig LightColorSchemeNameOverrideSource() { return ColorSchemeRefOverrideSource(); }
|
||||
|
||||
#define APPEARANCE_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, type, name, ##__VA_ARGS__)
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, type, name, jsonKey, ##__VA_ARGS__)
|
||||
MTSM_APPEARANCE_SETTINGS(APPEARANCE_SETTINGS_INITIALIZE)
|
||||
#undef APPEARANCE_SETTINGS_INITIALIZE
|
||||
|
||||
// IMediaResource settings with backing fields for resolution lifecycle.
|
||||
// See IInheritable.h for the INHERITABLE_MEDIA_RESOURCE_SETTING macro definition.
|
||||
INHERITABLE_MEDIA_RESOURCE_SETTING(Model::IAppearanceConfig, PixelShaderPath, "experimental.pixelShaderPath", implementation::MediaResource::Empty())
|
||||
INHERITABLE_MEDIA_RESOURCE_SETTING(Model::IAppearanceConfig, PixelShaderImagePath, "experimental.pixelShaderImagePath", implementation::MediaResource::Empty())
|
||||
INHERITABLE_MEDIA_RESOURCE_SETTING(Model::IAppearanceConfig, BackgroundImagePath, "backgroundImage", implementation::MediaResource::Empty())
|
||||
|
||||
private:
|
||||
winrt::weak_ref<Profile> _sourceProfile;
|
||||
|
||||
// Raw JSON for this layer (appearance-relevant keys only).
|
||||
Json::Value _json{ Json::ValueType::objectValue };
|
||||
|
||||
std::set<std::string> _changeLog;
|
||||
|
||||
void _logSettingSet(const std::string_view& setting);
|
||||
|
||||
@@ -12,6 +12,8 @@ using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
||||
static constexpr std::string_view FontInfoKey{ "font" };
|
||||
static constexpr std::string_view FontAxesKey{ "axes" };
|
||||
static constexpr std::string_view FontFeaturesKey{ "features" };
|
||||
static constexpr std::string_view LegacyFontFaceKey{ "fontFace" };
|
||||
static constexpr std::string_view LegacyFontSizeKey{ "fontSize" };
|
||||
static constexpr std::string_view LegacyFontWeightKey{ "fontWeight" };
|
||||
@@ -25,29 +27,11 @@ winrt::com_ptr<FontConfig> FontConfig::CopyFontInfo(const FontConfig* source, wi
|
||||
{
|
||||
auto fontInfo{ winrt::make_self<FontConfig>(std::move(sourceProfile)) };
|
||||
|
||||
#define FONT_SETTINGS_COPY(type, name, jsonKey, ...) \
|
||||
fontInfo->_##name = source->_##name;
|
||||
MTSM_FONT_SETTINGS(FONT_SETTINGS_COPY)
|
||||
#undef FONT_SETTINGS_COPY
|
||||
fontInfo->_json = source->_json;
|
||||
|
||||
// We cannot simply copy the font axes and features with `fontInfo->_FontAxes = source->_FontAxes;`
|
||||
// since that'll just create a reference; we have to manually copy the values.
|
||||
static constexpr auto cloneFontMap = [](const IFontFeatureMap& map) {
|
||||
std::map<winrt::hstring, float> fontAxes;
|
||||
for (const auto& [k, v] : map)
|
||||
{
|
||||
fontAxes.emplace(k, v);
|
||||
}
|
||||
return winrt::single_threaded_map(std::move(fontAxes));
|
||||
};
|
||||
if (source->_FontAxes)
|
||||
{
|
||||
fontInfo->_FontAxes = cloneFontMap(*source->_FontAxes);
|
||||
}
|
||||
if (source->_FontFeatures)
|
||||
{
|
||||
fontInfo->_FontFeatures = cloneFontMap(*source->_FontFeatures);
|
||||
}
|
||||
// FontAxes and FontFeatures are now JSON-backed — handled by _json copy above.
|
||||
// No manual deep-copy needed; the JSON-backed getter returns a fresh deserialized
|
||||
// collection each time.
|
||||
|
||||
return fontInfo;
|
||||
}
|
||||
@@ -56,14 +40,58 @@ Json::Value FontConfig::ToJson() const
|
||||
{
|
||||
Json::Value json{ Json::ValueType::objectValue };
|
||||
|
||||
// MTSM font settings: copy from _json (the source of truth)
|
||||
#define FONT_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::SetValueForKey(json, jsonKey, _##name);
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, jsonKey);
|
||||
MTSM_FONT_SETTINGS(FONT_SETTINGS_TO_JSON)
|
||||
#undef FONT_SETTINGS_TO_JSON
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
bool FontConfig::HasSetting(FontSettingKey key) const
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _FONT_HAS_SETTING(type, name, jsonKey, ...) \
|
||||
case FontSettingKey::name: \
|
||||
return Has##name();
|
||||
MTSM_FONT_SETTINGS(_FONT_HAS_SETTING)
|
||||
#undef _FONT_HAS_SETTING
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void FontConfig::ClearSetting(FontSettingKey key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _FONT_CLEAR_SETTING(type, name, jsonKey, ...) \
|
||||
case FontSettingKey::name: \
|
||||
Clear##name(); \
|
||||
break;
|
||||
MTSM_FONT_SETTINGS(_FONT_CLEAR_SETTING)
|
||||
#undef _FONT_CLEAR_SETTING
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<FontSettingKey> FontConfig::CurrentSettings() const
|
||||
{
|
||||
std::vector<FontSettingKey> result;
|
||||
for (auto i = 0; i < static_cast<int>(FontSettingKey::SETTINGS_SIZE); i++)
|
||||
{
|
||||
const auto key = static_cast<FontSettingKey>(i);
|
||||
if (HasSetting(key))
|
||||
{
|
||||
result.push_back(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Layer values from the given json object on top of the existing properties
|
||||
// of this object. For any keys we're expecting to be able to parse in the
|
||||
@@ -83,25 +111,38 @@ void FontConfig::LayerJson(const Json::Value& json)
|
||||
{
|
||||
// A font object is defined, use that
|
||||
const auto fontInfoJson = json[JsonKey(FontInfoKey)];
|
||||
#define FONT_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::GetValueForKey(fontInfoJson, jsonKey, _##name); \
|
||||
_logSettingIfSet(jsonKey, _##name.has_value());
|
||||
|
||||
// Merge the font sub-object into stored _json (font-object shape).
|
||||
JsonUtils::MergeJsonKeys(fontInfoJson, _json);
|
||||
|
||||
// MTSM font settings are now JSON-backed (including FontAxes/FontFeatures).
|
||||
// Values are already in _json. We only need to log which settings were set.
|
||||
#define FONT_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
_logSettingIfSet(jsonKey, fontInfoJson.isMember(jsonKey) && !fontInfoJson[jsonKey].isNull());
|
||||
|
||||
MTSM_FONT_SETTINGS(FONT_SETTINGS_LAYER_JSON)
|
||||
#undef FONT_SETTINGS_LAYER_JSON
|
||||
}
|
||||
else
|
||||
{
|
||||
// No font object is defined
|
||||
// No font object is defined — normalize legacy flat keys into font-object shape.
|
||||
if (json.isMember(JsonKey(LegacyFontFaceKey)))
|
||||
{
|
||||
_json["face"] = json[JsonKey(LegacyFontFaceKey)];
|
||||
}
|
||||
if (json.isMember(JsonKey(LegacyFontSizeKey)))
|
||||
{
|
||||
_json["size"] = json[JsonKey(LegacyFontSizeKey)];
|
||||
}
|
||||
if (json.isMember(JsonKey(LegacyFontWeightKey)))
|
||||
{
|
||||
_json["weight"] = json[JsonKey(LegacyFontWeightKey)];
|
||||
}
|
||||
|
||||
// Log settings as if they were a part of the font object
|
||||
JsonUtils::GetValueForKey(json, LegacyFontFaceKey, _FontFace);
|
||||
_logSettingIfSet("face", _FontFace.has_value());
|
||||
|
||||
JsonUtils::GetValueForKey(json, LegacyFontSizeKey, _FontSize);
|
||||
_logSettingIfSet("size", _FontSize.has_value());
|
||||
|
||||
JsonUtils::GetValueForKey(json, LegacyFontWeightKey, _FontWeight);
|
||||
_logSettingIfSet("weight", _FontWeight.has_value());
|
||||
_logSettingIfSet("face", json.isMember(JsonKey(LegacyFontFaceKey)));
|
||||
_logSettingIfSet("size", json.isMember(JsonKey(LegacyFontSizeKey)));
|
||||
_logSettingIfSet("weight", json.isMember(JsonKey(LegacyFontWeightKey)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,16 +153,16 @@ winrt::Microsoft::Terminal::Settings::Model::Profile FontConfig::SourceProfile()
|
||||
|
||||
void FontConfig::_logSettingSet(const std::string_view& setting)
|
||||
{
|
||||
if (setting == "axes" && _FontAxes.has_value())
|
||||
if (setting == FontAxesKey && HasFontAxes())
|
||||
{
|
||||
for (const auto& [mapKey, _] : _FontAxes.value())
|
||||
for (const auto& [mapKey, _] : FontAxes())
|
||||
{
|
||||
_changeLog.emplace(fmt::format(FMT_COMPILE("{}.{}"), setting, til::u16u8(mapKey)));
|
||||
}
|
||||
}
|
||||
else if (setting == "features" && _FontFeatures.has_value())
|
||||
else if (setting == FontFeaturesKey && HasFontFeatures())
|
||||
{
|
||||
for (const auto& [mapKey, _] : _FontFeatures.value())
|
||||
for (const auto& [mapKey, _] : FontFeatures())
|
||||
{
|
||||
_changeLog.emplace(fmt::format(FMT_COMPILE("{}.{}"), setting, til::u16u8(mapKey)));
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ Author(s):
|
||||
#include "pch.h"
|
||||
#include "FontConfig.g.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
#include "MTSMSettings.h"
|
||||
#include "IInheritable.h"
|
||||
#include <DefaultSettings.h>
|
||||
@@ -39,13 +40,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
Model::Profile SourceProfile();
|
||||
|
||||
// Generic setting access via SettingKey
|
||||
bool HasSetting(FontSettingKey key) const;
|
||||
void ClearSetting(FontSettingKey key);
|
||||
std::vector<FontSettingKey> CurrentSettings() const;
|
||||
|
||||
#define FONT_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
|
||||
INHERITABLE_SETTING(Model::FontConfig, type, name, ##__VA_ARGS__)
|
||||
INHERITABLE_SETTING(Model::FontConfig, type, name, jsonKey, ##__VA_ARGS__)
|
||||
MTSM_FONT_SETTINGS(FONT_SETTINGS_INITIALIZE)
|
||||
#undef FONT_SETTINGS_INITIALIZE
|
||||
|
||||
private:
|
||||
winrt::weak_ref<Profile> _sourceProfile;
|
||||
|
||||
// Raw JSON for this layer (font sub-object shape).
|
||||
Json::Value _json{ Json::ValueType::objectValue };
|
||||
|
||||
std::set<std::string> _changeLog;
|
||||
|
||||
void _logSettingSet(const std::string_view& setting);
|
||||
|
||||
@@ -59,16 +59,13 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
|
||||
{
|
||||
auto globals{ winrt::make_self<GlobalAppSettings>() };
|
||||
|
||||
globals->_UnparsedDefaultProfile = _UnparsedDefaultProfile;
|
||||
|
||||
globals->_defaultProfile = _defaultProfile;
|
||||
globals->_actionMap = _actionMap->Copy();
|
||||
globals->_keybindingsWarnings = _keybindingsWarnings;
|
||||
globals->_json = _json;
|
||||
|
||||
#define GLOBAL_SETTINGS_COPY(type, name, jsonKey, ...) \
|
||||
globals->_##name = _##name;
|
||||
MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_COPY)
|
||||
#undef GLOBAL_SETTINGS_COPY
|
||||
// JSON-backed settings (UnparsedDefaultProfile, MTSM settings) all live in _json,
|
||||
// which is already deep-copied above. No per-setting copy needed.
|
||||
|
||||
if (_colorSchemes)
|
||||
{
|
||||
@@ -86,6 +83,9 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
|
||||
globals->_themes.Insert(kv.Key(), *themeImpl->Copy());
|
||||
}
|
||||
}
|
||||
|
||||
// Complex/mutable settings — backing fields for mutation lifecycle.
|
||||
// _json (copied above) is the source of truth; backing fields hold runtime state.
|
||||
if (_NewTabMenu)
|
||||
{
|
||||
globals->_NewTabMenu = winrt::single_threaded_vector<Model::NewTabMenuEntry>();
|
||||
@@ -94,14 +94,6 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
|
||||
globals->_NewTabMenu->Append(get_self<NewTabMenuEntry>(entry)->Copy());
|
||||
}
|
||||
}
|
||||
if (_DisabledProfileSources)
|
||||
{
|
||||
globals->_DisabledProfileSources = winrt::single_threaded_vector<hstring>();
|
||||
for (const auto& src : *_DisabledProfileSources)
|
||||
{
|
||||
globals->_DisabledProfileSources->Append(src);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
@@ -120,7 +112,7 @@ winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, winrt::Microso
|
||||
void GlobalAppSettings::DefaultProfile(const winrt::guid& defaultProfile) noexcept
|
||||
{
|
||||
_defaultProfile = defaultProfile;
|
||||
_UnparsedDefaultProfile = Utils::GuidToString(defaultProfile);
|
||||
UnparsedDefaultProfile(hstring{ Utils::GuidToString(defaultProfile) });
|
||||
}
|
||||
|
||||
winrt::guid GlobalAppSettings::DefaultProfile() const
|
||||
@@ -150,25 +142,46 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::FromJson(const Json::Value&
|
||||
|
||||
void GlobalAppSettings::LayerJson(const Json::Value& json, const OriginTag origin)
|
||||
{
|
||||
JsonUtils::GetValueForKey(json, DefaultProfileKey, _UnparsedDefaultProfile);
|
||||
// Merge incoming JSON keys into stored _json (key-wise, not replacement).
|
||||
JsonUtils::MergeJsonKeys(json, _json);
|
||||
|
||||
// UnparsedDefaultProfile is now JSON-backed (in _json["defaultProfile"]).
|
||||
// No backing field to populate.
|
||||
|
||||
// GH#8076 - when adding enum values to this key, we also changed it from
|
||||
// "useTabSwitcher" to "tabSwitcherMode". Continue supporting
|
||||
// "useTabSwitcher", but prefer "tabSwitcherMode"
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyUseTabSwitcherModeKey, _TabSwitcherMode) || _fixupsAppliedDuringLoad;
|
||||
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyInputServiceWarningKey, _InputServiceWarning) || _fixupsAppliedDuringLoad;
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyWarnAboutLargePasteKey, _WarnAboutLargePaste) || _fixupsAppliedDuringLoad;
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyWarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste) || _fixupsAppliedDuringLoad;
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyConfirmCloseAllTabsKey, _ConfirmCloseAllTabs) || _fixupsAppliedDuringLoad;
|
||||
// Normalize legacy keys into canonical _json keys for JSON-backed getters.
|
||||
static constexpr std::pair<std::string_view, std::string_view> legacyKeyMappings[] = {
|
||||
{ LegacyUseTabSwitcherModeKey, "tabSwitcherMode" },
|
||||
{ LegacyInputServiceWarningKey, "warning.inputService" },
|
||||
{ LegacyWarnAboutLargePasteKey, "warning.largePaste" },
|
||||
{ LegacyWarnAboutMultiLinePasteKey, "warning.multiLinePaste" },
|
||||
{ LegacyConfirmCloseAllTabsKey, "warning.confirmCloseAllTabs" },
|
||||
};
|
||||
for (const auto& [legacyKey, canonicalKey] : legacyKeyMappings)
|
||||
{
|
||||
if (json.isMember(JsonKey(legacyKey)))
|
||||
{
|
||||
_fixupsAppliedDuringLoad = true;
|
||||
_json[JsonKey(canonicalKey)] = json[JsonKey(legacyKey)];
|
||||
}
|
||||
}
|
||||
|
||||
// MTSM settings are now JSON-backed (no backing fields).
|
||||
// Values are already in _json from the merge step above.
|
||||
// We only need to log which settings were set in this layer.
|
||||
#define GLOBAL_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::GetValueForKey(json, jsonKey, _##name); \
|
||||
_logSettingIfSet(jsonKey, _##name.has_value());
|
||||
_logSettingIfSet(jsonKey, json.isMember(jsonKey) && !json[jsonKey].isNull());
|
||||
|
||||
MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_LAYER_JSON)
|
||||
#undef GLOBAL_SETTINGS_LAYER_JSON
|
||||
|
||||
// Complex/mutable settings — backing fields populated from _json for runtime use.
|
||||
// _json is the source of truth for serialization; backing fields are for mutation lifecycle.
|
||||
JsonUtils::GetValueForKey(json, "newTabMenu", _NewTabMenu);
|
||||
_logSettingIfSet("newTabMenu", _NewTabMenu.has_value());
|
||||
|
||||
// GH#11975 We only want to allow sensible values and prevent crashes, so we are clamping those values
|
||||
// We only want to assign if the value did change through clamping,
|
||||
// otherwise we could end up setting defaults that get persisted
|
||||
@@ -304,47 +317,105 @@ Json::Value GlobalAppSettings::ToJson()
|
||||
// These experimental options should be removed from the settings file if they're at their default value.
|
||||
// This prevents them from sticking around forever, even if the user was just experimenting with them.
|
||||
// One could consider this a workaround for the settings UI right now not having a "reset to default" button for these.
|
||||
if (_GraphicsAPI == Control::GraphicsAPI::Automatic)
|
||||
if (HasGraphicsAPI() && GraphicsAPI() == Control::GraphicsAPI::Automatic)
|
||||
{
|
||||
_GraphicsAPI.reset();
|
||||
ClearGraphicsAPI();
|
||||
}
|
||||
if (_TextMeasurement == Control::TextMeasurement::Graphemes)
|
||||
if (HasTextMeasurement() && TextMeasurement() == Control::TextMeasurement::Graphemes)
|
||||
{
|
||||
_TextMeasurement.reset();
|
||||
ClearTextMeasurement();
|
||||
}
|
||||
if (_AmbiguousWidth == Control::AmbiguousWidth::Narrow)
|
||||
if (HasAmbiguousWidth() && AmbiguousWidth() == Control::AmbiguousWidth::Narrow)
|
||||
{
|
||||
_AmbiguousWidth.reset();
|
||||
ClearAmbiguousWidth();
|
||||
}
|
||||
if (_DefaultInputScope == Control::DefaultInputScope::Default)
|
||||
if (HasDefaultInputScope() && DefaultInputScope() == Control::DefaultInputScope::Default)
|
||||
{
|
||||
_DefaultInputScope.reset();
|
||||
ClearDefaultInputScope();
|
||||
}
|
||||
|
||||
if (_DisablePartialInvalidation == false)
|
||||
if (HasDisablePartialInvalidation() && DisablePartialInvalidation() == false)
|
||||
{
|
||||
_DisablePartialInvalidation.reset();
|
||||
ClearDisablePartialInvalidation();
|
||||
}
|
||||
if (_SoftwareRendering == false)
|
||||
if (HasSoftwareRendering() && SoftwareRendering() == false)
|
||||
{
|
||||
_SoftwareRendering.reset();
|
||||
ClearSoftwareRendering();
|
||||
}
|
||||
|
||||
Json::Value json{ Json::ValueType::objectValue };
|
||||
|
||||
JsonUtils::SetValueForKey(json, DefaultProfileKey, _UnparsedDefaultProfile);
|
||||
// DefaultProfile: copy from _json
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, DefaultProfileKey);
|
||||
|
||||
// MTSM global settings: copy from _json (the source of truth)
|
||||
#define GLOBAL_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::SetValueForKey(json, jsonKey, _##name);
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, jsonKey);
|
||||
MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_TO_JSON)
|
||||
#undef GLOBAL_SETTINGS_TO_JSON
|
||||
|
||||
// Complex/mutable settings — read from _json (source of truth), not backing fields
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, "newTabMenu");
|
||||
|
||||
json[JsonKey(ActionsKey)] = _actionMap->ToJson();
|
||||
json[JsonKey(KeybindingsKey)] = _actionMap->KeyBindingsToJson();
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
bool GlobalAppSettings::HasSetting(GlobalSettingKey key) const
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _GLOBAL_HAS_SETTING(type, name, jsonKey, ...) \
|
||||
case GlobalSettingKey::name: \
|
||||
return Has##name();
|
||||
MTSM_GLOBAL_SETTINGS(_GLOBAL_HAS_SETTING)
|
||||
#undef _GLOBAL_HAS_SETTING
|
||||
case GlobalSettingKey::_UnparsedDefaultProfile:
|
||||
return HasUnparsedDefaultProfile();
|
||||
case GlobalSettingKey::_NewTabMenu:
|
||||
return HasNewTabMenu();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalAppSettings::ClearSetting(GlobalSettingKey key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _GLOBAL_CLEAR_SETTING(type, name, jsonKey, ...) \
|
||||
case GlobalSettingKey::name: \
|
||||
Clear##name(); \
|
||||
break;
|
||||
MTSM_GLOBAL_SETTINGS(_GLOBAL_CLEAR_SETTING)
|
||||
#undef _GLOBAL_CLEAR_SETTING
|
||||
case GlobalSettingKey::_UnparsedDefaultProfile:
|
||||
ClearUnparsedDefaultProfile();
|
||||
break;
|
||||
case GlobalSettingKey::_NewTabMenu:
|
||||
ClearNewTabMenu();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<GlobalSettingKey> GlobalAppSettings::CurrentSettings() const
|
||||
{
|
||||
std::vector<GlobalSettingKey> result;
|
||||
for (auto i = 0; i < static_cast<int>(GlobalSettingKey::SETTINGS_SIZE); i++)
|
||||
{
|
||||
const auto key = static_cast<GlobalSettingKey>(i);
|
||||
if (HasSetting(key))
|
||||
{
|
||||
result.push_back(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool GlobalAppSettings::FixupsAppliedDuringLoad()
|
||||
{
|
||||
return _fixupsAppliedDuringLoad || _actionMap->FixupsAppliedDuringLoad();
|
||||
@@ -394,9 +465,9 @@ bool GlobalAppSettings::ShouldUsePersistedLayout() const
|
||||
void GlobalAppSettings::ResolveMediaResources(const Model::MediaResourceResolver& resolver)
|
||||
{
|
||||
_actionMap->ResolveMediaResourcesWithBasePath(SourceBasePath, resolver);
|
||||
if (_NewTabMenu)
|
||||
if (HasNewTabMenu())
|
||||
{
|
||||
for (const auto& entry : *_NewTabMenu)
|
||||
for (const auto& entry : NewTabMenu())
|
||||
{
|
||||
if (const auto resolvable{ entry.try_as<IPathlessMediaResourceContainer>() })
|
||||
{
|
||||
@@ -414,11 +485,12 @@ void GlobalAppSettings::_logSettingSet(const std::string_view& setting)
|
||||
{
|
||||
if (setting == "theme")
|
||||
{
|
||||
if (_Theme.has_value())
|
||||
if (HasTheme())
|
||||
{
|
||||
const auto theme = Theme();
|
||||
// ThemePair always has a Dark/Light value,
|
||||
// so we need to check if they were explicitly set
|
||||
if (_Theme->DarkName() == _Theme->LightName())
|
||||
if (theme.DarkName() == theme.LightName())
|
||||
{
|
||||
_changeLog.emplace(setting);
|
||||
}
|
||||
@@ -431,9 +503,9 @@ void GlobalAppSettings::_logSettingSet(const std::string_view& setting)
|
||||
}
|
||||
else if (setting == "newTabMenu")
|
||||
{
|
||||
if (_NewTabMenu.has_value())
|
||||
if (HasNewTabMenu())
|
||||
{
|
||||
for (const auto& entry : *_NewTabMenu)
|
||||
for (const auto& entry : NewTabMenu())
|
||||
{
|
||||
std::string entryType;
|
||||
switch (entry.Type())
|
||||
@@ -476,7 +548,7 @@ void GlobalAppSettings::UpdateCommandID(const Model::Command& cmd, winrt::hstrin
|
||||
_actionMap->UpdateCommandID(cmd, newID);
|
||||
// newID might have been empty when this function was called, if so actionMap would have generated a new ID, use that
|
||||
newID = cmd.ID();
|
||||
if (_NewTabMenu)
|
||||
if (HasNewTabMenu())
|
||||
{
|
||||
// Recursive lambda function to look through all the new tab menu entries and update IDs accordingly
|
||||
std::function<void(const Model::NewTabMenuEntry&)> recursiveEntryIdUpdate;
|
||||
@@ -503,7 +575,7 @@ void GlobalAppSettings::UpdateCommandID(const Model::Command& cmd, winrt::hstrin
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& entry : *_NewTabMenu)
|
||||
for (const auto& entry : NewTabMenu())
|
||||
{
|
||||
recursiveEntryIdUpdate(entry);
|
||||
}
|
||||
@@ -515,8 +587,8 @@ void GlobalAppSettings::_logSettingIfSet(const std::string_view& setting, const
|
||||
if (isSet)
|
||||
{
|
||||
// Exclude some false positives from userDefaults.json
|
||||
const bool settingCopyFormattingToDefault = til::equals_insensitive_ascii(setting, "copyFormatting") && _CopyFormatting.has_value() && _CopyFormatting.value() == static_cast<Control::CopyFormat>(0);
|
||||
const bool settingNTMToDefault = til::equals_insensitive_ascii(setting, "newTabMenu") && _NewTabMenu.has_value() && _NewTabMenu->Size() == 1 && _NewTabMenu->GetAt(0).Type() == NewTabMenuEntryType::RemainingProfiles;
|
||||
const bool settingCopyFormattingToDefault = til::equals_insensitive_ascii(setting, "copyFormatting") && HasCopyFormatting() && CopyFormatting() == static_cast<Control::CopyFormat>(0);
|
||||
const bool settingNTMToDefault = til::equals_insensitive_ascii(setting, "newTabMenu") && HasNewTabMenu() && NewTabMenu().Size() == 1 && NewTabMenu().GetAt(0).Type() == NewTabMenuEntryType::RemainingProfiles;
|
||||
if (!settingCopyFormattingToDefault && !settingNTMToDefault)
|
||||
{
|
||||
_logSettingSet(setting);
|
||||
|
||||
@@ -18,6 +18,7 @@ Author(s):
|
||||
#include "GlobalAppSettings.g.h"
|
||||
#include "IInheritable.h"
|
||||
#include "MTSMSettings.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
|
||||
#include "ActionMap.h"
|
||||
#include "Command.h"
|
||||
@@ -56,6 +57,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
Json::Value ToJson();
|
||||
bool FixupsAppliedDuringLoad();
|
||||
|
||||
// Generic setting access via SettingKey
|
||||
bool HasSetting(GlobalSettingKey key) const;
|
||||
void ClearSetting(GlobalSettingKey key);
|
||||
std::vector<GlobalSettingKey> CurrentSettings() const;
|
||||
|
||||
const std::vector<SettingsLoadWarnings>& KeybindingsWarnings() const;
|
||||
|
||||
// This DefaultProfile() setter is called by CascadiaSettings,
|
||||
@@ -80,13 +86,28 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
winrt::hstring SourceBasePath;
|
||||
|
||||
INHERITABLE_SETTING(Model::GlobalAppSettings, hstring, UnparsedDefaultProfile, L"");
|
||||
INHERITABLE_SETTING(Model::GlobalAppSettings, hstring, UnparsedDefaultProfile, "defaultProfile", L"");
|
||||
|
||||
#define GLOBAL_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
|
||||
INHERITABLE_SETTING_WITH_LOGGING(Model::GlobalAppSettings, type, name, jsonKey, ##__VA_ARGS__)
|
||||
MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_INITIALIZE)
|
||||
#undef GLOBAL_SETTINGS_INITIALIZE
|
||||
|
||||
// NewTabMenu: mutable with backing field for editor in-place mutation.
|
||||
// _json is the source of truth for serialization. Setter dual-writes to both.
|
||||
_BASE_INHERITABLE_MUTABLE_SETTING(Model::GlobalAppSettings, std::optional<winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>>, NewTabMenu, winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} }))
|
||||
public:
|
||||
winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry> NewTabMenu() const
|
||||
{
|
||||
const auto val{ _getNewTabMenuImpl() };
|
||||
return val ? *val : winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} });
|
||||
}
|
||||
void NewTabMenu(const winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>& value)
|
||||
{
|
||||
_NewTabMenu = value;
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey(_json, "newTabMenu", value);
|
||||
}
|
||||
|
||||
private:
|
||||
#ifdef NDEBUG
|
||||
static constexpr bool debugFeaturesDefault{ false };
|
||||
@@ -94,6 +115,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
static constexpr bool debugFeaturesDefault{ true };
|
||||
#endif
|
||||
|
||||
// Raw JSON for this layer. Populated by LayerJson(), will become the
|
||||
// source of truth for settings once the JSON-backed refactor is complete.
|
||||
Json::Value _json{ Json::ValueType::objectValue };
|
||||
|
||||
winrt::guid _defaultProfile{};
|
||||
bool _fixupsAppliedDuringLoad{ false };
|
||||
bool _legacyReloadEnvironmentVariables{ true };
|
||||
|
||||
@@ -14,6 +14,8 @@ Author(s):
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "JsonUtils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
template<typename T>
|
||||
@@ -79,189 +81,554 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
using NullableSetting = std::optional<std::optional<T>>;
|
||||
}
|
||||
|
||||
// Shared implementation between the INHERITABLE_SETTING and INHERITABLE_NULLABLE_SETTING macros.
|
||||
// See below for more details.
|
||||
#define _BASE_INHERITABLE_SETTING(projectedType, storageType, 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; \
|
||||
} \
|
||||
\
|
||||
/* Clear the user set value */ \
|
||||
void Clear##name() \
|
||||
{ \
|
||||
_##name = std::nullopt; \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
storageType _##name{ std::nullopt }; \
|
||||
\
|
||||
storageType _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; \
|
||||
} \
|
||||
\
|
||||
auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (_##name) \
|
||||
{ \
|
||||
return get_strong(); \
|
||||
} \
|
||||
\
|
||||
/*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; \
|
||||
} \
|
||||
\
|
||||
auto _get##name##OverrideSourceAndValueImpl() \
|
||||
->std::pair<decltype(get_strong()), storageType> \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (_##name) \
|
||||
{ \
|
||||
return { get_strong(), _##name }; \
|
||||
} \
|
||||
\
|
||||
/*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, source->_##name }; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return { nullptr, std::nullopt }; \
|
||||
// =============================================================================
|
||||
// Inheritable setting macros
|
||||
// =============================================================================
|
||||
//
|
||||
// These macros implement the standard functions for inheritable settings:
|
||||
// - Has<NAME>(): checks if the user explicitly set a value for this setting
|
||||
// - <NAME>OverrideSource(): returns the object that provides the resolved value
|
||||
// - Getter(): returns the resolved value (this layer --> parent --> default)
|
||||
// - Setter(): sets the value
|
||||
// - Clear<NAME>(): clears the user set value (reverts to inherited/default)
|
||||
//
|
||||
// There are two storage strategies:
|
||||
//
|
||||
// 1. JSON-backed (preferred): All state lives in _json (a Json::Value member).
|
||||
// Use these for any setting with a ConversionTrait. The owning class must
|
||||
// declare a `Json::Value _json{ Json::ValueType::objectValue };` member.
|
||||
//
|
||||
// 2. Mutable backing-field (exception): State lives in a std::optional<T> member.
|
||||
// Use these only for types that callers mutate in place after retrieval, such
|
||||
// as IMediaResource (resolved via ResolveMediaResources), IVector, and IMap.
|
||||
// A JSON-backed getter would deserialize a fresh copy on each call, losing
|
||||
// those in-place mutations.
|
||||
//
|
||||
// When adding a new setting, use the JSON-backed macros unless the type requires
|
||||
// in-place mutation. Existing backing-field settings should be migrated to
|
||||
// JSON-backed as their mutation patterns are resolved.
|
||||
//
|
||||
// Available macros (JSON-backed):
|
||||
// INHERITABLE_SETTING(projectedType, type, name, jsonKey, ...)
|
||||
// INHERITABLE_SETTING_WITH_LOGGING(projectedType, type, name, jsonKey, ...)
|
||||
// INHERITABLE_NULLABLE_SETTING(projectedType, type, name, jsonKey, ...)
|
||||
// _BASE_INHERITABLE_SETTING(projectedType, type, name, jsonKey, ...)
|
||||
//
|
||||
// Available macros (mutable backing-field):
|
||||
// INHERITABLE_MUTABLE_SETTING(projectedType, type, name, ...)
|
||||
// INHERITABLE_NULLABLE_MUTABLE_SETTING(projectedType, type, name, ...)
|
||||
//
|
||||
// =============================================================================
|
||||
|
||||
// =============================================================================
|
||||
// JSON-backed inheritable settings
|
||||
// =============================================================================
|
||||
// No std::optional<T> backing field — _json is the source of truth.
|
||||
// Getters deserialize from _json via ConversionTrait on each call.
|
||||
// Setters serialize to _json via SetValueForKey.
|
||||
// Has/Clear check/modify _json directly.
|
||||
|
||||
// Shared base for JSON-backed inheritable settings.
|
||||
// Provides Has, Clear, OverrideSource, and the private implementation helpers.
|
||||
// Does NOT provide the public getter/setter — use INHERITABLE_SETTING or
|
||||
// INHERITABLE_SETTING_WITH_LOGGING which add those on top of this base.
|
||||
#define _BASE_INHERITABLE_SETTING(projectedType, type, name, jsonKey, ...) \
|
||||
public: \
|
||||
/* Returns true if the user explicitly set the value, false otherwise */ \
|
||||
bool Has##name() const \
|
||||
{ \
|
||||
return _json.isMember(jsonKey) && !_json[jsonKey].isNull(); \
|
||||
} \
|
||||
\
|
||||
/* Returns the object that provides the resolved value */ \
|
||||
projectedType name##OverrideSource() \
|
||||
{ \
|
||||
/*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; \
|
||||
} \
|
||||
\
|
||||
/* Clear the user set value */ \
|
||||
void Clear##name() \
|
||||
{ \
|
||||
_json.removeMember(JsonKey(jsonKey)); \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
/* Read value from this layer's _json only (no parent walk) */ \
|
||||
std::optional<type> _get##name##FromThisLayer() const \
|
||||
{ \
|
||||
if (Has##name()) \
|
||||
{ \
|
||||
type val{ __VA_ARGS__ }; \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::GetValueForKey( \
|
||||
_json, jsonKey, val); \
|
||||
return val; \
|
||||
} \
|
||||
return std::nullopt; \
|
||||
} \
|
||||
\
|
||||
/* Resolve the value: this layer --> parents --> nullopt */ \
|
||||
std::optional<type> _get##name##Impl() const \
|
||||
{ \
|
||||
/*return value from this layer*/ \
|
||||
if (auto val{ _get##name##FromThisLayer() }) \
|
||||
{ \
|
||||
return val; \
|
||||
} \
|
||||
\
|
||||
/*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; \
|
||||
} \
|
||||
\
|
||||
/* Find the object that provides the resolved value */ \
|
||||
auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (Has##name()) \
|
||||
{ \
|
||||
return get_strong(); \
|
||||
} \
|
||||
\
|
||||
/*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; \
|
||||
} \
|
||||
\
|
||||
/* Find the override source and the value it provides */ \
|
||||
auto _get##name##OverrideSourceAndValueImpl() \
|
||||
->std::pair<decltype(get_strong()), std::optional<type>> \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (Has##name()) \
|
||||
{ \
|
||||
return { get_strong(), _get##name##FromThisLayer() }; \
|
||||
} \
|
||||
\
|
||||
/*iterate through parents to find one with a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return { source, source->_get##name##FromThisLayer() }; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return { nullptr, std::nullopt }; \
|
||||
}
|
||||
|
||||
// Use INHERITABLE_SETTING and INHERITABLE_NULLABLE_SETTING to implement
|
||||
// standard functions for inheritable settings. This is similar to the WINRT_PROPERTY macro,
|
||||
// except...
|
||||
// - Has<NAME>(): checks if the user explicitly set a value for this setting
|
||||
// - <NAME>OverrideSource(): 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, ...) \
|
||||
_BASE_INHERITABLE_SETTING(projectedType, std::optional<type>, name, ...) \
|
||||
public: \
|
||||
/* 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; \
|
||||
// JSON-backed inheritable setting.
|
||||
// Getter returns the resolved value: this layer --> inherited --> default.
|
||||
// Setter writes the value to _json.
|
||||
#define INHERITABLE_SETTING(projectedType, type, name, jsonKey, ...) \
|
||||
_BASE_INHERITABLE_SETTING(projectedType, type, name, jsonKey, __VA_ARGS__) \
|
||||
public: \
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: this layer --> inherited value --> default */ \
|
||||
type name() const \
|
||||
{ \
|
||||
const auto val{ _get##name##Impl() }; \
|
||||
return val ? *val : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
/* Overwrite the user set value in _json */ \
|
||||
void name(const type& value) \
|
||||
{ \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey( \
|
||||
_json, jsonKey, value); \
|
||||
}
|
||||
|
||||
#define INHERITABLE_SETTING_WITH_LOGGING(projectedType, type, name, jsonKey, ...) \
|
||||
_BASE_INHERITABLE_SETTING(projectedType, std::optional<type>, name, ...) \
|
||||
public: \
|
||||
/* 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) \
|
||||
{ \
|
||||
if (!_##name.has_value() || _##name.value() != value) \
|
||||
{ \
|
||||
_logSettingSet(jsonKey); \
|
||||
} \
|
||||
_##name = value; \
|
||||
// JSON-backed inheritable setting with change logging.
|
||||
// Same as INHERITABLE_SETTING, but the setter also calls _logSettingSet(jsonKey)
|
||||
// when the value actually changes (for settings change telemetry).
|
||||
#define INHERITABLE_SETTING_WITH_LOGGING(projectedType, type, name, jsonKey, ...) \
|
||||
_BASE_INHERITABLE_SETTING(projectedType, type, name, jsonKey, __VA_ARGS__) \
|
||||
public: \
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: this layer --> inherited value --> default */ \
|
||||
type name() const \
|
||||
{ \
|
||||
const auto val{ _get##name##Impl() }; \
|
||||
return val ? *val : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
/* Overwrite the user set value, log the change, and write to _json */ \
|
||||
void name(const type& value) \
|
||||
{ \
|
||||
const auto existingVal{ _get##name##FromThisLayer() }; \
|
||||
if (!existingVal.has_value() || existingVal.value() != value) \
|
||||
{ \
|
||||
_logSettingSet(jsonKey); \
|
||||
} \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey( \
|
||||
_json, jsonKey, value); \
|
||||
}
|
||||
|
||||
// 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, ...) \
|
||||
_BASE_INHERITABLE_SETTING(projectedType, NullableSetting<type>, name, ...) \
|
||||
public: \
|
||||
/* 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 }; \
|
||||
} \
|
||||
// JSON-backed nullable inheritable setting.
|
||||
// For settings where null is a valid explicit value (e.g., Foreground color).
|
||||
// Key absent → inherit from parent
|
||||
// Key present + null → explicitly "no value" (getter returns nullptr)
|
||||
// Key present + value → has a value
|
||||
// Getter returns IReference<T>.
|
||||
#define INHERITABLE_NULLABLE_SETTING(projectedType, type, name, jsonKey, ...) \
|
||||
public: \
|
||||
/* Key presence means explicitly set (even if value is null) */ \
|
||||
bool Has##name() const \
|
||||
{ \
|
||||
return _json.isMember(jsonKey); \
|
||||
} \
|
||||
\
|
||||
/* Returns the object that provides the resolved value */ \
|
||||
projectedType name##OverrideSource() \
|
||||
{ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return *source; \
|
||||
} \
|
||||
} \
|
||||
return nullptr; \
|
||||
} \
|
||||
\
|
||||
/* Clear the user set value (remove key entirely → inherit from parent) */ \
|
||||
void Clear##name() \
|
||||
{ \
|
||||
_json.removeMember(JsonKey(jsonKey)); \
|
||||
} \
|
||||
\
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: this layer --> inherited value --> default */ \
|
||||
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 in _json */ \
|
||||
void name(const winrt::Windows::Foundation::IReference<type>& value) \
|
||||
{ \
|
||||
if (value) \
|
||||
{ \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey( \
|
||||
_json, jsonKey, value.Value()); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
/* explicitly set to null (not the same as clearing) */ \
|
||||
_json[JsonKey(jsonKey)] = Json::nullValue; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
/* Read nullable value from this layer's _json only (no parent walk) */ \
|
||||
/* Returns: nullopt = not set. optional{nullopt} = explicitly null. */ \
|
||||
/* optional{optional{val}} = has value. */ \
|
||||
NullableSetting<type> _get##name##FromThisLayer() const \
|
||||
{ \
|
||||
if (_json.isMember(jsonKey)) \
|
||||
{ \
|
||||
if (_json[jsonKey].isNull()) \
|
||||
{ \
|
||||
return std::optional<type>{ std::nullopt }; \
|
||||
} \
|
||||
type val{}; \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::GetValueForKey( \
|
||||
_json, jsonKey, val); \
|
||||
return std::optional<type>{ val }; \
|
||||
} \
|
||||
return std::nullopt; \
|
||||
} \
|
||||
\
|
||||
/* Resolve the nullable value: this layer --> parents --> nullopt */ \
|
||||
NullableSetting<type> _get##name##Impl() const \
|
||||
{ \
|
||||
if (auto val{ _get##name##FromThisLayer() }) \
|
||||
{ \
|
||||
return val; \
|
||||
} \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto val{ parent->_get##name##Impl() }) \
|
||||
{ \
|
||||
return val; \
|
||||
} \
|
||||
} \
|
||||
return std::nullopt; \
|
||||
} \
|
||||
\
|
||||
/* Find the object that provides the resolved value */ \
|
||||
auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \
|
||||
{ \
|
||||
if (Has##name()) \
|
||||
{ \
|
||||
return get_strong(); \
|
||||
} \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return source; \
|
||||
} \
|
||||
} \
|
||||
return nullptr; \
|
||||
} \
|
||||
\
|
||||
/* Find the override source and the value it provides */ \
|
||||
auto _get##name##OverrideSourceAndValueImpl() \
|
||||
->std::pair<decltype(get_strong()), NullableSetting<type>> \
|
||||
{ \
|
||||
if (Has##name()) \
|
||||
{ \
|
||||
return { get_strong(), _get##name##FromThisLayer() }; \
|
||||
} \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return { source, source->_get##name##FromThisLayer() }; \
|
||||
} \
|
||||
} \
|
||||
return { nullptr, std::nullopt }; \
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Mutable backing-field inheritable settings
|
||||
// =============================================================================
|
||||
// These use a std::optional<T> backing field instead of _json.
|
||||
//
|
||||
// Use these ONLY for types that callers mutate in place after retrieval.
|
||||
// A JSON-backed getter would deserialize a fresh copy on each call, so
|
||||
// in-place mutations (e.g. ResolveMediaResources) would be lost.
|
||||
//
|
||||
// Current uses:
|
||||
// Profile: Icon, EnvironmentVariables, BellSound, UnfocusedAppearance
|
||||
// GlobalAppSettings: DisabledProfileSources, NewTabMenu
|
||||
// AppearanceConfig: PixelShaderPath, PixelShaderImagePath, BackgroundImagePath,
|
||||
// DarkColorSchemeName, LightColorSchemeName
|
||||
// FontConfig: FontAxes, FontFeatures
|
||||
|
||||
// Shared base for backing-field inheritable settings.
|
||||
// Provides Has, Clear, OverrideSource, and the private implementation helpers.
|
||||
#define _BASE_INHERITABLE_MUTABLE_SETTING(projectedType, storageType, name, ...) \
|
||||
public: \
|
||||
/* Returns true if the user explicitly set the value, false otherwise */ \
|
||||
bool Has##name() const \
|
||||
{ \
|
||||
return _##name.has_value(); \
|
||||
} \
|
||||
\
|
||||
/* Returns the object that provides the resolved value */ \
|
||||
projectedType name##OverrideSource() \
|
||||
{ \
|
||||
/*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; \
|
||||
} \
|
||||
\
|
||||
/* Clear the user set value */ \
|
||||
void Clear##name() \
|
||||
{ \
|
||||
_##name = std::nullopt; \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
storageType _##name{ std::nullopt }; \
|
||||
\
|
||||
/* Resolve the value: this layer --> parents --> nullopt */ \
|
||||
storageType _get##name##Impl() const \
|
||||
{ \
|
||||
/*return user set value*/ \
|
||||
if (_##name) \
|
||||
{ \
|
||||
return _##name; \
|
||||
} \
|
||||
\
|
||||
/*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; \
|
||||
} \
|
||||
\
|
||||
/* Find the object that provides the resolved value */ \
|
||||
auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (_##name) \
|
||||
{ \
|
||||
return get_strong(); \
|
||||
} \
|
||||
\
|
||||
/*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; \
|
||||
} \
|
||||
\
|
||||
/* Find the override source and the value it provides */ \
|
||||
auto _get##name##OverrideSourceAndValueImpl() \
|
||||
->std::pair<decltype(get_strong()), storageType> \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (_##name) \
|
||||
{ \
|
||||
return { get_strong(), _##name }; \
|
||||
} \
|
||||
\
|
||||
/*iterate through parents to find one with a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return { source, source->_##name }; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return { nullptr, std::nullopt }; \
|
||||
}
|
||||
|
||||
// Mutable backing-field setting.
|
||||
// Getter returns the resolved value: this layer --> inherited --> default.
|
||||
// Setter writes to the std::optional<T> backing field.
|
||||
#define INHERITABLE_MUTABLE_SETTING(projectedType, type, name, ...) \
|
||||
_BASE_INHERITABLE_MUTABLE_SETTING(projectedType, std::optional<type>, name, ...) \
|
||||
public: \
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: this layer --> inherited value --> default */ \
|
||||
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; \
|
||||
}
|
||||
|
||||
// Mutable nullable backing-field setting.
|
||||
// Same as INHERITABLE_MUTABLE_SETTING, but for optional settings where null is
|
||||
// a valid explicit value. Getter returns IReference<T>.
|
||||
#define INHERITABLE_NULLABLE_MUTABLE_SETTING(projectedType, type, name, ...) \
|
||||
_BASE_INHERITABLE_MUTABLE_SETTING(projectedType, NullableSetting<type>, name, ...) \
|
||||
public: \
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: this layer --> inherited value --> default */ \
|
||||
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) \
|
||||
{ \
|
||||
_##name = std::optional<type>{ value.Value() }; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
/* explicitly set to null (not the same as clearing) */ \
|
||||
_##name = std::optional<type>{ std::nullopt }; \
|
||||
} \
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// IMediaResource settings with backing fields for resolution lifecycle.
|
||||
// =============================================================================
|
||||
//
|
||||
// Use for settings of type IMediaResource that need in-place resolution
|
||||
// (e.g., Icon, PixelShaderPath, BackgroundImagePath). The backing field
|
||||
// holds the runtime-resolved object. _json is the source of truth for
|
||||
// serialization. The setter dual-writes to both.
|
||||
//
|
||||
// Parameters:
|
||||
// projectedType - the WinRT projected type (e.g., Model::Profile)
|
||||
// name - the setting name (e.g., Icon)
|
||||
// jsonKey - the JSON key (e.g., "icon")
|
||||
// ... - default value (e.g., implementation::MediaResource::Empty())
|
||||
//
|
||||
#define INHERITABLE_MEDIA_RESOURCE_SETTING(projectedType, name, jsonKey, ...) \
|
||||
_BASE_INHERITABLE_MUTABLE_SETTING(projectedType, std::optional<IMediaResource>, name, \
|
||||
implementation::MediaResource::Empty()) \
|
||||
public: \
|
||||
/* Returns the resolved value: this layer --> inherited --> default */ \
|
||||
IMediaResource name() const \
|
||||
{ \
|
||||
const auto v{ _get##name##Impl() }; \
|
||||
return v ? *v : IMediaResource{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
/* Dual-write: backing field (for resolution) + _json (for serialization) */ \
|
||||
void name(const IMediaResource& value) \
|
||||
{ \
|
||||
_##name = value; \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey(_json, jsonKey, value); \
|
||||
}
|
||||
|
||||
@@ -250,6 +250,28 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
|
||||
SetValueForKey(json, key, target, ConversionTrait<std::decay_t<T>>{});
|
||||
}
|
||||
|
||||
// CopyKeyIfPresent: copies a key's raw Json::Value from source to target
|
||||
// if it exists in source. No type conversion — just raw JSON copy.
|
||||
// Used in ToJson() to copy JSON-backed settings from _json to output.
|
||||
inline void CopyKeyIfPresent(const Json::Value& source, Json::Value& target, const std::string_view key)
|
||||
{
|
||||
if (source.isMember(JsonKey(key)))
|
||||
{
|
||||
target[JsonKey(key)] = source[JsonKey(key)];
|
||||
}
|
||||
}
|
||||
|
||||
// MergeJsonKeys: copies all keys from source into target (key-wise merge).
|
||||
// Existing keys in target are overwritten by source values.
|
||||
// Used in LayerJson() to merge incoming JSON into stored _json.
|
||||
inline void MergeJsonKeys(const Json::Value& source, Json::Value& target)
|
||||
{
|
||||
for (const auto& key : source.getMemberNames())
|
||||
{
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<std::string>
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ Module Name:
|
||||
Abstract:
|
||||
- Contains most of the settings within Terminal Settings Model (global, profile, font, appearance)
|
||||
- To add a new setting to any one of those classes, simply add it to the respective list below, following the macro format
|
||||
- Also defines SettingKey enums for generic setting access
|
||||
|
||||
Author(s):
|
||||
- Pankaj Bhojwani - October 2021
|
||||
@@ -62,16 +63,15 @@ Author(s):
|
||||
X(Model::WindowingMode, WindowingBehavior, "windowingBehavior", Model::WindowingMode::UseNew) \
|
||||
X(bool, MinimizeToNotificationArea, "minimizeToNotificationArea", false) \
|
||||
X(bool, AlwaysShowNotificationIcon, "alwaysShowNotificationIcon", false) \
|
||||
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, "disabledProfileSources", nullptr) \
|
||||
X(bool, ShowAdminShield, "showAdminShield", true) \
|
||||
X(bool, TrimPaste, "trimPaste", true) \
|
||||
X(bool, EnableColorSelection, "experimental.enableColorSelection", false) \
|
||||
X(bool, EnableShellCompletionMenu, "experimental.enableShellCompletionMenu", false) \
|
||||
X(bool, EnableUnfocusedAcrylic, "compatibility.enableUnfocusedAcrylic", true) \
|
||||
X(winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, NewTabMenu, "newTabMenu", winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} })) \
|
||||
X(bool, AllowHeadless, "compatibility.allowHeadless", false) \
|
||||
X(hstring, SearchWebDefaultQueryUrl, "searchWebDefaultQueryUrl", L"https://www.bing.com/search?q=%22%s%22") \
|
||||
X(bool, ShowTabsFullscreen, "showTabsFullscreen", false)
|
||||
X(bool, ShowTabsFullscreen, "showTabsFullscreen", false) \
|
||||
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, "disabledProfileSources", nullptr)
|
||||
|
||||
// Also add these settings to:
|
||||
// * Profile.idl
|
||||
@@ -88,15 +88,12 @@ Author(s):
|
||||
X(Microsoft::Terminal::Control::ScrollbarState, ScrollState, "scrollbarState", Microsoft::Terminal::Control::ScrollbarState::Visible) \
|
||||
X(Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, "antialiasingMode", Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \
|
||||
X(hstring, StartingDirectory, "startingDirectory") \
|
||||
X(IMediaResource, Icon, "icon", implementation::MediaResource::FromString(L"\uE756")) \
|
||||
X(bool, SuppressApplicationTitle, "suppressApplicationTitle", false) \
|
||||
X(guid, ConnectionType, "connectionType") \
|
||||
X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic) \
|
||||
X(hstring, TabTitle, "tabTitle") \
|
||||
X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \
|
||||
X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \
|
||||
X(bool, RightClickContextMenu, "rightClickContextMenu", false) \
|
||||
X(Windows::Foundation::Collections::IVector<IMediaResource>, BellSound, "bellSound", nullptr) \
|
||||
X(bool, Elevate, "elevate", false) \
|
||||
X(bool, AutoMarkPrompts, "autoMarkPrompts", true) \
|
||||
X(bool, ShowMarks, "showMarksOnScrollbar", false) \
|
||||
@@ -109,7 +106,8 @@ Author(s):
|
||||
X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \
|
||||
X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \
|
||||
X(hstring, DragDropDelimiter, "dragDropDelimiter", L" ") \
|
||||
X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None)
|
||||
X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None) \
|
||||
X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr)
|
||||
|
||||
// Intentionally omitted Profile settings:
|
||||
// * Name
|
||||
@@ -124,12 +122,12 @@ Author(s):
|
||||
X(hstring, FontFace, "face", DEFAULT_FONT_FACE) \
|
||||
X(float, FontSize, "size", DEFAULT_FONT_SIZE) \
|
||||
X(winrt::Windows::UI::Text::FontWeight, FontWeight, "weight", DEFAULT_FONT_WEIGHT) \
|
||||
X(IFontAxesMap, FontAxes, "axes") \
|
||||
X(IFontFeatureMap, FontFeatures, "features") \
|
||||
X(bool, EnableBuiltinGlyphs, "builtinGlyphs", true) \
|
||||
X(bool, EnableColorGlyphs, "colorGlyphs", true) \
|
||||
X(winrt::hstring, CellWidth, "cellWidth") \
|
||||
X(winrt::hstring, CellHeight, "cellHeight")
|
||||
X(winrt::hstring, CellHeight, "cellHeight") \
|
||||
X(IFontAxesMap, FontAxes, "axes") \
|
||||
X(IFontFeatureMap, FontFeatures, "features")
|
||||
|
||||
#define MTSM_APPEARANCE_SETTINGS(X) \
|
||||
X(Core::CursorStyle, CursorShape, "cursorShape", Core::CursorStyle::Bar) \
|
||||
@@ -137,10 +135,7 @@ Author(s):
|
||||
X(float, BackgroundImageOpacity, "backgroundImageOpacity", 1.0f) \
|
||||
X(winrt::Windows::UI::Xaml::Media::Stretch, BackgroundImageStretchMode, "backgroundImageStretchMode", winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill) \
|
||||
X(bool, RetroTerminalEffect, "experimental.retroTerminalEffect", false) \
|
||||
X(IMediaResource, PixelShaderPath, "experimental.pixelShaderPath", implementation::MediaResource::Empty()) \
|
||||
X(IMediaResource, PixelShaderImagePath, "experimental.pixelShaderImagePath", implementation::MediaResource::Empty()) \
|
||||
X(ConvergedAlignment, BackgroundImageAlignment, "backgroundImageAlignment", ConvergedAlignment::Horizontal_Center | ConvergedAlignment::Vertical_Center) \
|
||||
X(IMediaResource, BackgroundImagePath, "backgroundImage", implementation::MediaResource::Empty()) \
|
||||
X(Model::IntenseStyle, IntenseTextStyle, "intenseTextStyle", Model::IntenseStyle::Bright) \
|
||||
X(Core::AdjustTextMode, AdjustIndistinguishableColors, "adjustIndistinguishableColors", Core::AdjustTextMode::Automatic) \
|
||||
X(bool, UseAcrylic, "useAcrylic", false)
|
||||
@@ -174,3 +169,163 @@ Author(s):
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, UnfocusedBackground, "unfocusedBackground", nullptr) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::IconStyle, IconStyle, "iconStyle", winrt::Microsoft::Terminal::Settings::Model::IconStyle::Default) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility, ShowCloseButton, "showCloseButton", winrt::Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility::Always)
|
||||
|
||||
// SettingKey enums: provide a generic way to reference settings by key.
|
||||
// Generated from the MTSM macros above. Also includes special-cased settings
|
||||
// that are not part of the macro lists (prefixed with _ to avoid name collisions).
|
||||
// SETTINGS_SIZE is a sentinel value that must remain last in each enum.
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
#define _MTSM_ENUM_VALUE(type, name, jsonKey, ...) name,
|
||||
|
||||
enum class ProfileSettingKey : int
|
||||
{
|
||||
MTSM_PROFILE_SETTINGS(_MTSM_ENUM_VALUE)
|
||||
// Special-cased profile settings (not in MTSM_PROFILE_SETTINGS due to
|
||||
// custom JSON parsing, but included here for completeness)
|
||||
_Name,
|
||||
_Guid,
|
||||
_Source,
|
||||
_Hidden,
|
||||
_Padding,
|
||||
_TabColor,
|
||||
// Complex/mutable settings with backing fields
|
||||
_Icon,
|
||||
_BellSound,
|
||||
SETTINGS_SIZE
|
||||
};
|
||||
|
||||
enum class GlobalSettingKey : int
|
||||
{
|
||||
MTSM_GLOBAL_SETTINGS(_MTSM_ENUM_VALUE)
|
||||
// Special-cased global settings
|
||||
_UnparsedDefaultProfile,
|
||||
// Complex/mutable settings with backing fields
|
||||
_NewTabMenu,
|
||||
SETTINGS_SIZE
|
||||
};
|
||||
|
||||
enum class FontSettingKey : int
|
||||
{
|
||||
MTSM_FONT_SETTINGS(_MTSM_ENUM_VALUE)
|
||||
SETTINGS_SIZE
|
||||
};
|
||||
|
||||
enum class AppearanceSettingKey : int
|
||||
{
|
||||
MTSM_APPEARANCE_SETTINGS(_MTSM_ENUM_VALUE)
|
||||
// Special-cased appearance settings
|
||||
_Foreground,
|
||||
_Background,
|
||||
_SelectionBackground,
|
||||
_CursorColor,
|
||||
_Opacity,
|
||||
_DarkColorSchemeName,
|
||||
_LightColorSchemeName,
|
||||
// Complex/mutable settings with backing fields
|
||||
_PixelShaderPath,
|
||||
_PixelShaderImagePath,
|
||||
_BackgroundImagePath,
|
||||
SETTINGS_SIZE
|
||||
};
|
||||
|
||||
#undef _MTSM_ENUM_VALUE
|
||||
|
||||
// JSON key lookup: returns the JSON key string for a given SettingKey.
|
||||
// Generated from the same macros to maintain a single source of truth.
|
||||
constexpr std::string_view JsonKeyForSetting(ProfileSettingKey key)
|
||||
{
|
||||
#define _MTSM_KEY_CASE(type, name, jsonKey, ...) \
|
||||
case ProfileSettingKey::name: \
|
||||
return jsonKey;
|
||||
switch (key)
|
||||
{
|
||||
MTSM_PROFILE_SETTINGS(_MTSM_KEY_CASE)
|
||||
case ProfileSettingKey::_Name:
|
||||
return "name";
|
||||
case ProfileSettingKey::_Guid:
|
||||
return "guid";
|
||||
case ProfileSettingKey::_Source:
|
||||
return "source";
|
||||
case ProfileSettingKey::_Hidden:
|
||||
return "hidden";
|
||||
case ProfileSettingKey::_Padding:
|
||||
return "padding";
|
||||
case ProfileSettingKey::_TabColor:
|
||||
return "tabColor";
|
||||
case ProfileSettingKey::_Icon:
|
||||
return "icon";
|
||||
case ProfileSettingKey::_BellSound:
|
||||
return "bellSound";
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
#undef _MTSM_KEY_CASE
|
||||
}
|
||||
|
||||
constexpr std::string_view JsonKeyForSetting(GlobalSettingKey key)
|
||||
{
|
||||
#define _MTSM_KEY_CASE(type, name, jsonKey, ...) \
|
||||
case GlobalSettingKey::name: \
|
||||
return jsonKey;
|
||||
switch (key)
|
||||
{
|
||||
MTSM_GLOBAL_SETTINGS(_MTSM_KEY_CASE)
|
||||
case GlobalSettingKey::_UnparsedDefaultProfile:
|
||||
return "defaultProfile";
|
||||
case GlobalSettingKey::_NewTabMenu:
|
||||
return "newTabMenu";
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
#undef _MTSM_KEY_CASE
|
||||
}
|
||||
|
||||
constexpr std::string_view JsonKeyForSetting(FontSettingKey key)
|
||||
{
|
||||
#define _MTSM_KEY_CASE(type, name, jsonKey, ...) \
|
||||
case FontSettingKey::name: \
|
||||
return jsonKey;
|
||||
switch (key)
|
||||
{
|
||||
MTSM_FONT_SETTINGS(_MTSM_KEY_CASE)
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
#undef _MTSM_KEY_CASE
|
||||
}
|
||||
|
||||
constexpr std::string_view JsonKeyForSetting(AppearanceSettingKey key)
|
||||
{
|
||||
#define _MTSM_KEY_CASE(type, name, jsonKey, ...) \
|
||||
case AppearanceSettingKey::name: \
|
||||
return jsonKey;
|
||||
switch (key)
|
||||
{
|
||||
MTSM_APPEARANCE_SETTINGS(_MTSM_KEY_CASE)
|
||||
case AppearanceSettingKey::_Foreground:
|
||||
return "foreground";
|
||||
case AppearanceSettingKey::_Background:
|
||||
return "background";
|
||||
case AppearanceSettingKey::_SelectionBackground:
|
||||
return "selectionBackground";
|
||||
case AppearanceSettingKey::_CursorColor:
|
||||
return "cursorColor";
|
||||
case AppearanceSettingKey::_Opacity:
|
||||
return "opacity";
|
||||
case AppearanceSettingKey::_DarkColorSchemeName:
|
||||
return "colorScheme";
|
||||
case AppearanceSettingKey::_LightColorSchemeName:
|
||||
return "colorScheme";
|
||||
case AppearanceSettingKey::_PixelShaderPath:
|
||||
return "experimental.pixelShaderPath";
|
||||
case AppearanceSettingKey::_PixelShaderImagePath:
|
||||
return "experimental.pixelShaderImagePath";
|
||||
case AppearanceSettingKey::_BackgroundImagePath:
|
||||
return "backgroundImage";
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
#undef _MTSM_KEY_CASE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,9 +37,9 @@ static constexpr std::string_view LegacyAutoMarkPromptsKey{ "experimental.autoMa
|
||||
static constexpr std::string_view LegacyShowMarksKey{ "experimental.showMarksOnScrollbar" };
|
||||
static constexpr std::string_view LegacyRightClickContextMenuKey{ "experimental.rightClickContextMenu" };
|
||||
|
||||
Profile::Profile(guid guid) noexcept :
|
||||
_Guid(guid)
|
||||
Profile::Profile(guid guid)
|
||||
{
|
||||
Guid(guid);
|
||||
}
|
||||
|
||||
void Profile::CreateUnfocusedAppearance()
|
||||
@@ -105,25 +105,23 @@ winrt::com_ptr<Profile> Profile::CopySettings() const
|
||||
profile->_Deleted = _Deleted;
|
||||
profile->_Orphaned = _Orphaned;
|
||||
profile->_Updates = _Updates;
|
||||
profile->_Guid = _Guid;
|
||||
profile->_Name = _Name;
|
||||
profile->_Source = _Source;
|
||||
profile->_Hidden = _Hidden;
|
||||
profile->_TabColor = _TabColor;
|
||||
profile->_Padding = _Padding;
|
||||
|
||||
profile->_Origin = _Origin;
|
||||
profile->_FontInfo = *fontInfo;
|
||||
profile->_DefaultAppearance = *defaultAppearance;
|
||||
profile->_json = _json;
|
||||
|
||||
#define PROFILE_SETTINGS_COPY(type, name, jsonKey, ...) \
|
||||
profile->_##name = _##name;
|
||||
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_COPY)
|
||||
#undef PROFILE_SETTINGS_COPY
|
||||
// JSON-backed settings (Name, Source, Hidden, Guid, Padding, TabColor, MTSM settings)
|
||||
// all live in _json, which is already deep-copied above. No per-setting copy needed.
|
||||
|
||||
// Complex/mutable settings — backing fields for resolution lifecycle.
|
||||
// _json (copied above) is the source of truth; backing fields hold resolved runtime state.
|
||||
profile->_Icon = _Icon;
|
||||
|
||||
|
||||
// BellSound is an IVector<>, so we need to make a new vector pointing at the same objects
|
||||
if (_BellSound)
|
||||
{
|
||||
// BellSound is an IVector<>, so we need to make a new vector pointing at the same objects
|
||||
profile->_BellSound = winrt::single_threaded_vector(wil::to_vector(*_BellSound));
|
||||
}
|
||||
|
||||
@@ -169,6 +167,10 @@ winrt::com_ptr<winrt::Microsoft::Terminal::Settings::Model::implementation::Prof
|
||||
// <none>
|
||||
void Profile::LayerJson(const Json::Value& json)
|
||||
{
|
||||
// Merge incoming JSON keys into stored _json (key-wise, not replacement).
|
||||
// This preserves keys from earlier LayerJson calls that aren't in the new JSON.
|
||||
JsonUtils::MergeJsonKeys(json, _json);
|
||||
|
||||
// Appearance Settings
|
||||
auto defaultAppearanceImpl = winrt::get_self<implementation::AppearanceConfig>(_DefaultAppearance);
|
||||
defaultAppearanceImpl->LayerJson(json);
|
||||
@@ -177,37 +179,53 @@ void Profile::LayerJson(const Json::Value& json)
|
||||
auto fontInfoImpl = winrt::get_self<implementation::FontConfig>(_FontInfo);
|
||||
fontInfoImpl->LayerJson(json);
|
||||
|
||||
// Profile-specific Settings
|
||||
JsonUtils::GetValueForKey(json, NameKey, _Name);
|
||||
// Profile-specific settings: now JSON-backed. Values are already in _json
|
||||
// from the merge step above. Only need to handle non-JSON fields and logging.
|
||||
JsonUtils::GetValueForKey(json, UpdatesKey, _Updates);
|
||||
JsonUtils::GetValueForKey(json, GuidKey, _Guid);
|
||||
|
||||
// Normalize Padding: allow integer → string coercion (e.g. "padding": 8 → "8")
|
||||
if (_json.isMember(JsonKey(PaddingKey)) && _json[JsonKey(PaddingKey)].isInt())
|
||||
{
|
||||
_json[JsonKey(PaddingKey)] = std::to_string(_json[JsonKey(PaddingKey)].asInt());
|
||||
}
|
||||
|
||||
// Make sure Source is before Hidden! We use that to exclude false positives from the settings logger!
|
||||
JsonUtils::GetValueForKey(json, SourceKey, _Source);
|
||||
JsonUtils::GetValueForKey(json, HiddenKey, _Hidden);
|
||||
_logSettingIfSet(HiddenKey, _Hidden.has_value());
|
||||
|
||||
// Padding was never specified as an integer, but it was a common working mistake.
|
||||
// Allow it to be permissive.
|
||||
JsonUtils::GetValueForKey(json, PaddingKey, _Padding, JsonUtils::OptionalConverter<hstring, JsonUtils::PermissiveStringConverter<std::wstring>>{});
|
||||
_logSettingIfSet(PaddingKey, _Padding.has_value());
|
||||
|
||||
JsonUtils::GetValueForKey(json, TabColorKey, _TabColor);
|
||||
_logSettingIfSet(TabColorKey, _TabColor.has_value());
|
||||
_logSettingIfSet(HiddenKey, HasHidden());
|
||||
_logSettingIfSet(PaddingKey, HasPadding());
|
||||
_logSettingIfSet(TabColorKey, HasTabColor());
|
||||
|
||||
// Try to load some legacy keys, to migrate them.
|
||||
// Done _before_ the MTSM_PROFILE_SETTINGS, which have the updated keys.
|
||||
JsonUtils::GetValueForKey(json, LegacyShowMarksKey, _ShowMarks);
|
||||
JsonUtils::GetValueForKey(json, LegacyAutoMarkPromptsKey, _AutoMarkPrompts);
|
||||
JsonUtils::GetValueForKey(json, LegacyRightClickContextMenuKey, _RightClickContextMenu);
|
||||
// Normalize legacy keys into canonical _json keys so JSON-backed getters find them.
|
||||
// Done _before_ the MTSM_PROFILE_SETTINGS logging, which have the updated keys.
|
||||
static constexpr std::pair<std::string_view, std::string_view> legacyKeyMappings[] = {
|
||||
{ LegacyShowMarksKey, "showMarksOnScrollbar" },
|
||||
{ LegacyAutoMarkPromptsKey, "autoMarkPrompts" },
|
||||
{ LegacyRightClickContextMenuKey, "rightClickContextMenu" },
|
||||
};
|
||||
for (const auto& [legacyKey, canonicalKey] : legacyKeyMappings)
|
||||
{
|
||||
if (json.isMember(JsonKey(legacyKey)))
|
||||
{
|
||||
_json[JsonKey(canonicalKey)] = json[JsonKey(legacyKey)];
|
||||
}
|
||||
}
|
||||
|
||||
// MTSM settings are now JSON-backed (no backing fields).
|
||||
// Values are already in _json from the merge step above.
|
||||
// We only need to log which settings were set in this layer.
|
||||
#define PROFILE_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::GetValueForKey(json, jsonKey, _##name); \
|
||||
_logSettingIfSet(jsonKey, _##name.has_value());
|
||||
_logSettingIfSet(jsonKey, json.isMember(jsonKey) && !json[jsonKey].isNull());
|
||||
|
||||
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_LAYER_JSON)
|
||||
#undef PROFILE_SETTINGS_LAYER_JSON
|
||||
|
||||
// Complex/mutable settings — backing fields populated from _json for runtime resolution.
|
||||
// _json is the source of truth for serialization; backing fields are for resolution lifecycle.
|
||||
JsonUtils::GetValueForKey(json, "icon", _Icon);
|
||||
_logSettingIfSet("icon", _Icon.has_value());
|
||||
JsonUtils::GetValueForKey(json, "bellSound", _BellSound);
|
||||
_logSettingIfSet("bellSound", _BellSound.has_value());
|
||||
|
||||
if (json.isMember(JsonKey(UnfocusedAppearanceKey)))
|
||||
{
|
||||
auto unfocusedAppearance{ winrt::make_self<implementation::AppearanceConfig>(weak_ref<Model::Profile>(*this)) };
|
||||
@@ -332,25 +350,44 @@ Json::Value Profile::ToJson() const
|
||||
// GH #9962:
|
||||
// If the settings.json was missing, when we load the dynamic profiles, they are completely empty.
|
||||
// This caused us to serialize empty profiles "{}" on accident.
|
||||
const auto writeBasicSettings{ !Source().empty() };
|
||||
const auto writeBasicSettings{ HasSource() };
|
||||
|
||||
// Profile-specific Settings
|
||||
JsonUtils::SetValueForKey(json, NameKey, writeBasicSettings ? Name() : _Name);
|
||||
JsonUtils::SetValueForKey(json, GuidKey, writeBasicSettings ? Guid() : _Guid);
|
||||
JsonUtils::SetValueForKey(json, HiddenKey, writeBasicSettings ? Hidden() : _Hidden);
|
||||
JsonUtils::SetValueForKey(json, SourceKey, writeBasicSettings ? Source() : _Source);
|
||||
// Profile-specific Settings (JSON-backed)
|
||||
if (writeBasicSettings)
|
||||
{
|
||||
// Dynamic profiles: write resolved values so defaults are materialized
|
||||
JsonUtils::SetValueForKey(json, NameKey, Name());
|
||||
JsonUtils::SetValueForKey(json, GuidKey, Guid());
|
||||
JsonUtils::SetValueForKey(json, HiddenKey, Hidden());
|
||||
JsonUtils::SetValueForKey(json, SourceKey, Source());
|
||||
}
|
||||
else
|
||||
{
|
||||
// User-defined profiles: copy local layer from _json
|
||||
for (const auto& key : { NameKey, GuidKey, HiddenKey, SourceKey })
|
||||
{
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, key);
|
||||
}
|
||||
}
|
||||
|
||||
// PermissiveStringConverter is unnecessary for serialization
|
||||
JsonUtils::SetValueForKey(json, PaddingKey, _Padding);
|
||||
// Padding: copy from _json
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, PaddingKey);
|
||||
|
||||
JsonUtils::SetValueForKey(json, TabColorKey, _TabColor);
|
||||
// TabColor: nullable — key presence matters (explicit null is valid)
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, TabColorKey);
|
||||
|
||||
// MTSM profile settings: copy from _json (the source of truth)
|
||||
#define PROFILE_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::SetValueForKey(json, jsonKey, _##name);
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, jsonKey);
|
||||
|
||||
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_TO_JSON)
|
||||
#undef PROFILE_SETTINGS_TO_JSON
|
||||
|
||||
// Complex/mutable settings with backing fields (runtime resolution)
|
||||
// Read from _json (source of truth), not from backing fields
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, "icon");
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, "bellSound");
|
||||
|
||||
if (auto fontJSON = winrt::get_self<FontConfig>(_FontInfo)->ToJson(); !fontJSON.empty())
|
||||
{
|
||||
json[JsonKey(FontInfoKey)] = std::move(fontJSON);
|
||||
@@ -364,6 +401,89 @@ Json::Value Profile::ToJson() const
|
||||
return json;
|
||||
}
|
||||
|
||||
bool Profile::HasSetting(ProfileSettingKey key) const
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _PROFILE_HAS_SETTING(type, name, jsonKey, ...) \
|
||||
case ProfileSettingKey::name: \
|
||||
return Has##name();
|
||||
MTSM_PROFILE_SETTINGS(_PROFILE_HAS_SETTING)
|
||||
#undef _PROFILE_HAS_SETTING
|
||||
case ProfileSettingKey::_Name:
|
||||
return HasName();
|
||||
case ProfileSettingKey::_Guid:
|
||||
return HasGuid();
|
||||
case ProfileSettingKey::_Source:
|
||||
return HasSource();
|
||||
case ProfileSettingKey::_Hidden:
|
||||
return HasHidden();
|
||||
case ProfileSettingKey::_Padding:
|
||||
return HasPadding();
|
||||
case ProfileSettingKey::_TabColor:
|
||||
return HasTabColor();
|
||||
case ProfileSettingKey::_Icon:
|
||||
return HasIcon();
|
||||
case ProfileSettingKey::_BellSound:
|
||||
return HasBellSound();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Profile::ClearSetting(ProfileSettingKey key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _PROFILE_CLEAR_SETTING(type, name, jsonKey, ...) \
|
||||
case ProfileSettingKey::name: \
|
||||
Clear##name(); \
|
||||
break;
|
||||
MTSM_PROFILE_SETTINGS(_PROFILE_CLEAR_SETTING)
|
||||
#undef _PROFILE_CLEAR_SETTING
|
||||
case ProfileSettingKey::_Name:
|
||||
ClearName();
|
||||
break;
|
||||
case ProfileSettingKey::_Guid:
|
||||
ClearGuid();
|
||||
break;
|
||||
case ProfileSettingKey::_Source:
|
||||
ClearSource();
|
||||
break;
|
||||
case ProfileSettingKey::_Hidden:
|
||||
ClearHidden();
|
||||
break;
|
||||
case ProfileSettingKey::_Padding:
|
||||
ClearPadding();
|
||||
break;
|
||||
case ProfileSettingKey::_TabColor:
|
||||
ClearTabColor();
|
||||
break;
|
||||
case ProfileSettingKey::_Icon:
|
||||
ClearIcon();
|
||||
break;
|
||||
case ProfileSettingKey::_BellSound:
|
||||
ClearBellSound();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ProfileSettingKey> Profile::CurrentSettings() const
|
||||
{
|
||||
std::vector<ProfileSettingKey> result;
|
||||
for (auto i = 0; i < static_cast<int>(ProfileSettingKey::SETTINGS_SIZE); i++)
|
||||
{
|
||||
const auto key = static_cast<ProfileSettingKey>(i);
|
||||
if (HasSetting(key))
|
||||
{
|
||||
result.push_back(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Given a commandLine like the following:
|
||||
// * "C:\WINDOWS\System32\cmd.exe"
|
||||
// * "pwsh -WorkingDirectory ~"
|
||||
@@ -502,13 +622,13 @@ void Profile::_logSettingIfSet(const std::string_view& setting, const bool isSet
|
||||
|
||||
// Exclude some false positives from userDefaults.json
|
||||
// NOTE: we can't use the OriginTag here because it hasn't been set yet!
|
||||
const bool isWinPow = _Guid.has_value() && *_Guid == DEFAULT_WINDOWS_POWERSHELL_GUID; //_Name.has_value() && til::equals_insensitive_ascii(*_Name, L"Windows PowerShell");
|
||||
const bool isCmd = _Guid.has_value() && *_Guid == DEFAULT_COMMAND_PROMPT_GUID; //_Name.has_value() && til::equals_insensitive_ascii(*_Name, L"Command Prompt");
|
||||
const bool isACS = _Name.has_value() && til::equals_insensitive_ascii(*_Name, L"Azure Cloud Shell");
|
||||
const bool isWTDynamicProfile = _Source.has_value() && til::starts_with(*_Source, L"Windows.Terminal");
|
||||
const bool settingHiddenToFalse = til::equals_insensitive_ascii(setting, HiddenKey) && _Hidden.has_value() && _Hidden == false;
|
||||
const bool settingCommandlineToWinPow = til::equals_insensitive_ascii(setting, "commandline") && _Commandline.has_value() && til::equals_insensitive_ascii(*_Commandline, L"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe");
|
||||
const bool settingCommandlineToCmd = til::equals_insensitive_ascii(setting, "commandline") && _Commandline.has_value() && til::equals_insensitive_ascii(*_Commandline, L"%SystemRoot%\\System32\\cmd.exe");
|
||||
const bool isWinPow = HasGuid() && Guid() == DEFAULT_WINDOWS_POWERSHELL_GUID;
|
||||
const bool isCmd = HasGuid() && Guid() == DEFAULT_COMMAND_PROMPT_GUID;
|
||||
const bool isACS = HasName() && til::equals_insensitive_ascii(Name(), L"Azure Cloud Shell");
|
||||
const bool isWTDynamicProfile = HasSource() && til::starts_with(Source(), L"Windows.Terminal");
|
||||
const bool settingHiddenToFalse = til::equals_insensitive_ascii(setting, HiddenKey) && HasHidden() && Hidden() == false;
|
||||
const bool settingCommandlineToWinPow = til::equals_insensitive_ascii(setting, "commandline") && HasCommandline() && til::equals_insensitive_ascii(Commandline(), L"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe");
|
||||
const bool settingCommandlineToCmd = til::equals_insensitive_ascii(setting, "commandline") && HasCommandline() && til::equals_insensitive_ascii(Commandline(), L"%SystemRoot%\\System32\\cmd.exe");
|
||||
// clang-format off
|
||||
if (!(isWinPow && (settingHiddenToFalse || settingCommandlineToWinPow))
|
||||
&& !(isCmd && (settingHiddenToFalse || settingCommandlineToCmd))
|
||||
@@ -541,7 +661,7 @@ void Profile::ResolveMediaResources(const Model::MediaResourceResolver& resolver
|
||||
auto newIcon{ MediaResource::FromString(icon->Path()) };
|
||||
const std::wstring cmdline{ NormalizeCommandLine(iconSource->Commandline().c_str()) };
|
||||
newIcon.Resolve(cmdline.c_str() /* c_str: give hstring a chance to find the null terminator */);
|
||||
iconSource->_Icon = std::move(newIcon);
|
||||
iconSource->Icon(std::move(newIcon));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ Author(s):
|
||||
#include "MTSMSettings.h"
|
||||
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
#include <DefaultSettings.h>
|
||||
#include "MediaResourceSupport.h"
|
||||
#include "AppearanceConfig.h"
|
||||
@@ -80,7 +81,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
public:
|
||||
Profile() noexcept = default;
|
||||
Profile(guid guid) noexcept;
|
||||
// Not noexcept: Guid(guid) writes to _json, which allocates.
|
||||
// The old backing-field constructor was noexcept (POD copy into std::optional),
|
||||
// but JSON-backed storage requires heap allocation.
|
||||
Profile(guid guid);
|
||||
|
||||
void CreateUnfocusedAppearance();
|
||||
void DeleteUnfocusedAppearance();
|
||||
@@ -98,6 +102,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
void LayerJson(const Json::Value& json);
|
||||
Json::Value ToJson() const;
|
||||
|
||||
// Generic setting access via SettingKey
|
||||
bool HasSetting(ProfileSettingKey key) const;
|
||||
void ClearSetting(ProfileSettingKey key);
|
||||
std::vector<ProfileSettingKey> CurrentSettings() const;
|
||||
|
||||
hstring EvaluatedStartingDirectory() const;
|
||||
|
||||
Model::IAppearanceConfig DefaultAppearance();
|
||||
@@ -122,16 +131,29 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
WINRT_PROPERTY(OriginTag, Origin, OriginTag::None);
|
||||
WINRT_PROPERTY(guid, Updates);
|
||||
|
||||
// Nullable/optional settings
|
||||
INHERITABLE_NULLABLE_SETTING(Model::Profile, Microsoft::Terminal::Core::Color, TabColor, nullptr);
|
||||
INHERITABLE_SETTING(Model::Profile, Model::IAppearanceConfig, UnfocusedAppearance, nullptr);
|
||||
// Nullable/optional settings (JSON-backed)
|
||||
INHERITABLE_NULLABLE_SETTING(Model::Profile, Microsoft::Terminal::Core::Color, TabColor, "tabColor", nullptr)
|
||||
INHERITABLE_MUTABLE_SETTING(Model::Profile, Model::IAppearanceConfig, UnfocusedAppearance, nullptr);
|
||||
|
||||
// Settings that cannot be put in the macro because of how they are handled in ToJson/LayerJson
|
||||
INHERITABLE_SETTING(Model::Profile, hstring, Name, L"Default");
|
||||
INHERITABLE_SETTING(Model::Profile, hstring, Source);
|
||||
INHERITABLE_SETTING(Model::Profile, bool, Hidden, false);
|
||||
INHERITABLE_SETTING(Model::Profile, guid, Guid, _GenerateGuidForProfile(Name(), Source()));
|
||||
INHERITABLE_SETTING(Model::Profile, hstring, Padding, DEFAULT_PADDING);
|
||||
// Settings that are JSON-backed but need custom handling in ToJson/LayerJson
|
||||
INHERITABLE_SETTING(Model::Profile, hstring, Name, "name", L"Default")
|
||||
INHERITABLE_SETTING(Model::Profile, hstring, Source, "source")
|
||||
INHERITABLE_SETTING(Model::Profile, bool, Hidden, "hidden", false)
|
||||
INHERITABLE_SETTING(Model::Profile, hstring, Padding, "padding", DEFAULT_PADDING)
|
||||
|
||||
// Guid: hand-written JSON-backed (dynamic default)
|
||||
_BASE_INHERITABLE_SETTING(Model::Profile, guid, Guid, "guid")
|
||||
public:
|
||||
guid Guid() const
|
||||
{
|
||||
const auto val{ _getGuidImpl() };
|
||||
return val ? *val : _GenerateGuidForProfile(Name(), Source());
|
||||
}
|
||||
void Guid(const guid& value)
|
||||
{
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey(
|
||||
_json, "guid", value);
|
||||
}
|
||||
|
||||
winrt::hstring SourceBasePath;
|
||||
|
||||
@@ -141,10 +163,33 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_INITIALIZE)
|
||||
#undef PROFILE_SETTINGS_INITIALIZE
|
||||
|
||||
// IMediaResource settings with backing fields for resolution lifecycle.
|
||||
// _json is the source of truth for serialization. Setters dual-write to both
|
||||
// the backing field (for runtime resolution) and _json (for auto-save).
|
||||
INHERITABLE_MEDIA_RESOURCE_SETTING(Model::Profile, Icon, "icon", implementation::MediaResource::FromString(L"\uE756"))
|
||||
|
||||
// BellSound: IVector<IMediaResource> with backing field + dual-write setter.
|
||||
_BASE_INHERITABLE_MUTABLE_SETTING(Model::Profile, std::optional<Windows::Foundation::Collections::IVector<IMediaResource>>, BellSound, nullptr)
|
||||
public:
|
||||
Windows::Foundation::Collections::IVector<IMediaResource> BellSound() const
|
||||
{
|
||||
const auto val{ _getBellSoundImpl() };
|
||||
return val ? *val : nullptr;
|
||||
}
|
||||
void BellSound(const Windows::Foundation::Collections::IVector<IMediaResource>& value)
|
||||
{
|
||||
_BellSound = value;
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey(_json, "bellSound", value);
|
||||
}
|
||||
|
||||
private:
|
||||
Model::IAppearanceConfig _DefaultAppearance{ winrt::make<AppearanceConfig>(weak_ref<Model::Profile>(*this)) };
|
||||
Model::FontConfig _FontInfo{ winrt::make<FontConfig>(weak_ref<Model::Profile>(*this)) };
|
||||
|
||||
// Raw JSON for this layer. Populated by LayerJson(), will become the
|
||||
// source of truth for settings once the JSON-backed refactor is complete.
|
||||
Json::Value _json{ Json::ValueType::objectValue };
|
||||
|
||||
std::set<std::string> _changeLog;
|
||||
|
||||
static std::wstring EvaluateStartingDirectory(const std::wstring& directory);
|
||||
|
||||
@@ -31,6 +31,14 @@ namespace SettingsModelUnitTests
|
||||
TEST_METHOD(TestGenGuidsForProfiles);
|
||||
TEST_METHOD(TestCorrectOldDefaultShellPaths);
|
||||
TEST_METHOD(ProfileDefaultsProhibitedSettings);
|
||||
|
||||
TEST_METHOD(JsonSyncOnSetAndClear);
|
||||
TEST_METHOD(SettingKeyEnumAndJsonKeyLookup);
|
||||
TEST_METHOD(GenericHasAndClearMatchTypedAPIs);
|
||||
TEST_METHOD(CurrentSettingsReturnsCorrectKeys);
|
||||
TEST_METHOD(SpecialCasedSettingsJsonBacked);
|
||||
TEST_METHOD(NullableSettingJsonBacked);
|
||||
TEST_METHOD(ColorSchemeJsonBacked);
|
||||
};
|
||||
|
||||
void ProfileTests::ProfileGeneratesGuid()
|
||||
@@ -532,4 +540,424 @@ namespace SettingsModelUnitTests
|
||||
VERIFY_ARE_NOT_EQUAL(L"Default Profile Source", allProfiles.GetAt(2).Source());
|
||||
VERIFY_ARE_NOT_EQUAL(L"foo.exe", allProfiles.GetAt(2).Commandline());
|
||||
}
|
||||
|
||||
void ProfileTests::JsonSyncOnSetAndClear()
|
||||
{
|
||||
// Verify that setting a value via the typed setter updates the internal
|
||||
// _json, and clearing it removes the key from _json.
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1000
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
|
||||
// Verify initial value
|
||||
VERIFY_ARE_EQUAL(1000, profile.HistorySize());
|
||||
VERIFY_IS_TRUE(profile.HasHistorySize());
|
||||
|
||||
// Modify setting; _json should be updated
|
||||
profile.HistorySize(5000);
|
||||
VERIFY_ARE_EQUAL(5000, profile.HistorySize());
|
||||
|
||||
// Verify ToJson reflects the change
|
||||
const auto json = profileImpl->ToJson();
|
||||
VERIFY_ARE_EQUAL(5000, json["historySize"].asInt());
|
||||
|
||||
// Clear setting; should fall back to default
|
||||
profile.ClearHistorySize();
|
||||
VERIFY_IS_FALSE(profile.HasHistorySize());
|
||||
// Should now inherit or use default (9001 is the DEFAULT_HISTORY_SIZE)
|
||||
VERIFY_ARE_EQUAL(DEFAULT_HISTORY_SIZE, profile.HistorySize());
|
||||
|
||||
// ToJson should no longer have historySize
|
||||
const auto json2 = profileImpl->ToJson();
|
||||
VERIFY_IS_FALSE(json2.isMember("historySize"));
|
||||
}
|
||||
|
||||
void ProfileTests::SettingKeyEnumAndJsonKeyLookup()
|
||||
{
|
||||
// Verify that the SettingKey enums map to correct JSON keys.
|
||||
using namespace implementation;
|
||||
|
||||
// Spot-check a few profile setting keys
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "historySize" }, JsonKeyForSetting(ProfileSettingKey::HistorySize));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "snapOnInput" }, JsonKeyForSetting(ProfileSettingKey::SnapOnInput));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "commandline" }, JsonKeyForSetting(ProfileSettingKey::Commandline));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "tabTitle" }, JsonKeyForSetting(ProfileSettingKey::TabTitle));
|
||||
|
||||
// Special-cased settings
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "name" }, JsonKeyForSetting(ProfileSettingKey::_Name));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "guid" }, JsonKeyForSetting(ProfileSettingKey::_Guid));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "hidden" }, JsonKeyForSetting(ProfileSettingKey::_Hidden));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "padding" }, JsonKeyForSetting(ProfileSettingKey::_Padding));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "tabColor" }, JsonKeyForSetting(ProfileSettingKey::_TabColor));
|
||||
|
||||
// Global setting keys
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "initialRows" }, JsonKeyForSetting(GlobalSettingKey::InitialRows));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "alwaysOnTop" }, JsonKeyForSetting(GlobalSettingKey::AlwaysOnTop));
|
||||
|
||||
// Font setting keys
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "face" }, JsonKeyForSetting(FontSettingKey::FontFace));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "size" }, JsonKeyForSetting(FontSettingKey::FontSize));
|
||||
|
||||
// SETTINGS_SIZE should be a valid (but large) enum value
|
||||
VERIFY_IS_TRUE(static_cast<int>(ProfileSettingKey::SETTINGS_SIZE) > 0);
|
||||
VERIFY_IS_TRUE(static_cast<int>(GlobalSettingKey::SETTINGS_SIZE) > 0);
|
||||
VERIFY_IS_TRUE(static_cast<int>(FontSettingKey::SETTINGS_SIZE) > 0);
|
||||
VERIFY_IS_TRUE(static_cast<int>(AppearanceSettingKey::SETTINGS_SIZE) > 0);
|
||||
}
|
||||
|
||||
void ProfileTests::GenericHasAndClearMatchTypedAPIs()
|
||||
{
|
||||
// Verify that HasSetting(key) and ClearSetting(key) match the
|
||||
// typed HasXxx() and ClearXxx() methods.
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1000,
|
||||
"snapOnInput": false
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
|
||||
// HasSetting should match HasXxx for set values
|
||||
VERIFY_ARE_EQUAL(profile.HasHistorySize(), profileImpl->HasSetting(implementation::ProfileSettingKey::HistorySize));
|
||||
VERIFY_ARE_EQUAL(profile.HasSnapOnInput(), profileImpl->HasSetting(implementation::ProfileSettingKey::SnapOnInput));
|
||||
|
||||
// HasSetting should match HasXxx for unset values
|
||||
VERIFY_ARE_EQUAL(profile.HasTabTitle(), profileImpl->HasSetting(implementation::ProfileSettingKey::TabTitle));
|
||||
VERIFY_IS_FALSE(profileImpl->HasSetting(implementation::ProfileSettingKey::TabTitle));
|
||||
|
||||
// ClearSetting should behave like ClearXxx
|
||||
profileImpl->ClearSetting(implementation::ProfileSettingKey::HistorySize);
|
||||
VERIFY_IS_FALSE(profile.HasHistorySize());
|
||||
VERIFY_IS_FALSE(profileImpl->HasSetting(implementation::ProfileSettingKey::HistorySize));
|
||||
}
|
||||
|
||||
void ProfileTests::CurrentSettingsReturnsCorrectKeys()
|
||||
{
|
||||
// Verify that CurrentSettings() returns the keys that are explicitly
|
||||
// set at the current layer.
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1000,
|
||||
"snapOnInput": false,
|
||||
"tabTitle": "MyTab"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
|
||||
const auto currentKeys = profileImpl->CurrentSettings();
|
||||
|
||||
// historySize, snapOnInput, and tabTitle should be in the list
|
||||
auto hasHistorySize = false;
|
||||
auto hasSnapOnInput = false;
|
||||
auto hasTabTitle = false;
|
||||
for (const auto& key : currentKeys)
|
||||
{
|
||||
if (key == implementation::ProfileSettingKey::HistorySize)
|
||||
{
|
||||
hasHistorySize = true;
|
||||
}
|
||||
if (key == implementation::ProfileSettingKey::SnapOnInput)
|
||||
{
|
||||
hasSnapOnInput = true;
|
||||
}
|
||||
if (key == implementation::ProfileSettingKey::TabTitle)
|
||||
{
|
||||
hasTabTitle = true;
|
||||
}
|
||||
}
|
||||
VERIFY_IS_TRUE(hasHistorySize, L"historySize should be in CurrentSettings");
|
||||
VERIFY_IS_TRUE(hasSnapOnInput, L"snapOnInput should be in CurrentSettings");
|
||||
VERIFY_IS_TRUE(hasTabTitle, L"tabTitle should be in CurrentSettings");
|
||||
|
||||
// Clear one and verify it's removed
|
||||
profileImpl->ClearSetting(implementation::ProfileSettingKey::TabTitle);
|
||||
const auto updatedKeys = profileImpl->CurrentSettings();
|
||||
auto hasTabTitleAfterClear = false;
|
||||
for (const auto& key : updatedKeys)
|
||||
{
|
||||
if (key == implementation::ProfileSettingKey::TabTitle)
|
||||
{
|
||||
hasTabTitleAfterClear = true;
|
||||
}
|
||||
}
|
||||
VERIFY_IS_FALSE(hasTabTitleAfterClear, L"tabTitle should NOT be in CurrentSettings after clear");
|
||||
}
|
||||
|
||||
void ProfileTests::SpecialCasedSettingsJsonBacked()
|
||||
{
|
||||
// Verify that special-cased settings (Name, Guid, Hidden, Padding)
|
||||
// are now JSON-backed: setters write to _json, Has/Clear work correctly.
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "TestProfile",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"hidden": true,
|
||||
"padding": "12"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
|
||||
// Verify initial values loaded from JSON
|
||||
VERIFY_ARE_EQUAL(L"TestProfile", profile.Name());
|
||||
VERIFY_IS_TRUE(profile.HasName());
|
||||
VERIFY_IS_TRUE(profile.Hidden());
|
||||
VERIFY_IS_TRUE(profile.HasHidden());
|
||||
VERIFY_ARE_EQUAL(L"12", profile.Padding());
|
||||
VERIFY_IS_TRUE(profile.HasPadding());
|
||||
|
||||
// Modify Name via setter — should update _json
|
||||
profile.Name(L"NewName");
|
||||
VERIFY_ARE_EQUAL(L"NewName", profile.Name());
|
||||
const auto json1 = profileImpl->ToJson();
|
||||
VERIFY_ARE_EQUAL(L"NewName", til::u8u16(json1["name"].asString()));
|
||||
|
||||
// Clear Name — should fall back to default
|
||||
profile.ClearName();
|
||||
VERIFY_IS_FALSE(profile.HasName());
|
||||
VERIFY_ARE_EQUAL(L"Default", profile.Name());
|
||||
|
||||
// Modify Hidden via setter
|
||||
profile.Hidden(false);
|
||||
VERIFY_ARE_EQUAL(false, profile.Hidden());
|
||||
VERIFY_IS_TRUE(profile.HasHidden());
|
||||
|
||||
// Clear Hidden — should fall back to default (false)
|
||||
profile.ClearHidden();
|
||||
VERIFY_IS_FALSE(profile.HasHidden());
|
||||
VERIFY_ARE_EQUAL(false, profile.Hidden());
|
||||
|
||||
// Modify Padding — should write to _json
|
||||
profile.Padding(L"24");
|
||||
VERIFY_ARE_EQUAL(L"24", profile.Padding());
|
||||
|
||||
// Test Guid setter
|
||||
static constexpr winrt::guid testGuid{ 0x11111111, 0x2222, 0x3333, { 0x44, 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x77 } };
|
||||
profile.Guid(testGuid);
|
||||
VERIFY_ARE_EQUAL(testGuid, profile.Guid());
|
||||
}
|
||||
|
||||
void ProfileTests::NullableSettingJsonBacked()
|
||||
{
|
||||
// Verify that nullable settings (TabColor) are JSON-backed.
|
||||
// Null is a valid explicit value meaning "no color".
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"tabColor": "#FF0000"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
|
||||
// Verify initial value
|
||||
VERIFY_IS_TRUE(profile.HasTabColor());
|
||||
VERIFY_IS_NOT_NULL(profile.TabColor());
|
||||
|
||||
// Set to null explicitly (means "no color")
|
||||
profile.TabColor(nullptr);
|
||||
VERIFY_IS_TRUE(profile.HasTabColor()); // still "set" — explicitly null
|
||||
VERIFY_IS_NULL(profile.TabColor());
|
||||
|
||||
// Clear — should inherit from parent (which has no tabColor)
|
||||
profile.ClearTabColor();
|
||||
VERIFY_IS_FALSE(profile.HasTabColor());
|
||||
|
||||
// ToJson should not have tabColor after clearing
|
||||
const auto json = profileImpl->ToJson();
|
||||
VERIFY_IS_FALSE(json.isMember("tabColor"));
|
||||
}
|
||||
|
||||
void ProfileTests::ColorSchemeJsonBacked()
|
||||
{
|
||||
// Verify that DarkColorSchemeName/LightColorSchemeName are JSON-backed,
|
||||
// including round-trip through LayerJson/ToJson, parent inheritance,
|
||||
// and the polymorphic colorScheme key (string vs object forms).
|
||||
|
||||
// Case 1: string form input — sets both dark and light to the same scheme
|
||||
{
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"colorScheme": "One Half Dark"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
const auto appearance = profile.DefaultAppearance();
|
||||
const auto appearanceImpl = winrt::get_self<implementation::AppearanceConfig>(appearance);
|
||||
|
||||
// Both dark and light should be set to the same value
|
||||
VERIFY_ARE_EQUAL(L"One Half Dark", appearance.DarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"One Half Dark", appearance.LightColorSchemeName());
|
||||
VERIFY_IS_TRUE(appearanceImpl->HasDarkColorSchemeName());
|
||||
VERIFY_IS_TRUE(appearanceImpl->HasLightColorSchemeName());
|
||||
|
||||
// ToJson should collapse to a string since dark == light
|
||||
const auto json = profileImpl->ToJson();
|
||||
VERIFY_IS_TRUE(json.isMember("colorScheme"));
|
||||
VERIFY_IS_TRUE(json["colorScheme"].isString());
|
||||
VERIFY_ARE_EQUAL("One Half Dark", json["colorScheme"].asString());
|
||||
}
|
||||
|
||||
// Case 2: object form input — dark and light are different
|
||||
{
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"colorScheme": {
|
||||
"dark": "One Half Dark",
|
||||
"light": "One Half Light"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
const auto appearance = profile.DefaultAppearance();
|
||||
const auto appearanceImpl = winrt::get_self<implementation::AppearanceConfig>(appearance);
|
||||
|
||||
// Each should return its own value
|
||||
VERIFY_ARE_EQUAL(L"One Half Dark", appearance.DarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"One Half Light", appearance.LightColorSchemeName());
|
||||
|
||||
// ToJson should produce an object since dark != light
|
||||
const auto json = profileImpl->ToJson();
|
||||
VERIFY_IS_TRUE(json.isMember("colorScheme"));
|
||||
VERIFY_IS_TRUE(json["colorScheme"].isObject());
|
||||
VERIFY_ARE_EQUAL("One Half Dark", json["colorScheme"]["dark"].asString());
|
||||
VERIFY_ARE_EQUAL("One Half Light", json["colorScheme"]["light"].asString());
|
||||
}
|
||||
|
||||
// Case 3: setter updates — setting dark should preserve light, and vice versa
|
||||
{
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"colorScheme": "Campbell"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
const auto appearance = profile.DefaultAppearance();
|
||||
const auto appearanceImpl = winrt::get_self<implementation::AppearanceConfig>(appearance);
|
||||
|
||||
// Change dark only — light should stay "Campbell"
|
||||
appearance.DarkColorSchemeName(L"Tango Dark");
|
||||
VERIFY_ARE_EQUAL(L"Tango Dark", appearance.DarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"Campbell", appearance.LightColorSchemeName());
|
||||
|
||||
// ToJson should produce an object since dark != light
|
||||
const auto json1 = profileImpl->ToJson();
|
||||
VERIFY_IS_TRUE(json1["colorScheme"].isObject());
|
||||
VERIFY_ARE_EQUAL("Tango Dark", json1["colorScheme"]["dark"].asString());
|
||||
VERIFY_ARE_EQUAL("Campbell", json1["colorScheme"]["light"].asString());
|
||||
|
||||
// Change light to match dark — should collapse to string on ToJson
|
||||
appearance.LightColorSchemeName(L"Tango Dark");
|
||||
VERIFY_ARE_EQUAL(L"Tango Dark", appearance.LightColorSchemeName());
|
||||
const auto json2 = profileImpl->ToJson();
|
||||
VERIFY_IS_TRUE(json2["colorScheme"].isString());
|
||||
VERIFY_ARE_EQUAL("Tango Dark", json2["colorScheme"].asString());
|
||||
}
|
||||
|
||||
// Case 4: clear — removes colorScheme entirely, falls back to default "Campbell"
|
||||
{
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"colorScheme": "One Half Dark"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
const auto appearance = profile.DefaultAppearance();
|
||||
const auto appearanceImpl = winrt::get_self<implementation::AppearanceConfig>(appearance);
|
||||
|
||||
VERIFY_IS_TRUE(appearanceImpl->HasDarkColorSchemeName());
|
||||
|
||||
// Clear removes colorScheme from this layer
|
||||
appearanceImpl->ClearDarkColorSchemeName();
|
||||
VERIFY_IS_FALSE(appearanceImpl->HasDarkColorSchemeName());
|
||||
VERIFY_IS_FALSE(appearanceImpl->HasLightColorSchemeName());
|
||||
|
||||
// Falls back to default "Campbell"
|
||||
VERIFY_ARE_EQUAL(L"Campbell", appearance.DarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"Campbell", appearance.LightColorSchemeName());
|
||||
|
||||
// ToJson should not have colorScheme
|
||||
const auto json = profileImpl->ToJson();
|
||||
VERIFY_IS_FALSE(json.isMember("colorScheme"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user