Add Serializer to CascadiaSettings (#8018)

##  Summary of the Pull Request
This adds `ToJson` functions to `Profile`, `GlobalAppSettings`, and `ColorScheme`. They are used in `CascadiaSettings` to completely serialize an instance of the settings model. Thanks to #7923, all of the settings are `std::optional`, and `JsonUtils` only writes out values that are actually populated.

`CascadiaSettings::WriteSettingsToDisk` serializes the current settings and writes them to the settings.json. A backup file is created with your old contents.

#### Limitations:
- all of the color schemes are serialized regardless of them coming from defaults.json or settings.json
- keybindings/actions are copied/pasted

## References
#1564 - Settings UI
TSM Specs (#6904 and #7876)

## PR Checklist
* [x] Tests added/passed
This commit is contained in:
Carlos Zamora 2020-11-16 16:37:19 -08:00 committed by GitHub
parent 77a204b765
commit 6b503ba887
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 562 additions and 77 deletions

View file

@ -34,6 +34,7 @@ IObject
IStorage
LCID
llabs
localtime
lround
LSHIFT
msappx

View file

@ -418872,6 +418872,7 @@ time-shrouded
time-space
time-spirit
timestamp
timestamped
timestamps
timet
time-table

View file

@ -884,7 +884,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
VERIFY_ARE_EQUAL(2u, settings->_allProfiles.Size());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid());
VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).HasGuid());
}
void DeserializationTests::TestReorderWithNullGuids()
@ -935,7 +935,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
VERIFY_ARE_EQUAL(4u, settings->_allProfiles.Size());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid());
VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(2).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(3).HasGuid());
VERIFY_ARE_EQUAL(L"profile0", settings->_allProfiles.GetAt(0).Name());
@ -1036,7 +1036,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
VERIFY_ARE_EQUAL(4u, settings->_allProfiles.Size());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid());
VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(2).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(3).HasGuid());
VERIFY_ARE_EQUAL(L"Command Prompt", settings->_allProfiles.GetAt(0).Name());
@ -1190,8 +1190,8 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(5u, settings->_allProfiles.Size());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(2).HasGuid());
VERIFY_IS_FALSE(settings->_allProfiles.GetAt(1).HasGuid());
VERIFY_IS_FALSE(settings->_allProfiles.GetAt(2).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(3).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(4).HasGuid());
VERIFY_ARE_EQUAL(L"ThisProfileIsGood", settings->_allProfiles.GetAt(0).Name());

View file

@ -22,6 +22,12 @@ public:
{
_reader = std::unique_ptr<Json::CharReader>(Json::CharReaderBuilder::CharReaderBuilder().newCharReader());
};
void InitializeJsonWriter()
{
_writer = std::unique_ptr<Json::StreamWriter>(Json::StreamWriterBuilder::StreamWriterBuilder().newStreamWriter());
}
Json::Value VerifyParseSucceeded(std::string content)
{
Json::Value root;
@ -31,6 +37,14 @@ public:
return root;
};
std::string toString(const Json::Value& json)
{
std::stringstream s;
_writer->write(json, &s);
return s.str();
}
protected:
std::unique_ptr<Json::CharReader> _reader;
std::unique_ptr<Json::StreamWriter> _writer;
};

View file

