Skip to content

Commit 28c50a4

Browse files
committed
Add Serializer to CascadiaSettings
1 parent 5c594f3 commit 28c50a4

File tree

12 files changed

+449
-7
lines changed

12 files changed

+449
-7
lines changed
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
#include "pch.h"
5+
6+
#include "../TerminalSettingsModel/ColorScheme.h"
7+
#include "../TerminalSettingsModel/CascadiaSettings.h"
8+
#include "JsonTestClass.h"
9+
#include "TestUtils.h"
10+
#include <defaults.h>
11+
#include "../ut_app/TestDynamicProfileGenerator.h"
12+
13+
using namespace Microsoft::Console;
14+
using namespace WEX::Logging;
15+
using namespace WEX::TestExecution;
16+
using namespace WEX::Common;
17+
using namespace winrt::Microsoft::Terminal::Settings::Model;
18+
using namespace winrt::Microsoft::Terminal::TerminalControl;
19+
20+
namespace SettingsModelLocalTests
21+
{
22+
// TODO:microsoft/terminal#3838:
23+
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
24+
// an updated TAEF that will let us install framework packages when the test
25+
// package is deployed. Until then, these tests won't deploy in CI.
26+
27+
class SerializationTests : public JsonTestClass
28+
{
29+
// Use a custom AppxManifest to ensure that we can activate winrt types
30+
// from our test. This property will tell taef to manually use this as
31+
// the AppxManifest for this test class.
32+
// This does not yet work for anything XAML-y. See TabTests.cpp for more
33+
// details on that.
34+
BEGIN_TEST_CLASS(SerializationTests)
35+
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
36+
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
37+
END_TEST_CLASS()
38+
39+
TEST_METHOD(GlobalSettings);
40+
TEST_METHOD(Profile);
41+
TEST_METHOD(ColorScheme);
42+
TEST_METHOD(CascadiaSettings);
43+
44+
TEST_CLASS_SETUP(ClassSetup)
45+
{
46+
InitializeJsonReader();
47+
return true;
48+
}
49+
50+
private:
51+
std::string toString(const Json::Value& json)
52+
{
53+
// set indentation to empty string to remove newlines
54+
// enableYAMLCompatibility adds a space after ':'
55+
Json::StreamWriterBuilder builder;
56+
builder["indentation"] = "";
57+
builder["enableYAMLCompatibility"] = true;
58+
return Json::writeString(builder, json);
59+
}
60+
};
61+
62+
void SerializationTests::GlobalSettings()
63+
{
64+
DebugBreak();
65+
// This needs to be in alphabetical order.
66+
const std::string globalsString{
67+
"{"
68+
"\"alwaysOnTop\": false,"
69+
"\"alwaysShowTabs\": true,"
70+
"\"confirmCloseAllTabs\": true,"
71+
"\"copyFormatting\": \"all\","
72+
"\"copyOnSelect\": false,"
73+
"\"debugFeatures\": true,"
74+
"\"defaultProfile\": \"{61c54bbd-c2c6-5271-96e7-009a87ff44bf}\","
75+
"\"experimental.input.forceVT\": false,"
76+
"\"experimental.rendering.forceFullRepaint\": false,"
77+
"\"experimental.rendering.software\": false,"
78+
"\"initialCols\": 120,"
79+
"\"initialPosition\": \",\","
80+
"\"initialRows\": 30,"
81+
"\"largePasteWarning\": true,"
82+
"\"launchMode\": \"default\","
83+
"\"multiLinePasteWarning\": true,"
84+
"\"showTabsInTitlebar\": true,"
85+
"\"showTerminalTitleInTitlebar\": true,"
86+
"\"snapToGridOnResize\": true,"
87+
"\"startOnUserLogin\": false,"
88+
"\"tabWidthMode\": \"equal\","
89+
"\"theme\": \"system\","
90+
"\"useTabSwitcher\": true,"
91+
"\"wordDelimiters\": \" /\\\\()\\\"'-.,:;<>~!@#$%^&*|+=[]{}~?\\u2502\""
92+
"}"
93+
};
94+
95+
const std::string smallGlobalsString{
96+
"{"
97+
"\"defaultProfile\": \"{61c54bbd-c2c6-5271-96e7-009a87ff44bf}\""
98+
"}"
99+
};
100+
101+
auto roundtripTest = [this](auto jsonString) {
102+
const auto json{ VerifyParseSucceeded(jsonString) };
103+
const auto globals{ implementation::GlobalAppSettings::FromJson(json) };
104+
105+
const auto result{ globals->ToJson() };
106+
107+
VERIFY_ARE_EQUAL(jsonString, toString(result));
108+
};
109+
110+
roundtripTest(globalsString);
111+
roundtripTest(smallGlobalsString);
112+
}
113+
114+
void SerializationTests::Profile()
115+
{
116+
DebugBreak();
117+
const std::string profileString{
118+
"{"
119+
"\"name\": \"Windows PowerShell\","
120+
"\"guid\": \"{61c54bbd-c2c6-5271-96e7-009a87ff44bf}\","
121+
"\"hidden\": false,"
122+
"\"foreground\": \"#AABBCC\","
123+
"\"background\": \"#BBCCAA\","
124+
"\"selectionBackground\": \"#CCAABB\","
125+
"\"cursorColor\": \"#CCBBAA\","
126+
"\"colorScheme\": \"Campbell\","
127+
"\"historySize\": 9001,"
128+
"\"snapOnInput\": true,"
129+
"\"altGrAliasing\": true,"
130+
"\"cursorHeight\": null,"
131+
"\"cursorShape\": \"bar\","
132+
"\"tabTitle\": null,"
133+
"\"fontWeight\": \"normal\","
134+
//"\"connectionType\": null,"
135+
"\"commandline\": \"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\","
136+
"\"fontFace\": \"Cascadia Mono\","
137+
"\"fontSize\": 12,"
138+
"\"useAcrylic\": false,"
139+
"\"suppressApplicationTitle\": false,"
140+
"\"closeOnExit\": \"graceful\","
141+
"\"padding\": \"8, 8, 8, 8\","
142+
"\"scrollbarState\": \"visible\","
143+
"\"startingDirectory\": \"%USERPROFILE%\","
144+
"\"icon\": \"ms-appx:///ProfileIcons/{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.png\","
145+
"\"backgroundImage\": null,"
146+
"\"backgroundImageOpacity\": 1.0,"
147+
"\"backgroundImageStretchMode\": \"uniformToFill\","
148+
"\"backgroundImageAlignment\": \"center\","
149+
"\"experimental.retroTerminalEffect\": false,"
150+
"\"antialiasingMode\": \"grayscale\""
151+
"\"tabColor\": \"#0C0C0C\","
152+
"}"
153+
};
154+
155+
const std::string smallProfileString{
156+
"{"
157+
"\"name\": \"Custom Profile\""
158+
"}"
159+
};
160+
161+
auto roundtripTest = [this](auto jsonString) {
162+
const auto json{ VerifyParseSucceeded(jsonString) };
163+
const auto globals{ implementation::Profile::FromJson(json) };
164+
165+
const auto result{ globals->ToJson() };
166+
167+
VERIFY_ARE_EQUAL(jsonString, toString(result));
168+
};
169+
170+
roundtripTest(profileString);
171+
roundtripTest(smallProfileString);
172+
}
173+
174+
void SerializationTests::ColorScheme()
175+
{
176+
DebugBreak();
177+
const std::string schemeString{ "{"
178+
"\"name\" : \"Campbell\","
179+
"\"foreground\" : \"#F2F2F2\","
180+
"\"background\" : \"#0C0C0C\","
181+
"\"selectionBackground\" : \"#131313\","
182+
"\"cursorColor\" : \"#FFFFFF\","
183+
"\"black\" : \"#0C0C0C\","
184+
"\"red\" : \"#C50F1F\","
185+
"\"green\" : \"#13A10E\","
186+
"\"yellow\" : \"#C19C00\""
187+
"\"blue\" : \"#0037DA\","
188+
"\"purple\" : \"#881798\","
189+
"\"cyan\" : \"#3A96DD\","
190+
"\"white\" : \"#CCCCCC\","
191+
"\"brightBlack\" : \"#767676\","
192+
"\"brightRed\" : \"#E74856\","
193+
"\"brightGreen\" : \"#16C60C\","
194+
"\"brightYellow\" : \"#F9F1A5\","
195+
"\"brightBlue\" : \"#3B78FF\","
196+
"\"brightPurple\" : \"#B4009E\","
197+
"\"brightCyan\" : \"#61D6D6\","
198+
"\"brightWhite\" : \"#F2F2F2\""
199+
"}" };
200+
201+
const auto json{ VerifyParseSucceeded(schemeString) };
202+
const auto profile{ implementation::ColorScheme::FromJson(json) };
203+
204+
const auto result{ profile->ToJson() };
205+
VERIFY_ARE_EQUAL(json, result);
206+
}
207+
208+
void SerializationTests::CascadiaSettings()
209+
{
210+
DebugBreak();
211+
const std::string settingsString{ "" };
212+
213+
const auto json{ VerifyParseSucceeded(settingsString) };
214+
const auto settings{ implementation::CascadiaSettings::FromJson(json) };
215+
216+
const auto result{ settings->ToJson() };
217+
VERIFY_ARE_EQUAL(json, result);
218+
}
219+
}

