Files
terminal/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1619 lines
70 KiB
C++
Raw Normal View History

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../TerminalSettingsModel/ColorScheme.h"
#include "../TerminalSettingsModel/CascadiaSettings.h"
#include "../TerminalSettingsModel/resource.h"
#include "JsonTestClass.h"
#include "TestUtils.h"
using namespace Microsoft::Console;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
using namespace winrt::Microsoft::Terminal::Settings::Model;
Rename `Microsoft.Terminal.TerminalControl` to `.Control`; Split into dll & lib (#9472) **BE NOT AFRAID**. I know that there's 107 files in this PR, but almost all of it is just find/replacing `TerminalControl` with `Control`. This is the start of the work to move TermControl into multiple pieces, for #5000. The PR starts this work by: * Splits `TerminalControl` into separate lib and dll projects. We'll want control tests in the future, and for that, we'll need a lib. * Moves `ICoreSettings` back into the `Microsoft.Terminal.Core` namespace. We'll have other types in there soon too. * I could not tell you why this works suddenly. New VS versions? New cppwinrt version? Maybe we're just better at dealing with mdmerge bugs these days. * RENAMES `Microsoft.Terminal.TerminalControl` to `Microsoft.Terminal.Control`. This touches pretty much every file in the sln. Sorry about that (not sorry). An upcoming PR will move much of the logic in TermControl into a new `ControlCore` class that we'll add in `Microsoft.Terminal.Core`. `ControlCore` will then be unittest-able in the `UnitTests_TerminalCore`, which will help prevent regressions like #9455 ## Detailed Description of the Pull Request / Additional comments You're really gonna want to clean the sln first, then merge this into your branch, then rebuild. It's very likely that old winmds will get left behind. If you see something like ``` Error MDM2007 Cannot create type Microsoft.Terminal.TerminalControl.KeyModifiers in read-only metadata file Microsoft.Terminal.TerminalControl. ``` then that's what happened to you.
2021-03-17 15:47:24 -05:00
using namespace winrt::Microsoft::Terminal::Control;
// Different architectures will hash the same SendInput command to a different ID
// Check for the correct ID based on the architecture
#if defined(_M_IX86)
#define SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH "56911147"
#else
#define SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH "A020D2"
#endif
#if defined(_M_IX86)
#define SEND_INPUT2_ARCH_SPECIFIC_ACTION_HASH "35488AA6"
#else
#define SEND_INPUT2_ARCH_SPECIFIC_ACTION_HASH "58D1971"
#endif
namespace SettingsModelUnitTests
{
class SerializationTests : public JsonTestClass
{
TEST_CLASS(SerializationTests);
TEST_METHOD(GlobalSettings);
TEST_METHOD(Profile);
TEST_METHOD(ColorScheme);
TEST_METHOD(Actions);
TEST_METHOD(CascadiaSettings);
TEST_METHOD(LegacyFontSettings);
Allow inheriting env vars from `wt` again and other env var changes too (#15897) This PR is a few things: * part the first: Convert the `compatibility.reloadEnvironmentVariables` setting to a per-profile one. * The settings should migrate it from the user's old global place to the new one. * We also added it to "Profile>Advanced" while I was here. * Adds a new pair of commandline flags to `new-tab` and `split-pane`: `--inheritEnvironment` / `--reloadEnvironment` * On `wt` launch, bundle the entire environment that `wt` was spawned with, and put it into the `Remoting.CommandlineArgs`, and give them to the monarch (and ultimately, down to `TerminalPage` with the `AppCommandlineArgs`). DO THIS ALWAYS. * As a part of this, we’ll default to _reloading_ if there’s no explicit commandline set, and _inheriting_ if there is. * For example, `wt -- cmd` would inherit, and `wt -p “Command Prompt”` would reload.[^1] * This is a little wacky, but we’re trying to separate out the intentions here: * `wt -- cmd` feels like “I want to run cmd.exe (in a terminal tab)”. That feels like the user would _like_ environment variables from the calling process. They’re doing something more manual, so they get more refined control over it. * `wt` (or `wt -p “Command Prompt”`) is more like, “I want to run the Terminal (or, my Command Prompt profile) using whatever the Terminal would normally do”. So that feels more like a situation where it should just reload by default. (Of course, this will respect their settings here) ## References and Relevant Issues https://github.com/microsoft/terminal/issues/15496#issuecomment-1692450231 has more notes. ## Detailed Description of the Pull Request / Additional comments This is so VERY much plumbing. I'll try to leave comments in the interesting parts. ## PR Checklist - [x] This is not _all_ of #15496. We're also going to do a `-E foo=bar` arg on top of this. - [x] Tests added/passed - [x] Schema updated [^1]: In both these cases, plus the `environment` setting, of course.
2023-09-19 15:03:24 -05:00
TEST_METHOD(RoundtripReloadEnvVars);
TEST_METHOD(DontRoundtripNoReloadEnvVars);
Deduplicate identical inbox color schemes to heal user settings (#12800) Up until now, we have treated inbox, fragment and user color schemes the same: we load them all into one big map and when we save the settings file we write them *all* out. It's been a big annoyance pretty much forever. In addition to cluttering the user's settings file, it prevents us from making changes to the stock color schemes (like to change the cursor color, or to adjust the colors in Tango Dark, or what have you) because they're already copied in full in the user settings. It also means that we need some special UI affordances for color schemes that you are allowed to view but not to delete or rename. We also have a funny hardcoded list of color scheme names and we use that to determine whether they're "inbox" for UI purposes. Because of all that, we are hesitant to add *more* color schemes to the default set. This pull request resolves all of those issues at once. It: - Adds an "origin" to color schemes indicating where they're from (Inbox, Fragment, User, ...) - Replaces the Edit UI with a much simpler version that pretty much only has a "duplicate this color scheme to start editing it" button - Deletes color schemes that we consider to be equivalent to inbox ones; this allows us to finally disentangle the user's preferences from the terminal's. - Migrates all user settings that referred to schemes they may have modified (even implicitly!) to their modified versions. The equivalence check intentionally leaves out the cursor and selection colors, so that we have the freedom to change them in the future. The Origin is part of a new interface, `ISettingsModelObject`, which we can use in the future for things like Themes and Actions.
2024-02-27 13:07:08 -06:00
TEST_METHOD(RoundtripUserModifiedColorSchemeCollision);
TEST_METHOD(RoundtripUserModifiedColorSchemeCollisionUnusedByProfiles);
TEST_METHOD(RoundtripUserDeletedColorSchemeCollision);
TEST_METHOD(RoundtripGenerateActionID);
TEST_METHOD(NoGeneratedIDsForIterableAndNestedCommands);
TEST_METHOD(GeneratedActionIDsEqualForIdenticalCommands);
TEST_METHOD(RoundtripLegacyToModernActions);
TEST_METHOD(RoundtripUserActionsSameAsInBoxAreRemoved);
TEST_METHOD(RoundtripActionsSameNameDifferentCommandsAreRetained);
TEST_METHOD(MultipleActionsAreCollapsed);
Rewrite media resource handling (relative path icons, web URLs) (#19143) This pull request broadly rewrites how we handle all media resources in the Terminal settings model. ## What is a media resource? A media resource is any JSON property that refers to a file on disk, including: - `icon` on profile - `backgroundImage` on profile (appearance) - `pixelShaderPath` and `pixelShaderImagePath` on profile (appearance) - `icon` on command and the new tab menu entries The last two bullet points were newly discovered during the course of this work. ## Description of Changes In every place the settings model used to store a string for a media path, it now stores an `IMediaResource`. A media resource must be _resolved_ before it's used. When resolved, it can report whether it is `Ok` (found, valid) and what the final normalized path was. This allows the settings model to apply some new behaviors. One of those new behaviors is resolving media paths _relative to the JSON file that referred to them._ This means fragments and user settings can now contain _local_ images, pixel shaders and more and refer to them by filename. Relative path support requires us to track the path from which every media resource "container" was read[^2]. For "big" objects like Profile, we track it directly in the object and for each layer. This means that fragments **updating** a profile pass their relative base path into the mix. For some of the entries such as those in `newTabMenu`, we just wing it (#19191). For everything that is recursively owned by a parent that has a path (say each Command inside an ActionMap), we pass it in from the parent during media resolution. During resolution, we now track _exactly which layer_ an icon, background image, or pixel shader path came from and read the "base path" from only that layer. The base path is not inherited. Another new behavior is in the handling of web and other URLs. Canonical and a few other WSL distributors had to resort to web URLs for icons because we did not support loading them from the package. Julia tried to use `ms-appx://JuliaPackageNameHere/path/to/icon` for the same reason. Neither was intended, and of the two the second _should_ have worked but never could[^1]. For both `http(s?)` URLs and `ms-appx://` URLs which specify a package name, we now strip everything except the filename. As an example... If my fragment specifies `https://example.net/assets/foo.ico`, and my fragment was loaded from `C:\Fragments`, Terminal will look *only* at `C:\Fragments\foo.ico`. This works today for Julia (they put their icon in the fragment folder hoping that one day we would support this.) It will require some work from existing WSL distributors. I'm told that this is similar to how XML schema documents work. Now, icons are special. They support _Emoji_ and _Segoe Icons_. This PR adds an early pass to avoid resolving anything that looks like an emoji. This PR intentionally expands the heuristic definition of an emoji. It used to only cover 1-2 code unit emoji, which prevented the use of any emoji more complicated than "man in business suite levitating." An icon path will now be considered an emoji or symbol icon if it is composed of a single grapheme cluster (as measured by ICU.) This is not perfect, as it errs on the side of allowing too many things... but each of those things is technically a single grapheme cluster and is a perfectly legal FontIcon ;) Profile icons are _even more special_ than icons. They have an additional fallback behavior which we had to preserve. When a profile icon fails validation, or is expressly set to `null`, we fall back to the EXE specified in the command line. Because we do this fallback during resolution, _and the icon may be inherited by any higher profile,_ we can only resolve it against the commandline at the same level as the failed or nulled icon. Therefore, if you specify `icon: null` in your `defaults` profile, it will only ever resolve to `cmd.exe` for any profile that inherits it (unless you change `defaults.commandline`). This change expands support for the magic keywords `desktopWallpaper` and `none` to all media paths (yes, even `pixelShaderPath`... but also, `pixelShaderImagePath`!) It also expands support for _environment variables_ to all of those places. Yes, we had like forty different handlers for different types of string path. They are now uniform. ## Resource Validation Media resources which are not found are "rejected". If a rejected resource lives in _user_ settings, we will generate a warning and display it. In the future, we could detect this in the Settings UI and display a warning inline. ## Surprises I learned that `Windows.Foundation.Uri` parses file paths into `file://` URIs, but does not offer you a way to get the original file path back out. If you pass `C:\hello world`, _`Uri.Path`_ will return `/C:/hello%20world`. I kid you not. As a workaround, we bail out of URL handling if the `:` is too close to the start (indicating an absolute file path). ## Testing I added a narow test hook in the media resource resolver, which is removed completely by link-time code generation. It is a real joy. The test cases are all new and hopefully comprehensive. Closes #19075 Closes #16295 Closes #10359 (except it doesn't support fonts) Supersedes #16949 somewhat (`WT_SETTINGS_DIR`) Refs #18679 Refs #19215 (future work) Refs #19201 (future work) Refs #19191 (future work) [^1]: Handling a `ms-appx` path requires us to _add their package to our dependency graph_ for the entire duration during which the resource will be used. For us, that could be any time (like opening the command palette for the first time!) [^2]: We don't bother tracking where the defaults came from, because we control everything about them.
2025-08-05 15:47:50 -05:00
TEST_METHOD(ProfileWithInvalidIcon);
Add more settings model unit tests (#20117) ## Summary of the Pull Request Adds tests to `UnitTests_SettingsModel` to improve coverage. Tests include: - `SettingInheritanceFallback`: Settings inherit from user defaults; unset settings fall back to built-in defaults - `ClearSettingRestoresInheritance`: `ClearXxx()` removes the value at the current layer, causing fallback to the parent - `HasSettingAtSpecificLayer`: `HasXxx() `distinguishes explicitly set values from inherited ones - `ModifyProfileSettingAndRoundtrip`: Change a profile setting via setter and `ToJson()` reflects it - `ModifyGlobalSettingAndRoundtrip`: Change global settings via setter and `ToJson()` reflects them - `ModifyColorSchemeAndRoundtrip`: Change a color scheme property and the serialized JSON reflects it - `FixupUserSettingsDetectsChanges`: A clean roundtrip produces idempotent FixupUserSettings() (returns false) - `FixupCommandlinePatching`: 4 sub-cases: CMD/PowerShell short names get patched to full paths, no-op when already clean, custom profiles are untouched This also updates `TestCloneInheritanceTree` to verify that `HasXxx()` and settters that modify the clone don't modify the original. This is being done in preparation for auto-save to help ensure we don't have any regressions. ## Validation Steps Performed ✅ Tests pass ✅ Manually reviewed the new tests, they make sense and do add value (though some are less valuable than others, admittedly) ✅ Sent Copilot on a quest to ensure we're not adding redundant tests. It did catch a few and remove them fwiw.
2026-04-29 10:16:30 -07:00
TEST_METHOD(ModifyProfileSettingAndRoundtrip);
TEST_METHOD(ModifyGlobalSettingAndRoundtrip);
TEST_METHOD(ModifyColorSchemeAndRoundtrip);
TEST_METHOD(FixupUserSettingsDetectsChanges);
TEST_METHOD(FixupCommandlinePatching);
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_view& 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));
}
// Helper to remove the `$schema` property from a json object. We
// populate that based off the local path to the settings file. Of
// course, that's entirely unpredictable in tests. So cut it out before
// we do any sort of roundtrip testing.
static Json::Value removeSchema(Json::Value json)
{
if (json.isMember("$schema"))
{
json.removeMember("$schema");
}
return json;
}
};
void SerializationTests::GlobalSettings()
{
static constexpr std::string_view 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",
"theme": "system",
"snapToGridOnResize": true,
"disableAnimations": false,
2021-10-28 17:38:23 +02:00
"trimPaste": true,
Replace `confirmCloseAllTabs` with `confirmOnClose` (#20055) ## Summary of the Pull Request Replaces the `warning.confirmCloseAllTabs` setting with a `warning.confirmOnClose` enum setting that accepts the following: - `never`: don't present a warning dialog when closing a session - `automatic`: present a warning dialog when closing multiple tabs/panes at once - `always`: present a warning dialog when closing any session The confirmation dialog contains a "don't ask me again" checkbox. When checked, we update the setting to `never`. This setting also affects the following actions: - "close other tabs" - "close tabs after" - "close other panes" - "quit" The appropriate confirmation dialog is shown in these scenarios. We also present an aggregate dialog instead of prompting the user once per tab/pane. If there are no other tabs/panes, we don't present a dialog and treat the key binding as unhandled (passing the key through). ## References and Relevant Issues Iteration of #19944 ## Validation Steps Performed - closing a tab: - ✅ 1 pane --> no dialog - ✅ 2 panes --> dialog - ✅ action and middle clicking tab trigger same flow - close all other tabs: - ✅ no other tabs --> no dialog - ✅ 1 other tab --> dialog - close all other panes: - ✅ 1 pane --> no dialog - ✅ 2 panes --> dialog - close all tabs after the current tab: - ✅ no tabs after --> no dialog (even if tabs before) - ✅ 1 tab after --> dialog - close window: - ✅ 2 tabs --> dialog - ✅ 2 panes --> dialog - ✅ 1 tab with one pane --> no dialog - Quit the Terminal: - ✅ 3 windows --> dialog - ✅1 window --> dialog - ✅ "don't ask me again" checkbox checked --> setting changed to "never" - ✅ "never" --> no dialog for scenarios above - ✅ "always" --> dialog always appears, even when closing a single pane ## PR Checklist Closes #5301 Closes #6641 "don't ask me again" checkbox is also mentioned in #10000 Co-authored by @zadjii-msft
2026-05-04 10:43:34 -07:00
"warning.confirmOnClose": "automatic",
"warning.inputService" : true,
"warning.largePaste" : true,
"warning.multiLinePaste" : "automatic",
"actions": [],
"keybindings": []
})" };
static constexpr std::string_view smallGlobalsString{ R"(
{
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"actions": [],
"keybindings": []
})" };
RoundtripTest<implementation::GlobalAppSettings>(globalsString);
RoundtripTest<implementation::GlobalAppSettings>(smallGlobalsString);
}
void SerializationTests::Profile()
{
static constexpr std::string_view 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,
"font": {
"face": "Cascadia Mono",
"size": 12,
"weight": "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,
"opacity": 50,
"backgroundImage": "made_you_look.jpeg",
"backgroundImageStretchMode": "uniformToFill",
"backgroundImageAlignment": "center",
"backgroundImageOpacity": 1,
"scrollbarState": "visible",
"snapOnInput": true,
"historySize": 9001,
"closeOnExit": "graceful",
"experimental.retroTerminalEffect": false,
"environment":
{
"KEY_1": "VALUE_1",
"KEY_2": "%KEY_1%",
"KEY_3": "%PATH%"
}
})" };
static constexpr std::string_view 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
static constexpr std::string_view weirdProfileString{ R"(
{
"guid" : "{8b039d4d-77ca-5a83-88e1-dfc8e895a127}",
"name": "Weird Profile",
"hidden": false,
"tabColor": null,
"foreground": null,
"source": "local"
})" };
static constexpr std::string_view profileWithIcon{ R"(
{
"guid" : "{8b039d4d-77ca-5a83-88e1-dfc8e895a127}",
"name": "profileWithIcon",
"hidden": false,
"icon": "foo.png"
})" };
static constexpr std::string_view profileWithNullIcon{ R"(
{
"guid" : "{8b039d4d-77ca-5a83-88e1-dfc8e895a127}",
"name": "profileWithNullIcon",
"hidden": false,
"icon": null
})" };
static constexpr std::string_view profileWithNoIcon{ R"(
{
"guid" : "{8b039d4d-77ca-5a83-88e1-dfc8e895a127}",
"name": "profileWithNoIcon",
"hidden": false,
"icon": "none"
})" };
RoundtripTest<implementation::Profile>(profileString);
RoundtripTest<implementation::Profile>(smallProfileString);
RoundtripTest<implementation::Profile>(weirdProfileString);
RoundtripTest<implementation::Profile>(profileWithIcon);
RoundtripTest<implementation::Profile>(profileWithNullIcon);
RoundtripTest<implementation::Profile>(profileWithNoIcon);
}
void SerializationTests::ColorScheme()
{
static constexpr std::string_view 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::Actions()
{
// simple command
static constexpr std::string_view actionsString1{ R"([
{ "command": "paste", "id": "Test.Paste" }
])" };
// complex command
static constexpr std::string_view actionsString2A{ R"([
{ "command": { "action": "setTabColor" }, "id": "Test.SetTabColor" }
])" };
static constexpr std::string_view actionsString2B{ R"([
{ "command": { "action": "setTabColor", "color": "#112233" }, "id": "Test.SetTabColor112233" }
])" };
static constexpr std::string_view actionsString2C{ R"([
{ "command": { "action": "copy" }, "id": "Test.Copy" },
{ "command": { "action": "copy", "singleLine": true, "copyFormatting": "html" }, "id": "Test.CopyWithArgs" }
])" };
// simple command with key chords
static constexpr std::string_view actionsString3{ R"({ "actions": [
{ "command": "toggleAlwaysOnTop", "id": "Test.ToggleAlwaysOnTop" } ],
"keybindings": [
{ "keys": "ctrl+a", "id": "Test.ToggleAlwaysOnTop" },
{ "keys": "ctrl+b", "id": "Test.ToggleAlwaysOnTop" } ]})" };
// complex command with key chords
static constexpr std::string_view actionsString4A{ R"({ "actions":[
{ "command": { "action": "adjustFontSize", "delta": 1 }, "id": "Test.EnlargeFont" } ],
"keybindings": [
{ "keys": "ctrl+c", "id": "Test.EnlargeFont" },
{ "keys": "ctrl+d", "id": "Test.EnlargeFont" } ]})" };
// command with name and icon and multiple key chords
static constexpr std::string_view actionsString5{ R"({ "actions":[
{ "icon": "image.png", "name": "Scroll To Top Name", "command": "scrollToTop", "id": "Test.ScrollToTop" } ],
"keybindings": [
{ "id": "Test.ScrollToTop", "keys": "ctrl+f" },
{ "id": "Test.ScrollToTop", "keys": "ctrl+e" } ]})" };
// complex command with new terminal args
static constexpr std::string_view actionsString6{ R"([
{ "command": { "action": "newTab", "index": 0 }, "id": "Test.NewTerminal" },
])" };
// complex command with meaningful null arg
static constexpr std::string_view actionsString7{ R"([
{ "command": { "action": "renameWindow", "name": null }, "id": "Test.MeaningfulNull" }
])" };
// nested command
static constexpr std::string_view actionsString8{ R"([
{
"name": "Change font size...",
"commands": [
{ "command": { "action": "adjustFontSize", "delta": 1 } },
{ "command": { "action": "adjustFontSize", "delta": -1 } },
{ "command": "resetFontSize" },
]
}
])" };
// iterable command
static constexpr std::string_view actionsString9A{ R"([
{
"name": "New tab",
"commands": [
{
"iterateOn": "profiles",
"icon": "${profile.icon}",
"name": "${profile.name}",
"command": { "action": "newTab", "profile": "${profile.name}" }
}
]
}
])" };
static constexpr std::string_view actionsString9B{ R"([
{
Reduce usage of Json::Value throughout Terminal.Settings.Model (#11184) This commit reduces the code surface that interacts with raw JSON data, reducing code complexity and improving maintainability. Files that needed to be changed drastically were additionally cleaned up to remove any code cruft that has accrued over time. In order to facility this the following changes were made: * Move JSON handling from `CascadiaSettings` into `SettingsLoader` This allows us to use STL containers for data model instances. For instance profiles are now added to a hashmap for O(1) lookup. * JSON parsing within `SettingsLoader` doesn't differentiate between user, inbox and fragment JSON data, reducing code complexity and size. It also centralizes common concerns, like profile deduplication and ensuring that all profiles are assigned a GUID. * Direct JSON modification, like the insertion of dynamic profiles into settings.json were removed. This vastly reduces code complexity, but unfortunately removes support for comments in JSON on first start. * `ColorScheme`s cannot be layered. As such its `LayerJson` API was replaced with `FromJson`, allowing us to remove JSON-based color scheme validation. * `Profile`s used to test their wish to layer using `ShouldBeLayered`, which was replaced with a GUID-based hashmap lookup on previously parsed profiles. Further changes were made as improvements upon the previous changes: * Compact the JSON files embedded binary, saving 28kB * Prevent double-initialization of the color table in `ColorScheme` * Making `til::color` getters `constexpr`, allow better optimizations The result is a reduction of: * 48kB binary size for the Settings.Model.dll * 5-10% startup duration * 26% code for the `CascadiaSettings` class * 1% overall code in this project Furthermore this results in the following breaking changes: * The long deprecated "globals" settings object will not be detected and no warning will be created during load. * The initial creation of a new settings.json will not produce helpful comments. Both cases are caused by the removal of manual JSON handling and the move to representing the settings file with model objects instead ## PR Checklist * [x] Closes #5276 * [x] Closes #7421 * [x] I work here * [x] Tests added/passed ## Validation Steps Performed * Out-of-box-experience is identical to before ✔️ (Except for the settings.json file lacking comments.) * Existing user settings load correctly ✔️ * New WSL instances are added to user settings ✔️ * New fragments are added to user settings ✔️ * All profiles are assigned GUIDs ✔️
2021-09-22 18:27:31 +02:00
"commands":
[
{
Reduce usage of Json::Value throughout Terminal.Settings.Model (#11184) This commit reduces the code surface that interacts with raw JSON data, reducing code complexity and improving maintainability. Files that needed to be changed drastically were additionally cleaned up to remove any code cruft that has accrued over time. In order to facility this the following changes were made: * Move JSON handling from `CascadiaSettings` into `SettingsLoader` This allows us to use STL containers for data model instances. For instance profiles are now added to a hashmap for O(1) lookup. * JSON parsing within `SettingsLoader` doesn't differentiate between user, inbox and fragment JSON data, reducing code complexity and size. It also centralizes common concerns, like profile deduplication and ensuring that all profiles are assigned a GUID. * Direct JSON modification, like the insertion of dynamic profiles into settings.json were removed. This vastly reduces code complexity, but unfortunately removes support for comments in JSON on first start. * `ColorScheme`s cannot be layered. As such its `LayerJson` API was replaced with `FromJson`, allowing us to remove JSON-based color scheme validation. * `Profile`s used to test their wish to layer using `ShouldBeLayered`, which was replaced with a GUID-based hashmap lookup on previously parsed profiles. Further changes were made as improvements upon the previous changes: * Compact the JSON files embedded binary, saving 28kB * Prevent double-initialization of the color table in `ColorScheme` * Making `til::color` getters `constexpr`, allow better optimizations The result is a reduction of: * 48kB binary size for the Settings.Model.dll * 5-10% startup duration * 26% code for the `CascadiaSettings` class * 1% overall code in this project Furthermore this results in the following breaking changes: * The long deprecated "globals" settings object will not be detected and no warning will be created during load. * The initial creation of a new settings.json will not produce helpful comments. Both cases are caused by the removal of manual JSON handling and the move to representing the settings file with model objects instead ## PR Checklist * [x] Closes #5276 * [x] Closes #7421 * [x] I work here * [x] Tests added/passed ## Validation Steps Performed * Out-of-box-experience is identical to before ✔️ (Except for the settings.json file lacking comments.) * Existing user settings load correctly ✔️ * New WSL instances are added to user settings ✔️ * New fragments are added to user settings ✔️ * All profiles are assigned GUIDs ✔️
2021-09-22 18:27:31 +02:00
"command":
{
"action": "sendInput",
"input": "${profile.name}"
},
"iterateOn": "profiles"
}
],
"name": "Send Input ..."
}
])" };
static constexpr std::string_view actionsString9C{ R""([
{
Reduce usage of Json::Value throughout Terminal.Settings.Model (#11184) This commit reduces the code surface that interacts with raw JSON data, reducing code complexity and improving maintainability. Files that needed to be changed drastically were additionally cleaned up to remove any code cruft that has accrued over time. In order to facility this the following changes were made: * Move JSON handling from `CascadiaSettings` into `SettingsLoader` This allows us to use STL containers for data model instances. For instance profiles are now added to a hashmap for O(1) lookup. * JSON parsing within `SettingsLoader` doesn't differentiate between user, inbox and fragment JSON data, reducing code complexity and size. It also centralizes common concerns, like profile deduplication and ensuring that all profiles are assigned a GUID. * Direct JSON modification, like the insertion of dynamic profiles into settings.json were removed. This vastly reduces code complexity, but unfortunately removes support for comments in JSON on first start. * `ColorScheme`s cannot be layered. As such its `LayerJson` API was replaced with `FromJson`, allowing us to remove JSON-based color scheme validation. * `Profile`s used to test their wish to layer using `ShouldBeLayered`, which was replaced with a GUID-based hashmap lookup on previously parsed profiles. Further changes were made as improvements upon the previous changes: * Compact the JSON files embedded binary, saving 28kB * Prevent double-initialization of the color table in `ColorScheme` * Making `til::color` getters `constexpr`, allow better optimizations The result is a reduction of: * 48kB binary size for the Settings.Model.dll * 5-10% startup duration * 26% code for the `CascadiaSettings` class * 1% overall code in this project Furthermore this results in the following breaking changes: * The long deprecated "globals" settings object will not be detected and no warning will be created during load. * The initial creation of a new settings.json will not produce helpful comments. Both cases are caused by the removal of manual JSON handling and the move to representing the settings file with model objects instead ## PR Checklist * [x] Closes #5276 * [x] Closes #7421 * [x] I work here * [x] Tests added/passed ## Validation Steps Performed * Out-of-box-experience is identical to before ✔️ (Except for the settings.json file lacking comments.) * Existing user settings load correctly ✔️ * New WSL instances are added to user settings ✔️ * New fragments are added to user settings ✔️ * All profiles are assigned GUIDs ✔️
2021-09-22 18:27:31 +02:00
"commands":
[
{
Reduce usage of Json::Value throughout Terminal.Settings.Model (#11184) This commit reduces the code surface that interacts with raw JSON data, reducing code complexity and improving maintainability. Files that needed to be changed drastically were additionally cleaned up to remove any code cruft that has accrued over time. In order to facility this the following changes were made: * Move JSON handling from `CascadiaSettings` into `SettingsLoader` This allows us to use STL containers for data model instances. For instance profiles are now added to a hashmap for O(1) lookup. * JSON parsing within `SettingsLoader` doesn't differentiate between user, inbox and fragment JSON data, reducing code complexity and size. It also centralizes common concerns, like profile deduplication and ensuring that all profiles are assigned a GUID. * Direct JSON modification, like the insertion of dynamic profiles into settings.json were removed. This vastly reduces code complexity, but unfortunately removes support for comments in JSON on first start. * `ColorScheme`s cannot be layered. As such its `LayerJson` API was replaced with `FromJson`, allowing us to remove JSON-based color scheme validation. * `Profile`s used to test their wish to layer using `ShouldBeLayered`, which was replaced with a GUID-based hashmap lookup on previously parsed profiles. Further changes were made as improvements upon the previous changes: * Compact the JSON files embedded binary, saving 28kB * Prevent double-initialization of the color table in `ColorScheme` * Making `til::color` getters `constexpr`, allow better optimizations The result is a reduction of: * 48kB binary size for the Settings.Model.dll * 5-10% startup duration * 26% code for the `CascadiaSettings` class * 1% overall code in this project Furthermore this results in the following breaking changes: * The long deprecated "globals" settings object will not be detected and no warning will be created during load. * The initial creation of a new settings.json will not produce helpful comments. Both cases are caused by the removal of manual JSON handling and the move to representing the settings file with model objects instead ## PR Checklist * [x] Closes #5276 * [x] Closes #7421 * [x] I work here * [x] Tests added/passed ## Validation Steps Performed * Out-of-box-experience is identical to before ✔️ (Except for the settings.json file lacking comments.) * Existing user settings load correctly ✔️ * New WSL instances are added to user settings ✔️ * New fragments are added to user settings ✔️ * All profiles are assigned GUIDs ✔️
2021-09-22 18:27:31 +02:00
"commands":
[
{
Reduce usage of Json::Value throughout Terminal.Settings.Model (#11184) This commit reduces the code surface that interacts with raw JSON data, reducing code complexity and improving maintainability. Files that needed to be changed drastically were additionally cleaned up to remove any code cruft that has accrued over time. In order to facility this the following changes were made: * Move JSON handling from `CascadiaSettings` into `SettingsLoader` This allows us to use STL containers for data model instances. For instance profiles are now added to a hashmap for O(1) lookup. * JSON parsing within `SettingsLoader` doesn't differentiate between user, inbox and fragment JSON data, reducing code complexity and size. It also centralizes common concerns, like profile deduplication and ensuring that all profiles are assigned a GUID. * Direct JSON modification, like the insertion of dynamic profiles into settings.json were removed. This vastly reduces code complexity, but unfortunately removes support for comments in JSON on first start. * `ColorScheme`s cannot be layered. As such its `LayerJson` API was replaced with `FromJson`, allowing us to remove JSON-based color scheme validation. * `Profile`s used to test their wish to layer using `ShouldBeLayered`, which was replaced with a GUID-based hashmap lookup on previously parsed profiles. Further changes were made as improvements upon the previous changes: * Compact the JSON files embedded binary, saving 28kB * Prevent double-initialization of the color table in `ColorScheme` * Making `til::color` getters `constexpr`, allow better optimizations The result is a reduction of: * 48kB binary size for the Settings.Model.dll * 5-10% startup duration * 26% code for the `CascadiaSettings` class * 1% overall code in this project Furthermore this results in the following breaking changes: * The long deprecated "globals" settings object will not be detected and no warning will be created during load. * The initial creation of a new settings.json will not produce helpful comments. Both cases are caused by the removal of manual JSON handling and the move to representing the settings file with model objects instead ## PR Checklist * [x] Closes #5276 * [x] Closes #7421 * [x] I work here * [x] Tests added/passed ## Validation Steps Performed * Out-of-box-experience is identical to before ✔️ (Except for the settings.json file lacking comments.) * Existing user settings load correctly ✔️ * New WSL instances are added to user settings ✔️ * New fragments are added to user settings ✔️ * All profiles are assigned GUIDs ✔️
2021-09-22 18:27:31 +02:00
"command":
{
"action": "sendInput",
"input": "${profile.name} ${scheme.name}"
},
"iterateOn": "schemes"
}
],
"iterateOn": "profiles",
"name": "nest level (${profile.name})"
}
],
"name": "Send Input (Evil) ..."
}
])"" };
static constexpr std::string_view actionsString9D{ R""([
{
Reduce usage of Json::Value throughout Terminal.Settings.Model (#11184) This commit reduces the code surface that interacts with raw JSON data, reducing code complexity and improving maintainability. Files that needed to be changed drastically were additionally cleaned up to remove any code cruft that has accrued over time. In order to facility this the following changes were made: * Move JSON handling from `CascadiaSettings` into `SettingsLoader` This allows us to use STL containers for data model instances. For instance profiles are now added to a hashmap for O(1) lookup. * JSON parsing within `SettingsLoader` doesn't differentiate between user, inbox and fragment JSON data, reducing code complexity and size. It also centralizes common concerns, like profile deduplication and ensuring that all profiles are assigned a GUID. * Direct JSON modification, like the insertion of dynamic profiles into settings.json were removed. This vastly reduces code complexity, but unfortunately removes support for comments in JSON on first start. * `ColorScheme`s cannot be layered. As such its `LayerJson` API was replaced with `FromJson`, allowing us to remove JSON-based color scheme validation. * `Profile`s used to test their wish to layer using `ShouldBeLayered`, which was replaced with a GUID-based hashmap lookup on previously parsed profiles. Further changes were made as improvements upon the previous changes: * Compact the JSON files embedded binary, saving 28kB * Prevent double-initialization of the color table in `ColorScheme` * Making `til::color` getters `constexpr`, allow better optimizations The result is a reduction of: * 48kB binary size for the Settings.Model.dll * 5-10% startup duration * 26% code for the `CascadiaSettings` class * 1% overall code in this project Furthermore this results in the following breaking changes: * The long deprecated "globals" settings object will not be detected and no warning will be created during load. * The initial creation of a new settings.json will not produce helpful comments. Both cases are caused by the removal of manual JSON handling and the move to representing the settings file with model objects instead ## PR Checklist * [x] Closes #5276 * [x] Closes #7421 * [x] I work here * [x] Tests added/passed ## Validation Steps Performed * Out-of-box-experience is identical to before ✔️ (Except for the settings.json file lacking comments.) * Existing user settings load correctly ✔️ * New WSL instances are added to user settings ✔️ * New fragments are added to user settings ✔️ * All profiles are assigned GUIDs ✔️
2021-09-22 18:27:31 +02:00
"command":
{
"action": "newTab",
"profile": "${profile.name}"
},
"icon": "${profile.icon}",
"iterateOn": "profiles",
"name": "${profile.name}: New tab"
}
])"" };
// unbound command
static constexpr std::string_view actionsString10{ R"({ "actions": [],
"keybindings": [
{ "id": null, "keys": "ctrl+c" } ]})" };
Log::Comment(L"simple command");
RoundtripTest<implementation::ActionMap>(actionsString1);
Log::Comment(L"complex commands");
RoundtripTest<implementation::ActionMap>(actionsString2A);
RoundtripTest<implementation::ActionMap>(actionsString2B);
RoundtripTest<implementation::ActionMap>(actionsString2C);
// ActionMap has effectively 2 "to json" calls we need to make, one for the actions and one for the keybindings
// So we cannot use RoundtripTest<ActionMap> for actions + keychords, just use RoundTripTest<GlobalAppSettings>
Log::Comment(L"simple command with key chords");
RoundtripTest<implementation::GlobalAppSettings>(actionsString3);
Log::Comment(L"complex commands with key chords");
RoundtripTest<implementation::GlobalAppSettings>(actionsString4A);
Log::Comment(L"command with name and icon and multiple key chords");
RoundtripTest<implementation::GlobalAppSettings>(actionsString5);
Log::Comment(L"complex command with new terminal args");
RoundtripTest<implementation::ActionMap>(actionsString6);
Log::Comment(L"complex command with meaningful null arg");
RoundtripTest<implementation::ActionMap>(actionsString7);
Log::Comment(L"nested command");
RoundtripTest<implementation::ActionMap>(actionsString8);
Log::Comment(L"iterable command");
RoundtripTest<implementation::ActionMap>(actionsString9A);
RoundtripTest<implementation::ActionMap>(actionsString9B);
RoundtripTest<implementation::ActionMap>(actionsString9C);
RoundtripTest<implementation::ActionMap>(actionsString9D);
Log::Comment(L"unbound command");
RoundtripTest<implementation::GlobalAppSettings>(actionsString10);
}
void SerializationTests::CascadiaSettings()
{
static constexpr std::string_view settingsString{ R"({
Reduce usage of Json::Value throughout Terminal.Settings.Model (#11184) This commit reduces the code surface that interacts with raw JSON data, reducing code complexity and improving maintainability. Files that needed to be changed drastically were additionally cleaned up to remove any code cruft that has accrued over time. In order to facility this the following changes were made: * Move JSON handling from `CascadiaSettings` into `SettingsLoader` This allows us to use STL containers for data model instances. For instance profiles are now added to a hashmap for O(1) lookup. * JSON parsing within `SettingsLoader` doesn't differentiate between user, inbox and fragment JSON data, reducing code complexity and size. It also centralizes common concerns, like profile deduplication and ensuring that all profiles are assigned a GUID. * Direct JSON modification, like the insertion of dynamic profiles into settings.json were removed. This vastly reduces code complexity, but unfortunately removes support for comments in JSON on first start. * `ColorScheme`s cannot be layered. As such its `LayerJson` API was replaced with `FromJson`, allowing us to remove JSON-based color scheme validation. * `Profile`s used to test their wish to layer using `ShouldBeLayered`, which was replaced with a GUID-based hashmap lookup on previously parsed profiles. Further changes were made as improvements upon the previous changes: * Compact the JSON files embedded binary, saving 28kB * Prevent double-initialization of the color table in `ColorScheme` * Making `til::color` getters `constexpr`, allow better optimizations The result is a reduction of: * 48kB binary size for the Settings.Model.dll * 5-10% startup duration * 26% code for the `CascadiaSettings` class * 1% overall code in this project Furthermore this results in the following breaking changes: * The long deprecated "globals" settings object will not be detected and no warning will be created during load. * The initial creation of a new settings.json will not produce helpful comments. Both cases are caused by the removal of manual JSON handling and the move to representing the settings file with model objects instead ## PR Checklist * [x] Closes #5276 * [x] Closes #7421 * [x] I work here * [x] Tests added/passed ## Validation Steps Performed * Out-of-box-experience is identical to before ✔️ (Except for the settings.json file lacking comments.) * Existing user settings load correctly ✔️ * New WSL instances are added to user settings ✔️ * New fragments are added to user settings ✔️ * All profiles are assigned GUIDs ✔️
2021-09-22 18:27:31 +02:00
"$help" : "https://aka.ms/terminal-documentation",
"$schema" : "https://aka.ms/terminal-profiles-schema",
"defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
"disabledProfileSources": [ "Windows.Terminal.Wsl" ],
"newTabMenu":
[
{
"type": "remainingProfiles"
}
],
Reduce usage of Json::Value throughout Terminal.Settings.Model (#11184) This commit reduces the code surface that interacts with raw JSON data, reducing code complexity and improving maintainability. Files that needed to be changed drastically were additionally cleaned up to remove any code cruft that has accrued over time. In order to facility this the following changes were made: * Move JSON handling from `CascadiaSettings` into `SettingsLoader` This allows us to use STL containers for data model instances. For instance profiles are now added to a hashmap for O(1) lookup. * JSON parsing within `SettingsLoader` doesn't differentiate between user, inbox and fragment JSON data, reducing code complexity and size. It also centralizes common concerns, like profile deduplication and ensuring that all profiles are assigned a GUID. * Direct JSON modification, like the insertion of dynamic profiles into settings.json were removed. This vastly reduces code complexity, but unfortunately removes support for comments in JSON on first start. * `ColorScheme`s cannot be layered. As such its `LayerJson` API was replaced with `FromJson`, allowing us to remove JSON-based color scheme validation. * `Profile`s used to test their wish to layer using `ShouldBeLayered`, which was replaced with a GUID-based hashmap lookup on previously parsed profiles. Further changes were made as improvements upon the previous changes: * Compact the JSON files embedded binary, saving 28kB * Prevent double-initialization of the color table in `ColorScheme` * Making `til::color` getters `constexpr`, allow better optimizations The result is a reduction of: * 48kB binary size for the Settings.Model.dll * 5-10% startup duration * 26% code for the `CascadiaSettings` class * 1% overall code in this project Furthermore this results in the following breaking changes: * The long deprecated "globals" settings object will not be detected and no warning will be created during load. * The initial creation of a new settings.json will not produce helpful comments. Both cases are caused by the removal of manual JSON handling and the move to representing the settings file with model objects instead ## PR Checklist * [x] Closes #5276 * [x] Closes #7421 * [x] I work here * [x] Tests added/passed ## Validation Steps Performed * Out-of-box-experience is identical to before ✔️ (Except for the settings.json file lacking comments.) * Existing user settings load correctly ✔️ * New WSL instances are added to user settings ✔️ * New fragments are added to user settings ✔️ * All profiles are assigned GUIDs ✔️
2021-09-22 18:27:31 +02:00
"profiles": {
"defaults": {
"font": {
"face": "Zamora Code"
}
},
"list": [
{
"font": { "face": "Cascadia Code" },
"guid": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
"name": "HowettShell"
},
{
"hidden": true,
"guid": "{c08b0496-e71c-5503-b84e-3af7a7a6d2a7}",
"name": "BhojwaniShell"
},
{
"antialiasingMode": "aliased",
"guid": "{fe9df758-ac22-5c20-922d-c7766cdd13af}",
"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": "sendInput", "input": "VT Griese Mode" }, "id": "Test.SendInput" }
],
"keybindings": [
{ "id": "Test.SendInput", "keys": "ctrl+k" }
],
"theme": "system",
"themes": []
Reduce usage of Json::Value throughout Terminal.Settings.Model (#11184) This commit reduces the code surface that interacts with raw JSON data, reducing code complexity and improving maintainability. Files that needed to be changed drastically were additionally cleaned up to remove any code cruft that has accrued over time. In order to facility this the following changes were made: * Move JSON handling from `CascadiaSettings` into `SettingsLoader` This allows us to use STL containers for data model instances. For instance profiles are now added to a hashmap for O(1) lookup. * JSON parsing within `SettingsLoader` doesn't differentiate between user, inbox and fragment JSON data, reducing code complexity and size. It also centralizes common concerns, like profile deduplication and ensuring that all profiles are assigned a GUID. * Direct JSON modification, like the insertion of dynamic profiles into settings.json were removed. This vastly reduces code complexity, but unfortunately removes support for comments in JSON on first start. * `ColorScheme`s cannot be layered. As such its `LayerJson` API was replaced with `FromJson`, allowing us to remove JSON-based color scheme validation. * `Profile`s used to test their wish to layer using `ShouldBeLayered`, which was replaced with a GUID-based hashmap lookup on previously parsed profiles. Further changes were made as improvements upon the previous changes: * Compact the JSON files embedded binary, saving 28kB * Prevent double-initialization of the color table in `ColorScheme` * Making `til::color` getters `constexpr`, allow better optimizations The result is a reduction of: * 48kB binary size for the Settings.Model.dll * 5-10% startup duration * 26% code for the `CascadiaSettings` class * 1% overall code in this project Furthermore this results in the following breaking changes: * The long deprecated "globals" settings object will not be detected and no warning will be created during load. * The initial creation of a new settings.json will not produce helpful comments. Both cases are caused by the removal of manual JSON handling and the move to representing the settings file with model objects instead ## PR Checklist * [x] Closes #5276 * [x] Closes #7421 * [x] I work here * [x] Tests added/passed ## Validation Steps Performed * Out-of-box-experience is identical to before ✔️ (Except for the settings.json file lacking comments.) * Existing user settings load correctly ✔️ * New WSL instances are added to user settings ✔️ * New fragments are added to user settings ✔️ * All profiles are assigned GUIDs ✔️
2021-09-22 18:27:31 +02:00
})" };
const auto settings{ winrt::make_self<implementation::CascadiaSettings>(settingsString) };
const auto result{ settings->ToJson() };
VERIFY_ARE_EQUAL(toString(removeSchema(VerifyParseSucceeded(settingsString))),
toString(removeSchema(result)));
}
void SerializationTests::LegacyFontSettings()
{
static constexpr std::string_view profileString{ R"(
{
"name": "Profile with legacy font settings",
"fontFace": "Cascadia Mono",
"fontSize": 12,
"fontWeight": "normal"
})" };
static constexpr std::string_view expectedOutput{ R"(
{
"name": "Profile with legacy font settings",
"font": {
"face": "Cascadia Mono",
"size": 12,
"weight": "normal"
}
})" };
const auto json{ VerifyParseSucceeded(profileString) };
const auto settings{ implementation::Profile::FromJson(json) };
const auto result{ settings->ToJson() };
const auto jsonOutput{ VerifyParseSucceeded(expectedOutput) };
VERIFY_ARE_EQUAL(toString(jsonOutput), toString(result));
}
Allow inheriting env vars from `wt` again and other env var changes too (#15897) This PR is a few things: * part the first: Convert the `compatibility.reloadEnvironmentVariables` setting to a per-profile one. * The settings should migrate it from the user's old global place to the new one. * We also added it to "Profile>Advanced" while I was here. * Adds a new pair of commandline flags to `new-tab` and `split-pane`: `--inheritEnvironment` / `--reloadEnvironment` * On `wt` launch, bundle the entire environment that `wt` was spawned with, and put it into the `Remoting.CommandlineArgs`, and give them to the monarch (and ultimately, down to `TerminalPage` with the `AppCommandlineArgs`). DO THIS ALWAYS. * As a part of this, we’ll default to _reloading_ if there’s no explicit commandline set, and _inheriting_ if there is. * For example, `wt -- cmd` would inherit, and `wt -p “Command Prompt”` would reload.[^1] * This is a little wacky, but we’re trying to separate out the intentions here: * `wt -- cmd` feels like “I want to run cmd.exe (in a terminal tab)”. That feels like the user would _like_ environment variables from the calling process. They’re doing something more manual, so they get more refined control over it. * `wt` (or `wt -p “Command Prompt”`) is more like, “I want to run the Terminal (or, my Command Prompt profile) using whatever the Terminal would normally do”. So that feels more like a situation where it should just reload by default. (Of course, this will respect their settings here) ## References and Relevant Issues https://github.com/microsoft/terminal/issues/15496#issuecomment-1692450231 has more notes. ## Detailed Description of the Pull Request / Additional comments This is so VERY much plumbing. I'll try to leave comments in the interesting parts. ## PR Checklist - [x] This is not _all_ of #15496. We're also going to do a `-E foo=bar` arg on top of this. - [x] Tests added/passed - [x] Schema updated [^1]: In both these cases, plus the `environment` setting, of course.
2023-09-19 15:03:24 -05:00
void SerializationTests::RoundtripReloadEnvVars()
{
static constexpr std::string_view oldSettingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"compatibility.reloadEnvironmentVariables": false,
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"historySize": 1,
"commandline": "cmd.exe"
}
],
"actions": [
{
"name": "foo",
"command": "closePane",
"keys": "ctrl+shift+w"
}
]
})" };
static constexpr std::string_view newSettingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles":
{
"defaults":
{
"compatibility.reloadEnvironmentVariables": false
},
"list":
[
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"historySize": 1,
"commandline": "cmd.exe"
}
]
},
"actions": [
{
"name": "foo",
"command": "closePane",
"keys": "ctrl+shift+w"
}
]
})" };
implementation::SettingsLoader oldLoader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
Allow inheriting env vars from `wt` again and other env var changes too (#15897) This PR is a few things: * part the first: Convert the `compatibility.reloadEnvironmentVariables` setting to a per-profile one. * The settings should migrate it from the user's old global place to the new one. * We also added it to "Profile>Advanced" while I was here. * Adds a new pair of commandline flags to `new-tab` and `split-pane`: `--inheritEnvironment` / `--reloadEnvironment` * On `wt` launch, bundle the entire environment that `wt` was spawned with, and put it into the `Remoting.CommandlineArgs`, and give them to the monarch (and ultimately, down to `TerminalPage` with the `AppCommandlineArgs`). DO THIS ALWAYS. * As a part of this, we’ll default to _reloading_ if there’s no explicit commandline set, and _inheriting_ if there is. * For example, `wt -- cmd` would inherit, and `wt -p “Command Prompt”` would reload.[^1] * This is a little wacky, but we’re trying to separate out the intentions here: * `wt -- cmd` feels like “I want to run cmd.exe (in a terminal tab)”. That feels like the user would _like_ environment variables from the calling process. They’re doing something more manual, so they get more refined control over it. * `wt` (or `wt -p “Command Prompt”`) is more like, “I want to run the Terminal (or, my Command Prompt profile) using whatever the Terminal would normally do”. So that feels more like a situation where it should just reload by default. (Of course, this will respect their settings here) ## References and Relevant Issues https://github.com/microsoft/terminal/issues/15496#issuecomment-1692450231 has more notes. ## Detailed Description of the Pull Request / Additional comments This is so VERY much plumbing. I'll try to leave comments in the interesting parts. ## PR Checklist - [x] This is not _all_ of #15496. We're also going to do a `-E foo=bar` arg on top of this. - [x] Tests added/passed - [x] Schema updated [^1]: In both these cases, plus the `environment` setting, of course.
2023-09-19 15:03:24 -05:00
oldLoader.MergeInboxIntoUserSettings();
oldLoader.FinalizeLayering();
VERIFY_IS_TRUE(oldLoader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk");
const auto oldSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(oldLoader));
const auto oldResult{ oldSettings->ToJson() };
implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
Allow inheriting env vars from `wt` again and other env var changes too (#15897) This PR is a few things: * part the first: Convert the `compatibility.reloadEnvironmentVariables` setting to a per-profile one. * The settings should migrate it from the user's old global place to the new one. * We also added it to "Profile>Advanced" while I was here. * Adds a new pair of commandline flags to `new-tab` and `split-pane`: `--inheritEnvironment` / `--reloadEnvironment` * On `wt` launch, bundle the entire environment that `wt` was spawned with, and put it into the `Remoting.CommandlineArgs`, and give them to the monarch (and ultimately, down to `TerminalPage` with the `AppCommandlineArgs`). DO THIS ALWAYS. * As a part of this, we’ll default to _reloading_ if there’s no explicit commandline set, and _inheriting_ if there is. * For example, `wt -- cmd` would inherit, and `wt -p “Command Prompt”` would reload.[^1] * This is a little wacky, but we’re trying to separate out the intentions here: * `wt -- cmd` feels like “I want to run cmd.exe (in a terminal tab)”. That feels like the user would _like_ environment variables from the calling process. They’re doing something more manual, so they get more refined control over it. * `wt` (or `wt -p “Command Prompt”`) is more like, “I want to run the Terminal (or, my Command Prompt profile) using whatever the Terminal would normally do”. So that feels more like a situation where it should just reload by default. (Of course, this will respect their settings here) ## References and Relevant Issues https://github.com/microsoft/terminal/issues/15496#issuecomment-1692450231 has more notes. ## Detailed Description of the Pull Request / Additional comments This is so VERY much plumbing. I'll try to leave comments in the interesting parts. ## PR Checklist - [x] This is not _all_ of #15496. We're also going to do a `-E foo=bar` arg on top of this. - [x] Tests added/passed - [x] Schema updated [^1]: In both these cases, plus the `environment` setting, of course.
2023-09-19 15:03:24 -05:00
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
newLoader.FixupUserSettings();
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
const auto newResult{ newSettings->ToJson() };
VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult));
}
void SerializationTests::DontRoundtripNoReloadEnvVars()
{
// Kinda like the above test, but confirming that _nothing_ happens if
// we don't have a setting to migrate.
static constexpr std::string_view oldSettingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"historySize": 1,
"commandline": "cmd.exe"
}
],
"actions": [
{
"name": "foo",
"command": "closePane",
"keys": "ctrl+shift+w"
}
]
})" };
implementation::SettingsLoader oldLoader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
Allow inheriting env vars from `wt` again and other env var changes too (#15897) This PR is a few things: * part the first: Convert the `compatibility.reloadEnvironmentVariables` setting to a per-profile one. * The settings should migrate it from the user's old global place to the new one. * We also added it to "Profile>Advanced" while I was here. * Adds a new pair of commandline flags to `new-tab` and `split-pane`: `--inheritEnvironment` / `--reloadEnvironment` * On `wt` launch, bundle the entire environment that `wt` was spawned with, and put it into the `Remoting.CommandlineArgs`, and give them to the monarch (and ultimately, down to `TerminalPage` with the `AppCommandlineArgs`). DO THIS ALWAYS. * As a part of this, we’ll default to _reloading_ if there’s no explicit commandline set, and _inheriting_ if there is. * For example, `wt -- cmd` would inherit, and `wt -p “Command Prompt”` would reload.[^1] * This is a little wacky, but we’re trying to separate out the intentions here: * `wt -- cmd` feels like “I want to run cmd.exe (in a terminal tab)”. That feels like the user would _like_ environment variables from the calling process. They’re doing something more manual, so they get more refined control over it. * `wt` (or `wt -p “Command Prompt”`) is more like, “I want to run the Terminal (or, my Command Prompt profile) using whatever the Terminal would normally do”. So that feels more like a situation where it should just reload by default. (Of course, this will respect their settings here) ## References and Relevant Issues https://github.com/microsoft/terminal/issues/15496#issuecomment-1692450231 has more notes. ## Detailed Description of the Pull Request / Additional comments This is so VERY much plumbing. I'll try to leave comments in the interesting parts. ## PR Checklist - [x] This is not _all_ of #15496. We're also going to do a `-E foo=bar` arg on top of this. - [x] Tests added/passed - [x] Schema updated [^1]: In both these cases, plus the `environment` setting, of course.
2023-09-19 15:03:24 -05:00
oldLoader.MergeInboxIntoUserSettings();
oldLoader.FinalizeLayering();
oldLoader.FixupUserSettings();
const auto oldSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(oldLoader));
const auto oldResult{ oldSettings->ToJson() };
Log::Comment(L"Now, create a _new_ settings object from the re-serialization of the first");
implementation::SettingsLoader newLoader{ toString(oldResult), implementation::LoadStringResource(IDR_DEFAULTS) };
Allow inheriting env vars from `wt` again and other env var changes too (#15897) This PR is a few things: * part the first: Convert the `compatibility.reloadEnvironmentVariables` setting to a per-profile one. * The settings should migrate it from the user's old global place to the new one. * We also added it to "Profile>Advanced" while I was here. * Adds a new pair of commandline flags to `new-tab` and `split-pane`: `--inheritEnvironment` / `--reloadEnvironment` * On `wt` launch, bundle the entire environment that `wt` was spawned with, and put it into the `Remoting.CommandlineArgs`, and give them to the monarch (and ultimately, down to `TerminalPage` with the `AppCommandlineArgs`). DO THIS ALWAYS. * As a part of this, we’ll default to _reloading_ if there’s no explicit commandline set, and _inheriting_ if there is. * For example, `wt -- cmd` would inherit, and `wt -p “Command Prompt”` would reload.[^1] * This is a little wacky, but we’re trying to separate out the intentions here: * `wt -- cmd` feels like “I want to run cmd.exe (in a terminal tab)”. That feels like the user would _like_ environment variables from the calling process. They’re doing something more manual, so they get more refined control over it. * `wt` (or `wt -p “Command Prompt”`) is more like, “I want to run the Terminal (or, my Command Prompt profile) using whatever the Terminal would normally do”. So that feels more like a situation where it should just reload by default. (Of course, this will respect their settings here) ## References and Relevant Issues https://github.com/microsoft/terminal/issues/15496#issuecomment-1692450231 has more notes. ## Detailed Description of the Pull Request / Additional comments This is so VERY much plumbing. I'll try to leave comments in the interesting parts. ## PR Checklist - [x] This is not _all_ of #15496. We're also going to do a `-E foo=bar` arg on top of this. - [x] Tests added/passed - [x] Schema updated [^1]: In both these cases, plus the `environment` setting, of course.
2023-09-19 15:03:24 -05:00
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
newLoader.FixupUserSettings();
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
VERIFY_IS_FALSE(newSettings->ProfileDefaults().HasReloadEnvironmentVariables(),
L"Ensure that the new settings object didn't find a reloadEnvironmentVariables");
}
Deduplicate identical inbox color schemes to heal user settings (#12800) Up until now, we have treated inbox, fragment and user color schemes the same: we load them all into one big map and when we save the settings file we write them *all* out. It's been a big annoyance pretty much forever. In addition to cluttering the user's settings file, it prevents us from making changes to the stock color schemes (like to change the cursor color, or to adjust the colors in Tango Dark, or what have you) because they're already copied in full in the user settings. It also means that we need some special UI affordances for color schemes that you are allowed to view but not to delete or rename. We also have a funny hardcoded list of color scheme names and we use that to determine whether they're "inbox" for UI purposes. Because of all that, we are hesitant to add *more* color schemes to the default set. This pull request resolves all of those issues at once. It: - Adds an "origin" to color schemes indicating where they're from (Inbox, Fragment, User, ...) - Replaces the Edit UI with a much simpler version that pretty much only has a "duplicate this color scheme to start editing it" button - Deletes color schemes that we consider to be equivalent to inbox ones; this allows us to finally disentangle the user's preferences from the terminal's. - Migrates all user settings that referred to schemes they may have modified (even implicitly!) to their modified versions. The equivalence check intentionally leaves out the cursor and selection colors, so that we have the freedom to change them in the future. The Origin is part of a new interface, `ISettingsModelObject`, which we can use in the future for things like Themes and Actions.
2024-02-27 13:07:08 -06:00
void SerializationTests::RoundtripUserModifiedColorSchemeCollision()
{
static constexpr std::string_view oldSettingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
},
{
"name": "profile1",
"colorScheme": "Tango Dark",
"guid": "{d0a65a9d-8665-4128-97a4-a581aa747aa7}"
}
],
"schemes": [
{
"background": "#121314",
"black": "#121314",
"blue": "#121314",
"brightBlack": "#121314",
"brightBlue": "#121314",
"brightCyan": "#121314",
"brightGreen": "#121314",
"brightPurple": "#121314",
"brightRed": "#121314",
"brightWhite": "#121314",
"brightYellow": "#121314",
"cursorColor": "#121314",
"cyan": "#121314",
"foreground": "#121314",
"green": "#121314",
"name": "Campbell",
"purple": "#121314",
"red": "#121314",
"selectionBackground": "#121314",
"white": "#121314",
"yellow": "#121314"
},
{
"background": "#000000",
"black": "#000000",
"blue": "#3465A4",
"brightBlack": "#555753",
"brightBlue": "#729FCF",
"brightCyan": "#34E2E2",
"brightGreen": "#8AE234",
"brightPurple": "#AD7FA8",
"brightRed": "#EF2929",
"brightWhite": "#EEEEEC",
"brightYellow": "#FCE94F",
"cursorColor": "#FFFFFF",
"cyan": "#06989A",
"foreground": "#D3D7CF",
"green": "#4E9A06",
"name": "Tango Dark",
"purple": "#75507B",
"red": "#CC0000",
"selectionBackground": "#FFFFFF",
"white": "#D3D7CF",
"yellow": "#C4A000"
},
]
})" };
// Key differences: one fewer color scheme (Tango Dark has been deleted) and defaults.colorScheme is set.
static constexpr std::string_view newSettingsJson{ R"-(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles":
{
"defaults": {
"colorScheme": "Campbell (modified)"
},
"list":
[
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
},
{
"name": "profile1",
"colorScheme": "Tango Dark",
"guid": "{d0a65a9d-8665-4128-97a4-a581aa747aa7}"
}
]
},
"actions": [ ],
"schemes": [
{
"background": "#121314",
"black": "#121314",
"blue": "#121314",
"brightBlack": "#121314",
"brightBlue": "#121314",
"brightCyan": "#121314",
"brightGreen": "#121314",
"brightPurple": "#121314",
"brightRed": "#121314",
"brightWhite": "#121314",
"brightYellow": "#121314",
"cursorColor": "#121314",
"cyan": "#121314",
"foreground": "#121314",
"green": "#121314",
"name": "Campbell",
"purple": "#121314",
"red": "#121314",
"selectionBackground": "#121314",
"white": "#121314",
"yellow": "#121314"
}
]
})-" };
implementation::SettingsLoader oldLoader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
Deduplicate identical inbox color schemes to heal user settings (#12800) Up until now, we have treated inbox, fragment and user color schemes the same: we load them all into one big map and when we save the settings file we write them *all* out. It's been a big annoyance pretty much forever. In addition to cluttering the user's settings file, it prevents us from making changes to the stock color schemes (like to change the cursor color, or to adjust the colors in Tango Dark, or what have you) because they're already copied in full in the user settings. It also means that we need some special UI affordances for color schemes that you are allowed to view but not to delete or rename. We also have a funny hardcoded list of color scheme names and we use that to determine whether they're "inbox" for UI purposes. Because of all that, we are hesitant to add *more* color schemes to the default set. This pull request resolves all of those issues at once. It: - Adds an "origin" to color schemes indicating where they're from (Inbox, Fragment, User, ...) - Replaces the Edit UI with a much simpler version that pretty much only has a "duplicate this color scheme to start editing it" button - Deletes color schemes that we consider to be equivalent to inbox ones; this allows us to finally disentangle the user's preferences from the terminal's. - Migrates all user settings that referred to schemes they may have modified (even implicitly!) to their modified versions. The equivalence check intentionally leaves out the cursor and selection colors, so that we have the freedom to change them in the future. The Origin is part of a new interface, `ISettingsModelObject`, which we can use in the future for things like Themes and Actions.
2024-02-27 13:07:08 -06:00
oldLoader.MergeInboxIntoUserSettings();
oldLoader.FinalizeLayering();
VERIFY_IS_TRUE(oldLoader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk");
const auto oldSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(oldLoader));
const auto oldResult{ oldSettings->ToJson() };
implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
Deduplicate identical inbox color schemes to heal user settings (#12800) Up until now, we have treated inbox, fragment and user color schemes the same: we load them all into one big map and when we save the settings file we write them *all* out. It's been a big annoyance pretty much forever. In addition to cluttering the user's settings file, it prevents us from making changes to the stock color schemes (like to change the cursor color, or to adjust the colors in Tango Dark, or what have you) because they're already copied in full in the user settings. It also means that we need some special UI affordances for color schemes that you are allowed to view but not to delete or rename. We also have a funny hardcoded list of color scheme names and we use that to determine whether they're "inbox" for UI purposes. Because of all that, we are hesitant to add *more* color schemes to the default set. This pull request resolves all of those issues at once. It: - Adds an "origin" to color schemes indicating where they're from (Inbox, Fragment, User, ...) - Replaces the Edit UI with a much simpler version that pretty much only has a "duplicate this color scheme to start editing it" button - Deletes color schemes that we consider to be equivalent to inbox ones; this allows us to finally disentangle the user's preferences from the terminal's. - Migrates all user settings that referred to schemes they may have modified (even implicitly!) to their modified versions. The equivalence check intentionally leaves out the cursor and selection colors, so that we have the freedom to change them in the future. The Origin is part of a new interface, `ISettingsModelObject`, which we can use in the future for things like Themes and Actions.
2024-02-27 13:07:08 -06:00
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
newLoader.FixupUserSettings();
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
const auto newResult{ newSettings->ToJson() };
VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult));
}
void SerializationTests::RoundtripUserModifiedColorSchemeCollisionUnusedByProfiles()
{
static constexpr std::string_view oldSettingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
}
],
"schemes": [
{
"background": "#111111",
"black": "#111111",
"blue": "#111111",
"brightBlack": "#111111",
"brightBlue": "#111111",
"brightCyan": "#111111",
"brightGreen": "#111111",
"brightPurple": "#111111",
"brightRed": "#111111",
"brightWhite": "#111111",
"brightYellow": "#111111",
"cursorColor": "#111111",
"cyan": "#111111",
"foreground": "#111111",
"green": "#111111",
"name": "Tango Dark",
"purple": "#111111",
"red": "#111111",
"selectionBackground": "#111111",
"white": "#111111",
"yellow": "#111111"
},
]
})" };
// Key differences: Tango Dark has been renamed; nothing else has changed
static constexpr std::string_view newSettingsJson{ R"-(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles":
{
"list":
[
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
}
]
},
"actions": [ ],
"schemes": [
{
"background": "#111111",
"black": "#111111",
"blue": "#111111",
"brightBlack": "#111111",
"brightBlue": "#111111",
"brightCyan": "#111111",
"brightGreen": "#111111",
"brightPurple": "#111111",
"brightRed": "#111111",
"brightWhite": "#111111",
"brightYellow": "#111111",
"cursorColor": "#111111",
"cyan": "#111111",
"foreground": "#111111",
"green": "#111111",
"name": "Tango Dark (modified)",
"purple": "#111111",
"red": "#111111",
"selectionBackground": "#111111",
"white": "#111111",
"yellow": "#111111"
},
]
})-" };
implementation::SettingsLoader oldLoader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
Deduplicate identical inbox color schemes to heal user settings (#12800) Up until now, we have treated inbox, fragment and user color schemes the same: we load them all into one big map and when we save the settings file we write them *all* out. It's been a big annoyance pretty much forever. In addition to cluttering the user's settings file, it prevents us from making changes to the stock color schemes (like to change the cursor color, or to adjust the colors in Tango Dark, or what have you) because they're already copied in full in the user settings. It also means that we need some special UI affordances for color schemes that you are allowed to view but not to delete or rename. We also have a funny hardcoded list of color scheme names and we use that to determine whether they're "inbox" for UI purposes. Because of all that, we are hesitant to add *more* color schemes to the default set. This pull request resolves all of those issues at once. It: - Adds an "origin" to color schemes indicating where they're from (Inbox, Fragment, User, ...) - Replaces the Edit UI with a much simpler version that pretty much only has a "duplicate this color scheme to start editing it" button - Deletes color schemes that we consider to be equivalent to inbox ones; this allows us to finally disentangle the user's preferences from the terminal's. - Migrates all user settings that referred to schemes they may have modified (even implicitly!) to their modified versions. The equivalence check intentionally leaves out the cursor and selection colors, so that we have the freedom to change them in the future. The Origin is part of a new interface, `ISettingsModelObject`, which we can use in the future for things like Themes and Actions.
2024-02-27 13:07:08 -06:00
oldLoader.MergeInboxIntoUserSettings();
oldLoader.FinalizeLayering();
VERIFY_IS_TRUE(oldLoader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk");
const auto oldSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(oldLoader));
const auto oldResult{ oldSettings->ToJson() };
implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
Deduplicate identical inbox color schemes to heal user settings (#12800) Up until now, we have treated inbox, fragment and user color schemes the same: we load them all into one big map and when we save the settings file we write them *all* out. It's been a big annoyance pretty much forever. In addition to cluttering the user's settings file, it prevents us from making changes to the stock color schemes (like to change the cursor color, or to adjust the colors in Tango Dark, or what have you) because they're already copied in full in the user settings. It also means that we need some special UI affordances for color schemes that you are allowed to view but not to delete or rename. We also have a funny hardcoded list of color scheme names and we use that to determine whether they're "inbox" for UI purposes. Because of all that, we are hesitant to add *more* color schemes to the default set. This pull request resolves all of those issues at once. It: - Adds an "origin" to color schemes indicating where they're from (Inbox, Fragment, User, ...) - Replaces the Edit UI with a much simpler version that pretty much only has a "duplicate this color scheme to start editing it" button - Deletes color schemes that we consider to be equivalent to inbox ones; this allows us to finally disentangle the user's preferences from the terminal's. - Migrates all user settings that referred to schemes they may have modified (even implicitly!) to their modified versions. The equivalence check intentionally leaves out the cursor and selection colors, so that we have the freedom to change them in the future. The Origin is part of a new interface, `ISettingsModelObject`, which we can use in the future for things like Themes and Actions.
2024-02-27 13:07:08 -06:00
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
newLoader.FixupUserSettings();
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
const auto newResult{ newSettings->ToJson() };
VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult));
}
void SerializationTests::RoundtripUserDeletedColorSchemeCollision()
{
static constexpr std::string_view oldSettingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
}
],
"schemes": [
{
"name": "Tango Dark",
"foreground": "#D3D7CF",
"background": "#000000",
"cursorColor": "#FFFFFF",
"black": "#000000",
"red": "#CC0000",
"green": "#4E9A06",
"yellow": "#C4A000",
"blue": "#3465A4",
"purple": "#75507B",
"cyan": "#06989A",
"white": "#D3D7CF",
"brightBlack": "#555753",
"brightRed": "#EF2929",
"brightGreen": "#8AE234",
"brightYellow": "#FCE94F",
"brightBlue": "#729FCF",
"brightPurple": "#AD7FA8",
"brightCyan": "#34E2E2",
"brightWhite": "#EEEEEC"
}
]
})" };
// Key differences: Tango Dark has been deleted, as it was identical to the inbox one.
static constexpr std::string_view newSettingsJson{ R"-(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles":
{
"list":
[
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
}
]
},
"actions": [ ],
"schemes": [ ]
})-" };
implementation::SettingsLoader oldLoader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
Deduplicate identical inbox color schemes to heal user settings (#12800) Up until now, we have treated inbox, fragment and user color schemes the same: we load them all into one big map and when we save the settings file we write them *all* out. It's been a big annoyance pretty much forever. In addition to cluttering the user's settings file, it prevents us from making changes to the stock color schemes (like to change the cursor color, or to adjust the colors in Tango Dark, or what have you) because they're already copied in full in the user settings. It also means that we need some special UI affordances for color schemes that you are allowed to view but not to delete or rename. We also have a funny hardcoded list of color scheme names and we use that to determine whether they're "inbox" for UI purposes. Because of all that, we are hesitant to add *more* color schemes to the default set. This pull request resolves all of those issues at once. It: - Adds an "origin" to color schemes indicating where they're from (Inbox, Fragment, User, ...) - Replaces the Edit UI with a much simpler version that pretty much only has a "duplicate this color scheme to start editing it" button - Deletes color schemes that we consider to be equivalent to inbox ones; this allows us to finally disentangle the user's preferences from the terminal's. - Migrates all user settings that referred to schemes they may have modified (even implicitly!) to their modified versions. The equivalence check intentionally leaves out the cursor and selection colors, so that we have the freedom to change them in the future. The Origin is part of a new interface, `ISettingsModelObject`, which we can use in the future for things like Themes and Actions.
2024-02-27 13:07:08 -06:00
oldLoader.MergeInboxIntoUserSettings();
oldLoader.FinalizeLayering();
VERIFY_IS_TRUE(oldLoader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk");
const auto oldSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(oldLoader));
const auto oldResult{ oldSettings->ToJson() };
implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
Deduplicate identical inbox color schemes to heal user settings (#12800) Up until now, we have treated inbox, fragment and user color schemes the same: we load them all into one big map and when we save the settings file we write them *all* out. It's been a big annoyance pretty much forever. In addition to cluttering the user's settings file, it prevents us from making changes to the stock color schemes (like to change the cursor color, or to adjust the colors in Tango Dark, or what have you) because they're already copied in full in the user settings. It also means that we need some special UI affordances for color schemes that you are allowed to view but not to delete or rename. We also have a funny hardcoded list of color scheme names and we use that to determine whether they're "inbox" for UI purposes. Because of all that, we are hesitant to add *more* color schemes to the default set. This pull request resolves all of those issues at once. It: - Adds an "origin" to color schemes indicating where they're from (Inbox, Fragment, User, ...) - Replaces the Edit UI with a much simpler version that pretty much only has a "duplicate this color scheme to start editing it" button - Deletes color schemes that we consider to be equivalent to inbox ones; this allows us to finally disentangle the user's preferences from the terminal's. - Migrates all user settings that referred to schemes they may have modified (even implicitly!) to their modified versions. The equivalence check intentionally leaves out the cursor and selection colors, so that we have the freedom to change them in the future. The Origin is part of a new interface, `ISettingsModelObject`, which we can use in the future for things like Themes and Actions.
2024-02-27 13:07:08 -06:00
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
newLoader.FixupUserSettings();
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
const auto newResult{ newSettings->ToJson() };
VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult));
}
void SerializationTests::RoundtripGenerateActionID()
{
static constexpr std::string_view oldSettingsJson{ R"(
{
"actions": [
{
"name": "foo",
"command": { "action": "sendInput", "input": "just some input" },
"keys": "ctrl+shift+w"
}
]
})" };
implementation::SettingsLoader loader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
loader.MergeInboxIntoUserSettings();
loader.FinalizeLayering();
loader.FixupUserSettings();
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto oldResult{ settings->ToJson() };
const auto sendInputCmd = settings->ActionMap().GetActionByKeyChord(KeyChord{ true, false, true, false, 87, 0 });
std::string_view expectedID{ R"(User.sendInput.)" SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH };
VERIFY_ARE_EQUAL(sendInputCmd.ID(), winrt::to_hstring(expectedID));
}
void SerializationTests::NoGeneratedIDsForIterableAndNestedCommands()
{
// for iterable commands, nested commands, and user-defined actions that already have
// an ID, we do not need to generate an ID
static constexpr std::string_view oldSettingsJson{ R"(
{
"actions": [
{
"name": "foo",
"command": "closePane",
"id": "thisIsMyClosePane"
},
{
"iterateOn": "profiles",
"icon": "${profile.icon}",
"name": "${profile.name}",
"command": { "action": "newTab", "profile": "${profile.name}" }
},
{
"name": "Change font size...",
"commands": [
{ "command": { "action": "adjustFontSize", "delta": 1 } },
{ "command": { "action": "adjustFontSize", "delta": -1 } },
{ "command": "resetFontSize" },
]
}
]
})" };
implementation::SettingsLoader oldLoader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
oldLoader.MergeInboxIntoUserSettings();
oldLoader.FinalizeLayering();
VERIFY_IS_FALSE(oldLoader.FixupUserSettings(), L"Validate that there is no need to write back to disk");
}
void SerializationTests::GeneratedActionIDsEqualForIdenticalCommands()
{
static constexpr std::string_view settingsJson1{ R"(
{
"actions": [
{
"name": "foo",
"command": { "action": "sendInput", "input": "this is some other input string" },
"keys": "ctrl+shift+w"
}
]
})" };
// Both settings files define the same action, so the generated ID should be the same for both
static constexpr std::string_view settingsJson2{ R"(
{
"actions": [
{
"name": "foo",
"command": { "action": "sendInput", "input": "this is some other input string" },
"keys": "ctrl+shift+w"
}
]
})" };
implementation::SettingsLoader loader1{ settingsJson1, implementation::LoadStringResource(IDR_DEFAULTS) };
loader1.MergeInboxIntoUserSettings();
loader1.FinalizeLayering();
loader1.FixupUserSettings();
const auto settings1 = winrt::make_self<implementation::CascadiaSettings>(std::move(loader1));
const auto result1{ settings1->ToJson() };
implementation::SettingsLoader loader2{ settingsJson2, implementation::LoadStringResource(IDR_DEFAULTS) };
loader2.MergeInboxIntoUserSettings();
loader2.FinalizeLayering();
loader2.FixupUserSettings();
const auto settings2 = winrt::make_self<implementation::CascadiaSettings>(std::move(loader2));
const auto result2{ settings2->ToJson() };
const auto sendInputCmd1 = settings1->ActionMap().GetActionByKeyChord(KeyChord{ true, false, true, false, 87, 0 });
const auto sendInputCmd2 = settings2->ActionMap().GetActionByKeyChord(KeyChord{ true, false, true, false, 87, 0 });
VERIFY_ARE_EQUAL(sendInputCmd1.ID(), sendInputCmd1.ID());
}
void SerializationTests::RoundtripLegacyToModernActions()
{
static constexpr std::string_view oldSettingsJson{ R"(
{
"actions": [
{
"name": "foo",
"id": "Test.SendInput",
"command": { "action": "sendInput", "input": "just some input" },
"keys": "ctrl+shift+w"
},
{
"command": "unbound",
"keys": "ctrl+shift+x"
}
]
})" };
// modern style:
// - no "unbound" actions, these are just keybindings that have no id
// - no keys in actions, these are keybindings with an id
static constexpr std::string_view newSettingsJson{ R"(
{
"actions": [
{
"name": "foo",
"command": { "action": "sendInput", "input": "just some input" },
"id": "Test.SendInput"
}
],
"keybindings": [
{
"id": "Test.SendInput",
"keys": "ctrl+shift+w"
},
{
"id": null,
"keys": "ctrl+shift+x"
}
]
})" };
implementation::SettingsLoader loader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
loader.MergeInboxIntoUserSettings();
loader.FinalizeLayering();
VERIFY_IS_TRUE(loader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk");
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto oldResult{ settings->ToJson() };
implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
VERIFY_IS_FALSE(newLoader.FixupUserSettings(), L"Validate that there is no need to write back to disk");
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
const auto newResult{ newSettings->ToJson() };
VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult));
}
void SerializationTests::RoundtripUserActionsSameAsInBoxAreRemoved()
{
static constexpr std::string_view oldSettingsJson{ R"(
{
"actions": [
{
"command": "paste",
"keys": "ctrl+shift+x"
}
]
})" };
// this action is the same as in inbox one,
// so we will delete this action from the user's file but retain the keybinding
static constexpr std::string_view newSettingsJson{ R"(
{
"actions": [
],
"keybindings": [
{
"id": "Terminal.PasteFromClipboard",
"keys": "ctrl+shift+x"
}
]
})" };
implementation::SettingsLoader loader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
loader.MergeInboxIntoUserSettings();
loader.FinalizeLayering();
VERIFY_IS_TRUE(loader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk");
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto oldResult{ settings->ToJson() };
implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
VERIFY_IS_FALSE(newLoader.FixupUserSettings(), L"Validate that there is no need to write back to disk");
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
const auto newResult{ newSettings->ToJson() };
VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult));
}
void SerializationTests::RoundtripActionsSameNameDifferentCommandsAreRetained()
{
static constexpr std::string_view oldSettingsJson{ R"(
{
"actions": [
{
"command": { "action": "sendInput", "input": "just some input" },
"name": "mySendInput"
},
{
"command": { "action": "sendInput", "input": "just some input 2" },
"name": "mySendInput"
}
]
})" };
// There are two different actions with the same name,
// ensure that both are kept but have different IDs generated for them
static constexpr std::string_view newSettingsJson{ R"(
{
"actions": [
{
"name": "mySendInput",
"command": { "action": "sendInput", "input": "just some input" },
"id": "User.sendInput.)" SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH R"("
},
{
"name": "mySendInput",
"command": { "action": "sendInput", "input": "just some input 2" },
"id": "User.sendInput.)" SEND_INPUT2_ARCH_SPECIFIC_ACTION_HASH R"("
}
]
})" };
implementation::SettingsLoader loader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
loader.MergeInboxIntoUserSettings();
loader.FinalizeLayering();
VERIFY_IS_TRUE(loader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk");
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto oldResult{ settings->ToJson() };
implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
VERIFY_IS_FALSE(newLoader.FixupUserSettings(), L"Validate that there is no need to write back to disk");
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
const auto newResult{ newSettings->ToJson() };
VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult));
}
void SerializationTests::MultipleActionsAreCollapsed()
{
static constexpr std::string_view oldSettingsJson{ R"(
{
"actions": [
{
"name": "foo",
"icon": "myCoolIconPath.png",
"command": { "action": "sendInput", "input": "just some input" },
"keys": "ctrl+shift+w"
},
{
"command": { "action": "sendInput", "input": "just some input" },
"keys": "ctrl+shift+x"
}
]
})" };
// modern style:
// - multiple action blocks whose purpose is simply to define more keybindings for the same action
// get collapsed into one action block, with the name and icon path preserved and have multiple keybindings instead
static constexpr std::string_view newSettingsJson{ R"(
{
"actions": [
{
"name": "foo",
"icon": "myCoolIconPath.png",
"command": { "action": "sendInput", "input": "just some input" },
"id": "User.sendInput.)" SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH R"("
}
],
"keybindings": [
{
"keys": "ctrl+shift+w",
"id": "User.sendInput.)" SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH R"("
},
{
"keys": "ctrl+shift+x",
"id": "User.sendInput.)" SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH R"("
}
]
})" };
implementation::SettingsLoader loader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
loader.MergeInboxIntoUserSettings();
loader.FinalizeLayering();
VERIFY_IS_TRUE(loader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk");
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto oldResult{ settings->ToJson() };
implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
VERIFY_IS_FALSE(newLoader.FixupUserSettings(), L"Validate that there is no need to write back to disk");
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
const auto newResult{ newSettings->ToJson() };
VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult));
}
Rewrite media resource handling (relative path icons, web URLs) (#19143) This pull request broadly rewrites how we handle all media resources in the Terminal settings model. ## What is a media resource? A media resource is any JSON property that refers to a file on disk, including: - `icon` on profile - `backgroundImage` on profile (appearance) - `pixelShaderPath` and `pixelShaderImagePath` on profile (appearance) - `icon` on command and the new tab menu entries The last two bullet points were newly discovered during the course of this work. ## Description of Changes In every place the settings model used to store a string for a media path, it now stores an `IMediaResource`. A media resource must be _resolved_ before it's used. When resolved, it can report whether it is `Ok` (found, valid) and what the final normalized path was. This allows the settings model to apply some new behaviors. One of those new behaviors is resolving media paths _relative to the JSON file that referred to them._ This means fragments and user settings can now contain _local_ images, pixel shaders and more and refer to them by filename. Relative path support requires us to track the path from which every media resource "container" was read[^2]. For "big" objects like Profile, we track it directly in the object and for each layer. This means that fragments **updating** a profile pass their relative base path into the mix. For some of the entries such as those in `newTabMenu`, we just wing it (#19191). For everything that is recursively owned by a parent that has a path (say each Command inside an ActionMap), we pass it in from the parent during media resolution. During resolution, we now track _exactly which layer_ an icon, background image, or pixel shader path came from and read the "base path" from only that layer. The base path is not inherited. Another new behavior is in the handling of web and other URLs. Canonical and a few other WSL distributors had to resort to web URLs for icons because we did not support loading them from the package. Julia tried to use `ms-appx://JuliaPackageNameHere/path/to/icon` for the same reason. Neither was intended, and of the two the second _should_ have worked but never could[^1]. For both `http(s?)` URLs and `ms-appx://` URLs which specify a package name, we now strip everything except the filename. As an example... If my fragment specifies `https://example.net/assets/foo.ico`, and my fragment was loaded from `C:\Fragments`, Terminal will look *only* at `C:\Fragments\foo.ico`. This works today for Julia (they put their icon in the fragment folder hoping that one day we would support this.) It will require some work from existing WSL distributors. I'm told that this is similar to how XML schema documents work. Now, icons are special. They support _Emoji_ and _Segoe Icons_. This PR adds an early pass to avoid resolving anything that looks like an emoji. This PR intentionally expands the heuristic definition of an emoji. It used to only cover 1-2 code unit emoji, which prevented the use of any emoji more complicated than "man in business suite levitating." An icon path will now be considered an emoji or symbol icon if it is composed of a single grapheme cluster (as measured by ICU.) This is not perfect, as it errs on the side of allowing too many things... but each of those things is technically a single grapheme cluster and is a perfectly legal FontIcon ;) Profile icons are _even more special_ than icons. They have an additional fallback behavior which we had to preserve. When a profile icon fails validation, or is expressly set to `null`, we fall back to the EXE specified in the command line. Because we do this fallback during resolution, _and the icon may be inherited by any higher profile,_ we can only resolve it against the commandline at the same level as the failed or nulled icon. Therefore, if you specify `icon: null` in your `defaults` profile, it will only ever resolve to `cmd.exe` for any profile that inherits it (unless you change `defaults.commandline`). This change expands support for the magic keywords `desktopWallpaper` and `none` to all media paths (yes, even `pixelShaderPath`... but also, `pixelShaderImagePath`!) It also expands support for _environment variables_ to all of those places. Yes, we had like forty different handlers for different types of string path. They are now uniform. ## Resource Validation Media resources which are not found are "rejected". If a rejected resource lives in _user_ settings, we will generate a warning and display it. In the future, we could detect this in the Settings UI and display a warning inline. ## Surprises I learned that `Windows.Foundation.Uri` parses file paths into `file://` URIs, but does not offer you a way to get the original file path back out. If you pass `C:\hello world`, _`Uri.Path`_ will return `/C:/hello%20world`. I kid you not. As a workaround, we bail out of URL handling if the `:` is too close to the start (indicating an absolute file path). ## Testing I added a narow test hook in the media resource resolver, which is removed completely by link-time code generation. It is a real joy. The test cases are all new and hopefully comprehensive. Closes #19075 Closes #16295 Closes #10359 (except it doesn't support fonts) Supersedes #16949 somewhat (`WT_SETTINGS_DIR`) Refs #18679 Refs #19215 (future work) Refs #19201 (future work) Refs #19191 (future work) [^1]: Handling a `ms-appx` path requires us to _add their package to our dependency graph_ for the entire duration during which the resource will be used. For us, that could be any time (like opening the command palette for the first time!) [^2]: We don't bother tracking where the defaults came from, because we control everything about them.
2025-08-05 15:47:50 -05:00
void SerializationTests::ProfileWithInvalidIcon()
{
static constexpr std::string_view settingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"historySize": 1,
"commandline": "cmd.exe",
"icon": "c:\\this_icon_had_better_not_exist.tiff"
}
]
})" };
implementation::SettingsLoader loader{ settingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
loader.MergeInboxIntoUserSettings();
loader.FinalizeLayering();
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto newResult{ settings->ToJson() };
// A profile that specifies an invalid icon will fall back to the commandline on load, but that should
// not be reflected back in settings.json as null *or* as the commandline. The value should be exactly
// what was written in the settings file.
VERIFY_ARE_EQUAL(R"(c:\this_icon_had_better_not_exist.tiff)", newResult["profiles"]["list"][0]["icon"].asString());
}
Add more settings model unit tests (#20117) ## Summary of the Pull Request Adds tests to `UnitTests_SettingsModel` to improve coverage. Tests include: - `SettingInheritanceFallback`: Settings inherit from user defaults; unset settings fall back to built-in defaults - `ClearSettingRestoresInheritance`: `ClearXxx()` removes the value at the current layer, causing fallback to the parent - `HasSettingAtSpecificLayer`: `HasXxx() `distinguishes explicitly set values from inherited ones - `ModifyProfileSettingAndRoundtrip`: Change a profile setting via setter and `ToJson()` reflects it - `ModifyGlobalSettingAndRoundtrip`: Change global settings via setter and `ToJson()` reflects them - `ModifyColorSchemeAndRoundtrip`: Change a color scheme property and the serialized JSON reflects it - `FixupUserSettingsDetectsChanges`: A clean roundtrip produces idempotent FixupUserSettings() (returns false) - `FixupCommandlinePatching`: 4 sub-cases: CMD/PowerShell short names get patched to full paths, no-op when already clean, custom profiles are untouched This also updates `TestCloneInheritanceTree` to verify that `HasXxx()` and settters that modify the clone don't modify the original. This is being done in preparation for auto-save to help ensure we don't have any regressions. ## Validation Steps Performed ✅ Tests pass ✅ Manually reviewed the new tests, they make sense and do add value (though some are less valuable than others, admittedly) ✅ Sent Copilot on a quest to ensure we're not adding redundant tests. It did catch a few and remove them fwiw.
2026-04-29 10:16:30 -07:00
void SerializationTests::ModifyProfileSettingAndRoundtrip()
{
// Load settings, modify a profile setting via setter, serialize,
// and verify the JSON output reflects the change.
static constexpr std::string_view settingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"historySize": 1000,
"commandline": "cmd.exe"
}
]
})" };
const auto settings{ winrt::make_self<implementation::CascadiaSettings>(settingsJson) };
// Verify initial value
VERIFY_ARE_EQUAL(1000, settings->AllProfiles().GetAt(0).HistorySize());
// Modify the setting
settings->AllProfiles().GetAt(0).HistorySize(5000);
VERIFY_ARE_EQUAL(5000, settings->AllProfiles().GetAt(0).HistorySize());
// Serialize and verify the change is reflected in JSON
const auto result{ settings->ToJson() };
VERIFY_ARE_EQUAL(5000, result["profiles"]["list"][0]["historySize"].asInt());
// Verify other settings are preserved
VERIFY_ARE_EQUAL("cmd.exe", result["profiles"]["list"][0]["commandline"].asString());
// Also verify: modify a setting that wasn't previously set
settings->AllProfiles().GetAt(0).TabTitle(L"NewTitle");
const auto result2{ settings->ToJson() };
VERIFY_ARE_EQUAL("NewTitle", result2["profiles"]["list"][0]["tabTitle"].asString());
}
void SerializationTests::ModifyGlobalSettingAndRoundtrip()
{
// Load settings, modify a global setting, serialize, verify JSON.
static constexpr std::string_view settingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"initialRows": 30,
"alwaysOnTop": false,
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
}
]
})" };
const auto settings{ winrt::make_self<implementation::CascadiaSettings>(settingsJson) };
// Verify initial values
VERIFY_ARE_EQUAL(30, settings->GlobalSettings().InitialRows());
VERIFY_ARE_EQUAL(false, settings->GlobalSettings().AlwaysOnTop());
// Modify global settings
settings->GlobalSettings().InitialRows(50);
settings->GlobalSettings().AlwaysOnTop(true);
// Verify in-memory changes
VERIFY_ARE_EQUAL(50, settings->GlobalSettings().InitialRows());
VERIFY_ARE_EQUAL(true, settings->GlobalSettings().AlwaysOnTop());
// Serialize and verify
const auto result{ settings->ToJson() };
VERIFY_ARE_EQUAL(50, result["initialRows"].asInt());
VERIFY_ARE_EQUAL(true, result["alwaysOnTop"].asBool());
}
void SerializationTests::ModifyColorSchemeAndRoundtrip()
{
// Load settings with a user color scheme, modify it, serialize, verify.
static constexpr std::string_view settingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
}
],
"schemes": [
{
"name": "MyScheme",
"foreground": "#CCCCCC",
"background": "#0C0C0C",
"cursorColor": "#FFFFFF",
"black": "#0C0C0C",
"red": "#C50F1F",
"green": "#13A10E",
"yellow": "#C19C00",
"blue": "#0037DA",
"purple": "#881798",
"cyan": "#3A96DD",
"white": "#CCCCCC",
"brightBlack": "#767676",
"brightRed": "#E74856",
"brightGreen": "#16C60C",
"brightYellow": "#F9F1A5",
"brightBlue": "#3B78FF",
"brightPurple": "#B4009E",
"brightCyan": "#61D6D6",
"brightWhite": "#F2F2F2"
}
]
})" };
const auto settings{ winrt::make_self<implementation::CascadiaSettings>(settingsJson) };
// Find and modify the color scheme
const auto schemes = settings->GlobalSettings().ColorSchemes();
VERIFY_IS_TRUE(schemes.HasKey(L"MyScheme"));
auto myScheme = schemes.Lookup(L"MyScheme");
const auto origForeground = myScheme.Foreground();
myScheme.Foreground(til::color{ 0xAA, 0xBB, 0xCC });
// Serialize and verify the change persists
const auto result{ settings->ToJson() };
const auto& schemesJson = result["schemes"];
bool found = false;
for (const auto& scheme : schemesJson)
{
if (scheme["name"].asString() == "MyScheme")
{
VERIFY_ARE_EQUAL("#AABBCC", scheme["foreground"].asString());
found = true;
break;
}
}
VERIFY_IS_TRUE(found, L"MyScheme should be present in serialized output");
}
void SerializationTests::FixupUserSettingsDetectsChanges()
{
// Verify that FixupUserSettings returns true when settings need
// to be written back (e.g., migration), and false when clean.
static constexpr std::string_view cleanSettingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"commandline": "cmd.exe"
}
]
})" };
// Load, fixup, serialize. Reload and verify fixup returns false.
implementation::SettingsLoader loader1{ cleanSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
loader1.MergeInboxIntoUserSettings();
loader1.FinalizeLayering();
loader1.FixupUserSettings();
const auto settings1 = winrt::make_self<implementation::CascadiaSettings>(std::move(loader1));
const auto result1{ settings1->ToJson() };
// Reload from the serialized output (should be stable)
implementation::SettingsLoader loader2{ toString(result1), implementation::LoadStringResource(IDR_DEFAULTS) };
loader2.MergeInboxIntoUserSettings();
loader2.FinalizeLayering();
const auto fixupNeeded = loader2.FixupUserSettings();
// After a clean roundtrip, no further fixups should be needed
VERIFY_IS_FALSE(fixupNeeded, L"A clean roundtrip should not require further fixups");
}
void SerializationTests::FixupCommandlinePatching()
{
// Verify that FixupUserSettings patches "cmd.exe" to the full path
// for the Command Prompt profile, and "powershell.exe" for the
// Windows PowerShell profile, and returns true to indicate changes.
// Case 1: CMD profile with short commandline should be patched
static constexpr std::string_view cmdSettingsJson{ R"(
{
"defaultProfile": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"profiles": [
{
"name": "Command Prompt",
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"commandline": "cmd.exe"
}
]
})" };
{
implementation::SettingsLoader loader{ cmdSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
loader.MergeInboxIntoUserSettings();
loader.FinalizeLayering();
const auto fixupNeeded = loader.FixupUserSettings();
VERIFY_IS_TRUE(fixupNeeded, L"FixupUserSettings should return true when cmd.exe is patched");
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto cmdProfile = settings->FindProfile(Utils::GuidFromString(L"{0caa0dad-35be-5f56-a8ff-afceeeaa6101}"));
VERIFY_IS_NOT_NULL(cmdProfile);
VERIFY_ARE_EQUAL(L"%SystemRoot%\\System32\\cmd.exe", cmdProfile.Commandline());
}
// Case 2: PowerShell profile with short commandline should be patched
static constexpr std::string_view psSettingsJson{ R"(
{
"defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"profiles": [
{
"name": "Windows PowerShell",
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"commandline": "powershell.exe"
}
]
})" };
{
implementation::SettingsLoader loader{ psSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
loader.MergeInboxIntoUserSettings();
loader.FinalizeLayering();
const auto fixupNeeded = loader.FixupUserSettings();
VERIFY_IS_TRUE(fixupNeeded, L"FixupUserSettings should return true when powershell.exe is patched");
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto psProfile = settings->FindProfile(Utils::GuidFromString(L"{61c54bbd-c2c6-5271-96e7-009a87ff44bf}"));
VERIFY_IS_NOT_NULL(psProfile);
VERIFY_ARE_EQUAL(L"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", psProfile.Commandline());
}
// Case 3: CMD profile with the full path should NOT trigger fixup
static constexpr std::string_view cleanCmdSettingsJson{ R"(
{
"defaultProfile": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"profiles": [
{
"name": "Command Prompt",
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}"
}
]
})" };
{
implementation::SettingsLoader loader{ cleanCmdSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
loader.MergeInboxIntoUserSettings();
loader.FinalizeLayering();
const auto fixupNeeded = loader.FixupUserSettings();
VERIFY_IS_FALSE(fixupNeeded, L"FixupUserSettings should return false when no patching is needed");
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto cmdProfile = settings->FindProfile(Utils::GuidFromString(L"{0caa0dad-35be-5f56-a8ff-afceeeaa6101}"));
VERIFY_IS_NOT_NULL(cmdProfile);
// Should still resolve to the full path via inbox defaults
VERIFY_ARE_EQUAL(L"%SystemRoot%\\System32\\cmd.exe", cmdProfile.Commandline());
}
// Case 4: A non-builtin profile with "cmd.exe" should NOT be patched
static constexpr std::string_view customCmdSettingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "My Custom CMD",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"commandline": "cmd.exe"
}
]
})" };
{
implementation::SettingsLoader loader{ customCmdSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
loader.MergeInboxIntoUserSettings();
loader.FinalizeLayering();
loader.FixupUserSettings();
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto customProfile = settings->FindProfile(Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}"));
VERIFY_IS_NOT_NULL(customProfile);
// Custom profile should keep "cmd.exe" unchanged
VERIFY_ARE_EQUAL(L"cmd.exe", customProfile.Commandline());
}
}
}