@ -0,0 +1,295 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../TerminalSettingsModel/ColorScheme.h"
#include "../TerminalSettingsModel/CascadiaSettings.h"
#include "JsonTestClass.h"
#include "TestUtils.h"
#include <defaults.h>
#include "../ut_app/TestDynamicProfileGenerator.h"
using namespace Microsoft::Console;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::TerminalControl;
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class SerializationTests : public JsonTestClass
{
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(SerializationTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(GlobalSettings);
TEST_METHOD(Profile);
TEST_METHOD(ColorScheme);
TEST_METHOD(CascadiaSettings);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
InitializeJsonWriter();
return true;
}
private:
// Method Description:
// - deserializes and reserializes a json string representing a settings object model of type T
// - verifies that the generated json string matches the provided one
// Template Types:
// - <T>: The type of Settings Model object to generate (must be impl type)
// Arguments:
// - jsonString - JSON string we're performing the test on
// Return Value:
// - the JsonObject representing this instance
template<typename T>
void RoundtripTest(const std::string& jsonString)
{
const auto json{ VerifyParseSucceeded(jsonString) };
const auto settings{ T::FromJson(json) };
const auto result{ settings->ToJson() };
// Compare toString(json) instead of jsonString here.
// The toString writes the json out alphabetically.
// This trick allows jsonString to _not_ have to be
// written alphabetically.
VERIFY_ARE_EQUAL(toString(json), toString(result));
}
};
void SerializationTests::GlobalSettings()
{
const std::string globalsString{ R"(
{
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"initialRows": 30,
"initialCols": 120,
"initialPosition": ",",
"launchMode": "default",
"alwaysOnTop": false,
"copyOnSelect": false,
"copyFormatting": "all",
"wordDelimiters": " /\\()\"'-.,:;<>~!@#$%^&*|+=[]{}~?\u2502",
"alwaysShowTabs": true,
"showTabsInTitlebar": true,
"showTerminalTitleInTitlebar": true,
"tabWidthMode": "equal",
"tabSwitcherMode": "mru",
"startOnUserLogin": false,
"theme": "system",
"snapToGridOnResize": true,
"disableAnimations": false,
"confirmCloseAllTabs": true,
"largePasteWarning": true,
"multiLinePasteWarning": true,
"experimental.input.forceVT": false,
"experimental.rendering.forceFullRepaint": false,
"experimental.rendering.software": false
})" };
const std::string smallGlobalsString{ R"(
{
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}"
})" };
RoundtripTest<implementation::GlobalAppSettings>(globalsString);
RoundtripTest<implementation::GlobalAppSettings>(smallGlobalsString);
}
void SerializationTests::Profile()
{
const std::string profileString{ R"(
{
"name": "Windows PowerShell",
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"commandline": "%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
"startingDirectory": "%USERPROFILE%",
"icon": "ms-appx:///ProfileIcons/{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.png",
"hidden": false,
"tabTitle": "Cool Tab",
"suppressApplicationTitle": false,
"fontFace": "Cascadia Mono",
"fontSize": 12,
"fontWeight": "normal",
"padding": "8, 8, 8, 8",
"antialiasingMode": "grayscale",
"cursorShape": "bar",
"cursorColor": "#CCBBAA",
"cursorHeight": 10,
"altGrAliasing": true,
"colorScheme": "Campbell",
"tabColor": "#0C0C0C",
"foreground": "#AABBCC",
"background": "#BBCCAA",
"selectionBackground": "#CCAABB",
"useAcrylic": false,
"acrylicOpacity": 0.5,
"backgroundImage": "made_you_look.jpeg",
"backgroundImageStretchMode": "uniformToFill",
"backgroundImageAlignment": "center",
"backgroundImageOpacity": 1.0,
"scrollbarState": "visible",
"snapOnInput": true,
"historySize": 9001,
"closeOnExit": "graceful",
"experimental.retroTerminalEffect": false
})" };
const std::string smallProfileString{ R"(
{
"name": "Custom Profile"
})" };
// Setting "tabColor" to null tests two things:
// - null should count as an explicit user-set value, not falling back to the parent's value
// - null should be acceptable even though we're working with colors
const std::string weirdProfileString{ R"(
{
"name": "Weird Profile",
"tabColor": null,
"foreground": null,
"source": "local"
})" };
RoundtripTest<implementation::Profile>(profileString);
RoundtripTest<implementation::Profile>(smallProfileString);
RoundtripTest<implementation::Profile>(weirdProfileString);
}
void SerializationTests::ColorScheme()
{
const std::string schemeString{ R"({
"name": "Campbell",
"cursorColor": "#FFFFFF",
"selectionBackground": "#131313",
"background": "#0C0C0C",
"foreground": "#F2F2F2",
"black": "#0C0C0C",
"blue": "#0037DA",
"cyan": "#3A96DD",
"green": "#13A10E",
"purple": "#881798",
"red": "#C50F1F",
"white": "#CCCCCC",
"yellow": "#C19C00",
"brightBlack": "#767676",
"brightBlue": "#3B78FF",
"brightCyan": "#61D6D6",
"brightGreen": "#16C60C",
"brightPurple": "#B4009E",
"brightRed": "#E74856",
"brightWhite": "#F2F2F2",
"brightYellow": "#F9F1A5"
})" };
RoundtripTest<implementation::ColorScheme>(schemeString);
}
void SerializationTests::CascadiaSettings()
{
const std::string settingsString{ R"({
"$schema": "https://aka.ms/terminal-profiles-schema",
"defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
"profiles": {
"defaults": {
"fontFace": "Zamora Code"
},
"list": [
{
"fontFace": "Cascadia Code",
"guid": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
"name": "HowettShell"
},
{
"hidden": true,
"name": "BhojwaniShell"
},
{
"antialiasingMode": "aliased",
"name": "NiksaShell"
}
]
},
"schemes": [
{
"name": "Cinnamon Roll",
"cursorColor": "#FFFFFD",
"selectionBackground": "#FFFFFF",
"background": "#3C0315",
"foreground": "#FFFFFD",
"black": "#282A2E",
"blue": "#0170C5",
"cyan": "#3F8D83",
"green": "#76AB23",
"purple": "#7D498F",
"red": "#BD0940",
"white": "#FFFFFD",
"yellow": "#E0DE48",
"brightBlack": "#676E7A",
"brightBlue": "#5C98C5",
"brightCyan": "#8ABEB7",
"brightGreen": "#B5D680",
"brightPurple": "#AC79BB",
"brightRed": "#BD6D85",
"brightWhite": "#FFFFFD",
"brightYellow": "#FFFD76"
}
],
"actions": [
{"command": { "action": "renameTab","input": "Liang Tab" },"keys": "ctrl+t" }
],
"keybindings": [
{ "command": { "action": "sendInput","input": "VT Griese Mode" },"keys": "ctrl+k" }
]
})" };
auto settings{ winrt::make_self<implementation::CascadiaSettings>(false) };
settings->_ParseJsonString(settingsString, false);
settings->_ApplyDefaultsFromUserSettings();
settings->LayerJson(settings->_userSettings);
settings->_ValidateSettings();
const auto result{ settings->ToJson() };
VERIFY_ARE_EQUAL(toString(settings->_userSettings), toString(result));
}
}