src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
<ClCompile Include="KeyBindingsTests.cpp" />
5252
<ClCompile Include="CommandTests.cpp" />
5353
<ClCompile Include="DeserializationTests.cpp" />
54+
<ClCompile Include="SerializationTests.cpp" />
5455
<ClCompile Include="pch.cpp">
5556
<PrecompiledHeader>Create</PrecompiledHeader>
5657
</ClCompile>

src/cascadia/TerminalSettingsModel/CascadiaSettings.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
7373
static com_ptr<CascadiaSettings> FromJson(const Json::Value& json);
7474
void LayerJson(const Json::Value& json);
7575

76+
Json::Value ToJson() const;
77+
7678
static hstring SettingsPath();
7779
static hstring DefaultSettingsPath();
7880

src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ static constexpr std::wstring_view UnpackagedSettingsFolderName{ L"Microsoft\\Wi
2828
static constexpr std::wstring_view DefaultsFilename{ L"defaults.json" };
2929

3030
static constexpr std::string_view SchemaKey{ "$schema" };
31+
static constexpr std::string_view SchemaValue{ "https://aka.ms/terminal-profiles-schema" };
3132
static constexpr std::string_view ProfilesKey{ "profiles" };
3233
static constexpr std::string_view DefaultSettingsKey{ "defaults" };
3334
static constexpr std::string_view ProfilesListKey{ "list" };
34-
static constexpr std::string_view KeybindingsKey{ "keybindings" };
35+
static constexpr std::string_view LegacyKeybindingsKey{ "keybindings" };
36+
static constexpr std::string_view ActionsKey{ "actions" };
3537
static constexpr std::string_view SchemesKey{ "schemes" };
3638

3739
static constexpr std::string_view DisabledProfileSourcesKey{ "disabledProfileSources" };
@@ -213,7 +215,7 @@ winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings::
213215
}
214216