View file

@ -39,6 +39,7 @@
<ClCompile Include="KeyBindingsTests.cpp" />
<ClCompile Include="CommandTests.cpp" />
<ClCompile Include="DeserializationTests.cpp" />
<ClCompile Include="SerializationTests.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>

View file

@ -247,11 +247,6 @@ void CascadiaSettings::_ValidateSettings()
// Make sure to check that profiles exists at all first and foremost:
_ValidateProfilesExist();
// Verify all profiles actually had a GUID specified, otherwise generate a
// GUID for them. Make sure to do this before de-duping profiles and
// checking that the default profile is set.
_ValidateProfilesHaveGuid();
// Re-order profiles so that all profiles from the user's settings appear
// before profiles that _weren't_ in the user profiles.
_ReorderProfilesToMatchUserSettingsOrder();
@ -308,19 +303,6 @@ void CascadiaSettings::_ValidateProfilesExist()
}
}
// Method Description:
// - Walks through each profile, and ensures that they had a GUID set at some
// point. If the profile did _not_ have a GUID ever set for it, generate a
// temporary runtime GUID for it. This validation does not add any warnings.
void CascadiaSettings::_ValidateProfilesHaveGuid()
{
for (auto profile : _allProfiles)
{
auto profileImpl = winrt::get_self<implementation::Profile>(profile);
profileImpl->GenerateGuidIfNecessary();
}
}
// Method Description:
// - Resolves the "defaultProfile", which can be a profile name, to a GUID
// and stores it back to the globals.
@ -509,7 +491,8 @@ void CascadiaSettings::_ValidateAllSchemesExist()
const auto schemeName = profile.ColorSchemeName();
if (!_globals->ColorSchemes().HasKey(schemeName))
{
profile.ColorSchemeName({ L"Campbell" });
// Clear the user set color scheme. We'll just fallback instead.
profile.ClearColorSchemeName();
foundInvalidScheme = true;
}
}
@ -727,7 +710,8 @@ void CascadiaSettings::_ValidateKeybindings()
// we find any invalid background images.
void CascadiaSettings::_ValidateNoGlobalsKey()
{
if (auto oldGlobalsProperty{ _userSettings["globals"] })
// use isMember here. If you use [], you're actually injecting "globals": null.
if (_userSettings.isMember("globals"))
{
_warnings.Append(SettingsLoadWarnings::LegacyGlobalsProperty);
}

View file

@ -29,6 +29,7 @@ Author(s):
// fwdecl unittest classes
namespace SettingsModelLocalTests
{
class SerializationTests;
class DeserializationTests;
class ProfileTests;
class ColorSchemeTests;
@ -74,6 +75,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static com_ptr<CascadiaSettings> FromJson(const Json::Value& json);
void LayerJson(const Json::Value& json);
void WriteSettingsToDisk() const;
Json::Value ToJson() const;
static hstring SettingsPath();
static hstring DefaultSettingsPath();
@ -124,7 +128,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void _LoadDynamicProfiles();
static bool _IsPackaged();
static void _WriteSettings(const std::string_view content);
static void _WriteSettings(std::string_view content, const hstring filepath);
static std::optional<std::string> _ReadUserSettings();
static std::optional<std::string> _ReadFile(HANDLE hFile);
@ -133,7 +137,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void _ValidateSettings();
void _ValidateProfilesExist();
void _ValidateProfilesHaveGuid();
void _ValidateDefaultProfileExists();
void _ValidateNoDuplicateProfiles();
void _ResolveDefaultProfile();
@ -144,6 +147,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void _ValidateKeybindings();
void _ValidateNoGlobalsKey();
friend class SettingsModelLocalTests::SerializationTests;
friend class SettingsModelLocalTests::DeserializationTests;
friend class SettingsModelLocalTests::ProfileTests;
friend class SettingsModelLocalTests::ColorSchemeTests;

View file

@ -11,6 +11,8 @@ namespace Microsoft.Terminal.Settings.Model
CascadiaSettings(String json);
CascadiaSettings Copy();
void WriteSettingsToDisk();
static CascadiaSettings LoadDefaults();
static CascadiaSettings LoadAll();
static CascadiaSettings LoadUniversal();

View file

@ -9,6 +9,7 @@
#include "JsonUtils.h"
#include <appmodel.h>
#include <shlobj.h>
#include <fmt/chrono.h>
// defaults.h is a file containing the default json settings in a std::string_view
#include "defaults.h"
@ -28,10 +29,12 @@ static constexpr std::wstring_view UnpackagedSettingsFolderName{ L"Microsoft\\Wi
static constexpr std::wstring_view DefaultsFilename{ L"defaults.json" };
static constexpr std::string_view SchemaKey{ "$schema" };
static constexpr std::string_view SchemaValue{ "https://aka.ms/terminal-profiles-schema" };
static constexpr std::string_view ProfilesKey{ "profiles" };
static constexpr std::string_view DefaultSettingsKey{ "defaults" };
static constexpr std::string_view ProfilesListKey{ "list" };
static constexpr std::string_view KeybindingsKey{ "keybindings" };
static constexpr std::string_view LegacyKeybindingsKey{ "keybindings" };
static constexpr std::string_view ActionsKey{ "actions" };
static constexpr std::string_view SchemesKey{ "schemes" };
static constexpr std::string_view DisabledProfileSourcesKey{ "disabledProfileSources" };
@ -188,7 +191,7 @@ winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings::
try
{
_WriteSettings(resultPtr->_userSettingsString);
_WriteSettings(resultPtr->_userSettingsString, CascadiaSettings::SettingsPath());
}
catch (...)
{
@ -221,7 +224,7 @@ winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings::
}
// If the user had keybinding settings preferences, we want to learn from them to make better defaults
auto userKeybindings = resultPtr->_userSettings[JsonKey(KeybindingsKey)];
auto userKeybindings = resultPtr->_userSettings[JsonKey(LegacyKeybindingsKey)];
if (!userKeybindings.empty())
{
// If there are custom key bindings, let's understand what they are because maybe the defaults aren't good enough
@ -366,8 +369,6 @@ void CascadiaSettings::_LoadDynamicProfiles()
auto profiles = generator->GenerateProfiles();
for (auto& profile : profiles)
{
// If the profile did not have a GUID when it was generated,
// we'll synthesize a GUID for it in _ValidateProfilesHaveGuid
profile.Source(generatorNamespace);
_allProfiles.Append(profile);
@ -829,7 +830,7 @@ bool CascadiaSettings::_IsPackaged()
}
// Method Description:
// - Writes the given content in UTF-8 to our settings file using the Win32 APIS's.
// - Writes the given content in UTF-8 to a settings file using the Win32 APIS's.
// Will overwrite any existing content in the file.
// Arguments:
// - content: the given string of content to write to the file.
@ -837,11 +838,9 @@ bool CascadiaSettings::_IsPackaged()
// - <none>
// This can throw an exception if we fail to open the file for writing, or we
// fail to write the file
void CascadiaSettings::_WriteSettings(const std::string_view content)
void CascadiaSettings::_WriteSettings(const std::string_view content, const hstring filepath)
{
auto pathToSettingsFile{ CascadiaSettings::SettingsPath() };
wil::unique_hfile hOut{ CreateFileW(pathToSettingsFile.c_str(),
wil::unique_hfile hOut{ CreateFileW(filepath.c_str(),
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
@ -1039,3 +1038,83 @@ const Json::Value& CascadiaSettings::_GetDisabledProfileSourcesJsonObject(const
}
return json[JsonKey(DisabledProfileSourcesKey)];
}
// Method Description:
// - Write the current state of CascadiaSettings to our settings file
// - Create a backup file with the current contents, if one does not exist
// Arguments:
// - <none>
// Return Value:
// - <none>
void CascadiaSettings::WriteSettingsToDisk() const
{
const auto settingsPath{ CascadiaSettings::SettingsPath() };
try
{
// create a timestamped backup file
const auto clock{ std::chrono::system_clock() };
const auto timeStamp{ clock.to_time_t(clock.now()) };
const winrt::hstring backupSettingsPath{ fmt::format(L"{}.{:%Y-%m-%dT%H-%M-%S}.backup", settingsPath, fmt::localtime(timeStamp)) };
_WriteSettings(_userSettingsString, backupSettingsPath);
}
CATCH_LOG();
// write current settings to current settings file
Json::StreamWriterBuilder wbuilder;
wbuilder.settings_["indentation"] = " ";
wbuilder.settings_["enableYAMLCompatibility"] = true; // suppress spaces around colons
const auto styledString{ Json::writeString(wbuilder, ToJson()) };
_WriteSettings(styledString, settingsPath);
}
// Method Description:
// - Create a new serialized JsonObject from an instance of this class
// Arguments:
// - <none>
// Return Value:
// the JsonObject representing this instance
Json::Value CascadiaSettings::ToJson() const
{
// top-level json object
// directly inject "globals" and "$schema" into here
Json::Value json{ _globals->ToJson() };
JsonUtils::SetValueForKey(json, SchemaKey, JsonKey(SchemaValue));
// "profiles" will always be serialized as an object
Json::Value profiles{ Json::ValueType::objectValue };
profiles[JsonKey(DefaultSettingsKey)] = _userDefaultProfileSettings ? _userDefaultProfileSettings->ToJson() :
Json::ValueType::objectValue;
Json::Value profilesList{ Json::ValueType::arrayValue };
for (const auto& entry : _allProfiles)
{
const auto prof{ winrt::get_self<implementation::Profile>(entry) };
profilesList.append(prof->ToJson());
}
profiles[JsonKey(ProfilesListKey)] = profilesList;
json[JsonKey(ProfilesKey)] = profiles;
// TODO GH#8100:
// "schemes" will be an accumulation of _all_ the color schemes
// including all of the ones from defaults.json
Json::Value schemes{ Json::ValueType::arrayValue };
for (const auto& entry : _globals->ColorSchemes())
{
const auto scheme{ winrt::get_self<implementation::ColorScheme>(entry.Value()) };
schemes.append(scheme->ToJson());
}
json[JsonKey(SchemesKey)] = schemes;
// "actions"/"keybindings" will be whatever blob we had in the file
if (_userSettings.isMember(JsonKey(LegacyKeybindingsKey)))
{
json[JsonKey(LegacyKeybindingsKey)] = _userSettings[JsonKey(LegacyKeybindingsKey)];
}
if (_userSettings.isMember(JsonKey(ActionsKey)))
{
json[JsonKey(ActionsKey)] = _userSettings[JsonKey(ActionsKey)];
}
return json;
}

View file

@ -129,8 +129,8 @@ void ColorScheme::LayerJson(const Json::Value& json)
// Arguments:
// - <none>
// Return Value:
// <none>
Json::Value ColorScheme::ToJson()
// - the JsonObject representing this instance
Json::Value ColorScheme::ToJson() const
{
Json::Value json{ Json::ValueType::objectValue };

View file

@ -40,7 +40,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
bool ShouldBeLayered(const Json::Value& json) const;
void LayerJson(const Json::Value& json);
Json::Value ToJson();
Json::Value ToJson() const;
static std::optional<std::wstring> GetNameFromJson(const Json::Value& json);

View file

@ -53,12 +53,16 @@ static constexpr bool debugFeaturesDefault{ true };
static constexpr bool debugFeaturesDefault{ false };
#endif
bool GlobalAppSettings::_getDefaultDebugFeaturesValue()
{
return debugFeaturesDefault;
}
GlobalAppSettings::GlobalAppSettings() :
_keymap{ winrt::make_self<KeyMapping>() },
_keybindingsWarnings{},
_validDefaultProfile{ false },
_defaultProfile{},
_DebugFeaturesEnabled{ debugFeaturesDefault }
_defaultProfile{}
{
_commands = winrt::single_threaded_map<winrt::hstring, Model::Command>();
_colorSchemes = winrt::single_threaded_map<winrt::hstring, Model::ColorScheme>();
@ -347,3 +351,49 @@ winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, winrt::Microso
{
return _commands.GetView();
}
// Method Description:
// - Create a new serialized JsonObject from an instance of this class
// Arguments:
// - <none>
// Return Value:
// - the JsonObject representing this instance
Json::Value GlobalAppSettings::ToJson() const
{
Json::Value json{ Json::ValueType::objectValue };
// clang-format off
JsonUtils::SetValueForKey(json, DefaultProfileKey, _UnparsedDefaultProfile);
JsonUtils::SetValueForKey(json, AlwaysShowTabsKey, _AlwaysShowTabs);
JsonUtils::SetValueForKey(json, ConfirmCloseAllKey, _ConfirmCloseAllTabs);
JsonUtils::SetValueForKey(json, InitialRowsKey, _InitialRows);
JsonUtils::SetValueForKey(json, InitialColsKey, _InitialCols);
JsonUtils::SetValueForKey(json, InitialPositionKey, _InitialPosition);
JsonUtils::SetValueForKey(json, ShowTitleInTitlebarKey, _ShowTitleInTitlebar);
JsonUtils::SetValueForKey(json, ShowTabsInTitlebarKey, _ShowTabsInTitlebar);
JsonUtils::SetValueForKey(json, WordDelimitersKey, _WordDelimiters);
JsonUtils::SetValueForKey(json, CopyOnSelectKey, _CopyOnSelect);
JsonUtils::SetValueForKey(json, CopyFormattingKey, _CopyFormatting);
JsonUtils::SetValueForKey(json, WarnAboutLargePasteKey, _WarnAboutLargePaste);
JsonUtils::SetValueForKey(json, WarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste);
JsonUtils::SetValueForKey(json, LaunchModeKey, _LaunchMode);
JsonUtils::SetValueForKey(json, ThemeKey, _Theme);
JsonUtils::SetValueForKey(json, TabWidthModeKey, _TabWidthMode);
JsonUtils::SetValueForKey(json, SnapToGridOnResizeKey, _SnapToGridOnResize);
JsonUtils::SetValueForKey(json, DebugFeaturesKey, _DebugFeaturesEnabled);
JsonUtils::SetValueForKey(json, ForceFullRepaintRenderingKey, _ForceFullRepaintRendering);
JsonUtils::SetValueForKey(json, SoftwareRenderingKey, _SoftwareRendering);
JsonUtils::SetValueForKey(json, ForceVTInputKey, _ForceVTInput);
JsonUtils::SetValueForKey(json, EnableStartupTaskKey, _StartOnUserLogin);
JsonUtils::SetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop);
JsonUtils::SetValueForKey(json, TabSwitcherModeKey, _TabSwitcherMode);
JsonUtils::SetValueForKey(json, DisableAnimationsKey, _DisableAnimations);
// clang-format on
// TODO GH#8100: keymap needs to be serialized here
// For deserialization, we iterate over each action in the Json and interpret it as a keybinding, then as a command.
// Converting this back to JSON is a problem because we have no way to know if a Command and Keybinding come from
// the same entry.
return json;
}

View file

@ -46,6 +46,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static com_ptr<GlobalAppSettings> FromJson(const Json::Value& json);
void LayerJson(const Json::Value& json);
Json::Value ToJson() const;
std::vector<SettingsLoadWarnings> KeybindingsWarnings() const;
Windows::Foundation::Collections::IMapView<hstring, Model::Command> Commands() noexcept;
@ -78,7 +80,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
GETSET_SETTING(bool, ForceFullRepaintRendering, false);
GETSET_SETTING(bool, SoftwareRendering, false);
GETSET_SETTING(bool, ForceVTInput, false);
GETSET_SETTING(bool, DebugFeaturesEnabled); // default value set in constructor
GETSET_SETTING(bool, DebugFeaturesEnabled, _getDefaultDebugFeaturesValue());
GETSET_SETTING(bool, StartOnUserLogin, false);
GETSET_SETTING(bool, AlwaysOnTop, false);
GETSET_SETTING(Model::TabSwitcherMode, TabSwitcherMode, Model::TabSwitcherMode::MostRecentlyUsed);
@ -96,6 +98,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
Windows::Foundation::Collections::IMap<hstring, Model::Command> _commands;
std::optional<hstring> _getUnparsedDefaultProfileImpl() const;
static bool _getDefaultDebugFeaturesValue();
friend class SettingsModelLocalTests::DeserializationTests;
friend class SettingsModelLocalTests::ColorSchemeTests;

View file

@ -88,6 +88,13 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
static constexpr auto&& Value(const ::winrt::Windows::Foundation::IReference<T>& o) { return o.Value(); }
};
class SerializationError : public std::runtime_error
{
public:
SerializationError() :
runtime_error("failed to serialize") {}
};
class DeserializationError : public std::runtime_error
{
public:
@ -517,7 +524,7 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
return { pair.first.data() };
}
}
FAIL_FAST();
throw SerializationError{};
}
std::string TypeDescription() const

View file

@ -291,6 +291,7 @@ void Profile::LayerJson(const Json::Value& json)
JsonUtils::GetValueForKey(json, NameKey, _Name);
JsonUtils::GetValueForKey(json, GuidKey, _Guid);
JsonUtils::GetValueForKey(json, HiddenKey, _Hidden);
JsonUtils::GetValueForKey(json, SourceKey, _Source);
// Core Settings
JsonUtils::GetValueForKey(json, ForegroundKey, _Foreground);
@ -417,27 +418,6 @@ std::wstring Profile::EvaluateStartingDirectory(const std::wstring& directory)
}
}
// Method Description:
// - If this profile never had a GUID set for it, generate a runtime GUID for
// the profile. If a profile had their guid manually set to {0}, this method
// will _not_ change the profile's GUID.
void Profile::GenerateGuidIfNecessary() noexcept
{
if (!_getGuidImpl().has_value())
{
// Always use the name to generate the temporary GUID. That way, across
// reloads, we'll generate the same static GUID.
_Guid = Profile::_GenerateGuidForProfile(Name(), Source());
TraceLoggingWrite(
g_hSettingsModelProvider,
"SynthesizedGuidForProfile",
TraceLoggingDescription("Event emitted when a profile is deserialized without a GUID"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
}
}
// Function Description:
// - Returns true if the given JSON object represents a dynamic profile object.
// If it is a dynamic profile object, we should make sure to only layer the
@ -540,3 +520,63 @@ void Profile::BackgroundImageVerticalAlignment(const VerticalAlignment& value) n
}
}
#pragma endregion
// Method Description:
// - Create a new serialized JsonObject from an instance of this class
// Arguments:
// - <none>
// Return Value:
// - the JsonObject representing this instance
Json::Value Profile::ToJson() const
{
Json::Value json{ Json::ValueType::objectValue };
// Profile-specific Settings
JsonUtils::SetValueForKey(json, NameKey, _Name);
JsonUtils::SetValueForKey(json, GuidKey, _Guid);
JsonUtils::SetValueForKey(json, HiddenKey, _Hidden);
JsonUtils::SetValueForKey(json, SourceKey, _Source);
// Core Settings
JsonUtils::SetValueForKey(json, ForegroundKey, _Foreground);
JsonUtils::SetValueForKey(json, BackgroundKey, _Background);
JsonUtils::SetValueForKey(json, SelectionBackgroundKey, _SelectionBackground);
JsonUtils::SetValueForKey(json, CursorColorKey, _CursorColor);
JsonUtils::SetValueForKey(json, ColorSchemeKey, _ColorSchemeName);
// TODO:MSFT:20642297 - Use a sentinel value (-1) for "Infinite scrollback"
JsonUtils::SetValueForKey(json, HistorySizeKey, _HistorySize);
JsonUtils::SetValueForKey(json, SnapOnInputKey, _SnapOnInput);
JsonUtils::SetValueForKey(json, AltGrAliasingKey, _AltGrAliasing);
JsonUtils::SetValueForKey(json, CursorHeightKey, _CursorHeight);
JsonUtils::SetValueForKey(json, CursorShapeKey, _CursorShape);
JsonUtils::SetValueForKey(json, TabTitleKey, _TabTitle);
// Control Settings
JsonUtils::SetValueForKey(json, FontWeightKey, _FontWeight);
JsonUtils::SetValueForKey(json, ConnectionTypeKey, _ConnectionType);
JsonUtils::SetValueForKey(json, CommandlineKey, _Commandline);
JsonUtils::SetValueForKey(json, FontFaceKey, _FontFace);
JsonUtils::SetValueForKey(json, FontSizeKey, _FontSize);
JsonUtils::SetValueForKey(json, AcrylicTransparencyKey, _AcrylicOpacity);
JsonUtils::SetValueForKey(json, UseAcrylicKey, _UseAcrylic);
JsonUtils::SetValueForKey(json, SuppressApplicationTitleKey, _SuppressApplicationTitle);
JsonUtils::SetValueForKey(json, CloseOnExitKey, _CloseOnExit);
// PermissiveStringConverter is unnecessary for serialization
JsonUtils::SetValueForKey(json, PaddingKey, _Padding);
JsonUtils::SetValueForKey(json, ScrollbarStateKey, _ScrollState);
JsonUtils::SetValueForKey(json, StartingDirectoryKey, _StartingDirectory);
JsonUtils::SetValueForKey(json, IconKey, _Icon);
JsonUtils::SetValueForKey(json, BackgroundImageKey, _BackgroundImagePath);
JsonUtils::SetValueForKey(json, BackgroundImageOpacityKey, _BackgroundImageOpacity);
JsonUtils::SetValueForKey(json, BackgroundImageStretchModeKey, _BackgroundImageStretchMode);
JsonUtils::SetValueForKey(json, BackgroundImageAlignmentKey, _BackgroundImageAlignment);
JsonUtils::SetValueForKey(json, RetroTerminalEffectKey, _RetroTerminalEffect);
JsonUtils::SetValueForKey(json, AntialiasingModeKey, _AntialiasingMode);
JsonUtils::SetValueForKey(json, TabColorKey, _TabColor);
JsonUtils::SetValueForKey(json, BellStyleKey, _BellStyle);
return json;
}

View file

@ -55,10 +55,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
bool ShouldBeLayered(const Json::Value& json) const;
void LayerJson(const Json::Value& json);
static bool IsDynamicProfileObject(const Json::Value& json);
Json::Value ToJson() const;
hstring EvaluatedStartingDirectory() const;
hstring ExpandedBackgroundImagePath() const;
void GenerateGuidIfNecessary() noexcept;
static guid GetGuidOrGenerateForJson(const Json::Value& json) noexcept;
// BackgroundImageAlignment is 1 setting saved as 2 separate values

View file

@ -154,6 +154,19 @@ struct ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<::winr
return weight;
}
Json::Value ToJson(const ::winrt::Windows::UI::Text::FontWeight& val)
{
const auto weight{ val.Weight };
try
{
return BaseEnumMapper::ToJson(weight);
}
catch (SerializationError&)
{
return weight;
}
}
bool CanConvert(const Json::Value& json)
{
return BaseEnumMapper::CanConvert(json) || json.isUInt();

View file

@ -110,8 +110,7 @@ namespace TerminalAppUnitTests
void DynamicProfileTests::TestGenGuidsForProfiles()
{
// We'll generate GUIDs during
// CascadiaSettings::_ValidateProfilesHaveGuid. We should make sure that
// We'll generate GUIDs in the Profile::Guid getter. We should make sure that
// the GUID generated for a dynamic profile (with a source) is different
// than that of a profile without a source.
@ -167,14 +166,6 @@ namespace TerminalAppUnitTests
VERIFY_IS_FALSE(settings->_allProfiles.GetAt(4).HasGuid());
VERIFY_IS_FALSE(settings->_allProfiles.GetAt(4).Source().empty());
settings->_ValidateProfilesHaveGuid();
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(0).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(1).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(2).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(3).HasGuid());
VERIFY_IS_TRUE(settings->_allProfiles.GetAt(4).HasGuid());
VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(0).Guid(),
settings->_allProfiles.GetAt(1).Guid());
VERIFY_ARE_NOT_EQUAL(settings->_allProfiles.GetAt(0).Guid(),

View file

@ -120,7 +120,7 @@ namespace TerminalAppUnitTests
{
// Parse some profiles without guids. We should NOT generate new guids
// for them. If a profile doesn't have a GUID, we'll leave its _guid
// set to nullopt. CascadiaSettings::_ValidateProfilesHaveGuid will
// set to nullopt. The Profile::Guid() getter will
// ensure all profiles have a GUID that's actually set.
// The null guid _is_ a valid guid, so we won't re-generate that
// guid. null is _not_ a valid guid, so we'll leave that nullopt