215217
// If the user had keybinding settings preferences, we want to learn from them to make better defaults
216-
auto userKeybindings = resultPtr->_userSettings[JsonKey(KeybindingsKey)];
218+
auto userKeybindings = resultPtr->_userSettings[JsonKey(LegacyKeybindingsKey)];
217219
if (!userKeybindings.empty())
218220
{
219221
// If there are custom key bindings, let's understand what they are because maybe the defaults aren't good enough
@@ -1030,3 +1032,43 @@ const Json::Value& CascadiaSettings::_GetDisabledProfileSourcesJsonObject(const
10301032
}
10311033
return json[JsonKey(DisabledProfileSourcesKey)];
10321034
}
1035+
1036+
// Method Description:
1037+
// - Create a new serialized JsonObject from an instance of this class
1038+
// Arguments:
1039+
// - <none>
1040+
// Return Value:
1041+
// the JsonObject representing this instance
1042+
Json::Value CascadiaSettings::ToJson() const
1043+
{
1044+
// top-level json object
1045+
// directly inject "globals" and "$schema" into here
1046+
Json::Value json{ _globals->ToJson() };
1047+
JsonUtils::SetValueForKey(json, SchemaKey, JsonKey(SchemaValue));
1048+
1049+
// "profiles" will always be serialized as an object
1050+
Json::Value profiles{ Json::ValueType::objectValue };
1051+
profiles[JsonKey(DefaultSettingsKey)] = _userDefaultProfileSettings->ToJson();
1052+
profiles[JsonKey(ProfilesListKey)] = { Json::ValueType::arrayValue };
1053+
for (const auto& entry : _profiles)
1054+
{
1055+
const auto prof{ winrt::get_self<implementation::Profile>(entry) };
1056+
json[JsonKey(ProfilesKey)].append(prof->ToJson());
1057+
}
1058+
json[JsonKey(ProfilesKey)] = profiles;
1059+
1060+
// "schemes" will be an accumulation of _all_ the color schemes
1061+
Json::Value schemes{ Json::ValueType::arrayValue };
1062+
for (const auto& entry : _globals->ColorSchemes())
1063+
{
1064+
const auto scheme{ winrt::get_self<implementation::ColorScheme>(entry.Value()) };
1065+
json[JsonKey(SchemesKey)].append(scheme->ToJson());
1066+
}
1067+
json[JsonKey(SchemesKey)] = schemes;
1068+
1069+
// "actions" whatever blob we had in the file, we inject here
1070+
json[JsonKey(LegacyKeybindingsKey)] = _userSettings[JsonKey(LegacyKeybindingsKey)];
1071+
json[JsonKey(ActionsKey)] = _userSettings[JsonKey(ActionsKey)];
1072+
1073+
return json;
1074+
}

src/cascadia/TerminalSettingsModel/ColorScheme.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ void ColorScheme::LayerJson(const Json::Value& json)
129129
// Arguments:
130130
// - <none>
131131
// Return Value:
132-
// <none>
133-
Json::Value ColorScheme::ToJson()
132+
// - the JsonObject representing this instance
133+
Json::Value ColorScheme::ToJson() const
134134
{
135135
Json::Value json{ Json::ValueType::objectValue };
136136

src/cascadia/TerminalSettingsModel/ColorScheme.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
4040
bool ShouldBeLayered(const Json::Value& json) const;
4141
void LayerJson(const Json::Value& json);
4242

43-
Json::Value ToJson();
43+
Json::Value ToJson() const;
4444

4545
static std::optional<std::wstring> GetNameFromJson(const Json::Value& json);
4646

src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,49 @@ winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, winrt::Microso
341341
{
342342
return _commands.GetView();
343343
}
344+
345+
// Method Description:
346+
// - Create a new serialized JsonObject from an instance of this class
347+
// Arguments:
348+
// - <none>
349+
// Return Value:
350+
// - the JsonObject representing this instance
351+
Json::Value GlobalAppSettings::ToJson() const
352+
{
353+
Json::Value json{ Json::ValueType::objectValue };
354+
355+
// clang-format off
356+
JsonUtils::SetValueForKey(json, DefaultProfileKey, _UnparsedDefaultProfile);
357+
JsonUtils::SetValueForKey(json, AlwaysShowTabsKey, _AlwaysShowTabs);
358+
JsonUtils::SetValueForKey(json, ConfirmCloseAllKey, _ConfirmCloseAllTabs);
359+
JsonUtils::SetValueForKey(json, InitialRowsKey, _InitialRows);
360+
JsonUtils::SetValueForKey(json, InitialColsKey, _InitialCols);
361+
JsonUtils::SetValueForKey(json, InitialPositionKey, _InitialPosition);
362+
JsonUtils::SetValueForKey(json, ShowTitleInTitlebarKey, _ShowTitleInTitlebar);
363+
JsonUtils::SetValueForKey(json, ShowTabsInTitlebarKey, _ShowTabsInTitlebar);
364+
JsonUtils::SetValueForKey(json, WordDelimitersKey, _WordDelimiters);
365+
JsonUtils::SetValueForKey(json, CopyOnSelectKey, _CopyOnSelect);
366+
JsonUtils::SetValueForKey(json, CopyFormattingKey, _CopyFormatting);
367+
JsonUtils::SetValueForKey(json, WarnAboutLargePasteKey, _WarnAboutLargePaste);
368+
JsonUtils::SetValueForKey(json, WarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste);
369+
JsonUtils::SetValueForKey(json, LaunchModeKey, _LaunchMode);
370+
JsonUtils::SetValueForKey(json, ThemeKey, _Theme);
371+
JsonUtils::SetValueForKey(json, TabWidthModeKey, _TabWidthMode);
372+
JsonUtils::SetValueForKey(json, SnapToGridOnResizeKey, _SnapToGridOnResize);
373+
JsonUtils::SetValueForKey(json, DebugFeaturesKey, _DebugFeaturesEnabled);
374+
JsonUtils::SetValueForKey(json, ForceFullRepaintRenderingKey, _ForceFullRepaintRendering);
375+
JsonUtils::SetValueForKey(json, SoftwareRenderingKey, _SoftwareRendering);
376+
JsonUtils::SetValueForKey(json, ForceVTInputKey, _ForceVTInput);
377+
JsonUtils::SetValueForKey(json, EnableStartupTaskKey, _StartOnUserLogin);
378+
JsonUtils::SetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop);
379+
JsonUtils::SetValueForKey(json, UseTabSwitcherKey, _UseTabSwitcher);
380+
JsonUtils::SetValueForKey(json, DisableAnimationsKey, _DisableAnimations);
381+
// clang-format on
382+
383+
// TODO CARLOS: keymap needs to be serialized here
384+
// For deserialization, we iterate over each action in the Json and interpret it as a keybinding, then as a command.
385+
// Converting this back to JSON is a problem because we have no way to know if a Command and Keybinding come from
386+
// the same entry.
387+
388+
return json;
389+
}

src/cascadia/TerminalSettingsModel/GlobalAppSettings.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
4646
static com_ptr<GlobalAppSettings> FromJson(const Json::Value& json);
4747
void LayerJson(const Json::Value& json);
4848

49+
Json::Value ToJson() const;
50+
4951
std::vector<SettingsLoadWarnings> KeybindingsWarnings() const;
5052

5153
Windows::Foundation::Collections::IMapView<hstring, Model::Command> Commands() noexcept;

0 commit comments

Comments
 (0)