-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reduce usage of Json::Value throughout Terminal.Settings.Model #11184
Conversation
@@ -74,7 +74,7 @@ | |||
x:Uid="Globals_DefaultTerminal" | |||
x:Load="false"> | |||
<ComboBox x:Name="DefaultTerminal" | |||
ItemsSource="{x:Bind State.Settings.DefaultTerminals, Mode=OneWay}" | |||
ItemsSource="{x:Bind State.Settings.DefaultTerminals}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm.. why the change here? I guess we never reload this in practice...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I started testing my changes, the SUI was crashing on load and with @carlos-zamora I was able to determine that I made a mistake with my DefaultTerminals()
implementation. After fixing my issue only this change remained. I believe this change to be correct as DefaultTerminals
is unable to post updates (it's a pull-based API and cannot "push" updates).
But I do want to minimize the PR complexity and would thus be entirely happy to revert any change we deem unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is fine? Only concern is if we're still updating the ItemsSource
when we reload the page. But that's more a concern in Launch.cpp::OnNavigatedTo(). Mind double checking that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep it refreshes _State
.
7412dfc
to
1efe485
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
48/55 files reviewed! Would you mind sharing the output from running the LocalTests harness?
static com_ptr<Profile> CloneInheritanceGraph(com_ptr<Profile> oldProfile, com_ptr<Profile> newProfile, std::unordered_map<void*, com_ptr<Profile>>& visited); | ||
static com_ptr<Profile> CopySettings(com_ptr<Profile> source); | ||
static void InsertParentHelper(com_ptr<Profile> child, com_ptr<Profile> parent, std::optional<size_t> index = std::nullopt); | ||
static void CopyInheritanceGraph(std::unordered_map<const Profile*, winrt::com_ptr<Profile>>& visited, const std::vector<winrt::com_ptr<Profile>>& source, std::vector<winrt::com_ptr<Profile>>& target); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reduction of visited
from many to one suggests that we'll have an inheritance issue -- you may need to explain why there are no longer multiple parents or interlinked branches in the tree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a comment and renamed the two functions to make the intent more clear.
But they're still a basic algorithm to clone a directed acyclic graph now.
What I did here is to split up the previous function, which did "clone this node and its sub-graph", into:
- "clone this node and its sub-graph" and
- "clone these child-nodes and their sub-graphs"
The former uses the latter to clone the children of a node, whereas the latter uses the former to clone each child-node in a loop. The recursion thus now hops back and forth between two functions. This simplifies the implementation a lot.
settings->_ApplyDefaultsFromUserSettings(); | ||
settings->LayerJson(settings->_userSettings); | ||
settings->_ValidateSettings(); | ||
"$help" : "https://aka.ms/terminal-documentation", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CURIOUS! What does this do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since I removed the helpful comments from userDefaults.json
, I thought a documentation link might be useful for some. It's the first field in the settings.json
.
VERIFY_ARE_EQUAL(L"NeitherShouldThisOne", settings->_allProfiles.GetAt(4).Name()); | ||
} | ||
|
||
void DeserializationTests::TestExplodingNameOnlyProfiles() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we validate that this invariant is still true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered this test in particular a duplicate of TestLayeringNameOnlyProfiles
which is extremely similar. So I only fixed one of the two tests. Did I maybe miss something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok! I've taken a look at everything except...
- the tests
- Profile: copying the inheritance graph
A few memorable notes:
- Take a look at Generate GUID's when it's requested (Profile) #7421 and Add support for profiles.defaults in defaults.json #5276. You might be able to close them out.
userDefaults.json
still needs those comments. I didn't check if we're still populating the special variables in there (or I've forgotten how you do it). But we have to make sure we don't break that.SettingsLoader
would be extra nice if we moved away from knowing which layer we're in. I envision a world where we can easily load a chain of files. That's basically what you're already doing with fragments. So might as well change the wording a bit to check that off the list.
{ | ||
pScheme->LayerJson(schemeJson); | ||
throw SettingsException(SettingsLoadErrors::AllProfilesHidden); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if a user only wants their dynamic/fragment profiles? Won't that trigger this exception?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For sure! But this code is equivalent to the current code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Reviewed everything except 4 of the tests. But I'll get to those when the PR is out of draft.
{ | ||
clone = CopySettings(); | ||
CopyInheritanceGraph(visited, _parents, clone->_parents); | ||
clone->_FinalizeInheritance(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we also need to add the clone to visited
here? That way if we encounter it again, clone will be populated above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or just always emplace
in CopyInheritanceGraph
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ha! This is some evil C++ abuse! 😄
I'm using the operator[]
a few lines above to get a reference a node in visited
.
If the node doesn't exist the operator[]
will default-initialize it (which is an empty com_ptr
). In this if-condition I'm checking for emptiness and then assign clone
with a clone. Since clone
is a reference into the hashmap it writes it into the hashmap directly.
You gotta write up a PR body! |
azureCloudShellProfile.ConnectionType(AzureConnection::ConnectionType()); | ||
profiles.emplace_back(azureCloudShellProfile); | ||
auto azureCloudShellProfile{ CreateDefaultProfile(AzureGeneratorNamespace, L"Azure Cloud Shell") }; | ||
azureCloudShellProfile->Commandline(L"Azure"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't. It's just ballast.
src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
Outdated
Show resolved
Hide resolved
co_await winrt::resume_background(); | ||
auto v{ co_await task }; | ||
finalVal.emplace(co_await task); | ||
latch.count_down(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for making this use latch ;P
src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
Outdated
Show resolved
Hide resolved
src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
Outdated
Show resolved
Hide resolved
src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
Outdated
Show resolved
Hide resolved
if (fullFile.isMember(JsonKey(SchemesKey))) | ||
// profiles.defaults | ||
{ | ||
settings.baseLayerProfile = Profile::FromJson(defaultsObject); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works even if defaults
has been deleted (and is therefore a json null
), correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Most tests don't specify a defaults object for instance.
@msftbot make sure @zadjii-msft signs off |
Hello @DHowett! Because you've given me some instructions on how to help merge this pull request, I'll be modifying my merge approach. Here's how I understand your requirements for merging this pull request:
If this doesn't seem right to you, you can tell me to cancel these instructions and use the auto-merge policy that has been configured for this repository. Try telling me "forget everything I just told you". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels robust and effective, and I appreciate that I can now almost keep all the logic in my head at once. I've bothered Leonard numerous times during the evenings to tell him that I found "yet another" issue, and... I think I'm out of issues. I can't complain about the code style or the comments any more than I have, and I think this is ready to go.
@carlos-zamora, can you re-review?
I want 3 people to sign off on this as it's a big one. @zadjii-msft is my designated third. 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest also updating the PR body to mention any "breaking" changes we're ok with. That way it's a lot easier to track than having to look through this PR again. Some that come to mind are:
- removal of "globals" warning
- removal of comments on initial settings.json load
All the other comments I have don't have anything to do with the logic of this code. Happy to see this merge soon!
ParsedSettings inboxSettings; | ||
ParsedSettings userSettings; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(bump. Still want some kind of resolution on this one.)
src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
Outdated
Show resolved
Hide resolved
src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
Outdated
Show resolved
Hide resolved
@carlos-zamora Due to the large number of comments and GitHub collapsing them (y u do dis?) let me comment here regarding inboxSettings/parentSettings & userSettings/childSettings:
I'd like to refrain from this for now. I feel like that a name that's as specific as possible helps newcomers to understand the code more easily. It also allows us to develop a mental model that solves exactly just the problem of only solving the problem of "merging builtin with user-given settings", instead of creating a more general solution. In my personal opinion it should be easy for any of us to rename the member variables and functions to something else without involving sweeping changes. After all the inbox/user terminology is concentrated entirely in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
46/67. Finished CascadiaSettingsSerialization
, I suspect most of the remainder is tests. Boy GH does not like reviewing this diff thou, so big
<data name="LegacyGlobalsProperty" xml:space="preserve"> | ||
<value>The "globals" property is deprecated - your settings might need updating. </value> | ||
<comment>{Locked="\"globals\""} </comment> | ||
</data> | ||
<data name="LegacyGlobalsPropertyHrefUrl" xml:space="preserve"> | ||
<value>https://go.microsoft.com/fwlink/?linkid=2128258</value> | ||
<comment>{Locked}This is a FWLink, so it will be localized with the fwlink tool</comment> | ||
</data> | ||
<data name="LegacyGlobalsPropertyHrefLabel" xml:space="preserve"> | ||
<value>For more info, see this web page.</value> | ||
</data> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bump for an answer to this one @DHowett
ParsedSettings inboxSettings; | ||
ParsedSettings userSettings; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
meh, if there's a chain, then there's gonna be more than two anyways, so parent/child doesn't really matter, yea? it's just gonna be a vector of layers
if (!isDefaultSettings) | ||
// FYI: The static_cast ensures we don't move the profile into | ||
// `profilesByGuid`, even though we still need it later for `profiles`. | ||
if (settings.profilesByGuid.emplace(profile->Guid(), static_cast<const winrt::com_ptr<Profile>&>(profile)).second) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If i'm getting this right, say we have two profiles in the settings.json
:
{ "name": "foo" },
{ "name": "foo", "background": "#123456" },
we'll ignore the second one entirely, because it's a duplicate (and later use duplicateProfile
to raise a warning?)
Don't we currently just layer the second one on top of the first?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can restore this old behavior if we'd like. I just found it somewhat inconsistent with the duplicateProfile
warning and assumed that the likelihood of duplicate profiles in a single file is low.
I'll add a note to the PR body.
Should I restore the old behavior?
bool SettingsLoader::DisableDeletedProfiles() | ||
{ | ||
const auto& state = winrt::get_self<ApplicationState>(ApplicationState::SharedInstance()); | ||
auto generatedProfileIds = state->GeneratedProfiles(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can GeneratedProfiles
be null (like the first time the file is created, for ex)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No it returns a STL class actually (which is why I use get_self
here).
_CatchRethrowSerializationExceptionWithLocationInfo(resultPtr->_userSettingsString); | ||
// If inserted is true, then this is a generated profile that doesn't exist in the user's settings. | ||
// While emplace() has already created an appropriate entry in .profilesByGuid, we still need to | ||
// add it to .profiles (which is basically a sorted list of .profilesByGuid's values). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AH okay we CreateChild here because there wasn't an entry in the user's settings for this default profile. The child now acts like it belongs in the user settings, so when we write the file back out, we'll actually serialize that child to settings.json
. Okay, that now makes some sense. Had to read this and the fragments version a few times to figure out why we were making a child here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a comment. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm only blocking for:
- The
Opacity
thing - The resources thing
y'all can feel free to dismiss me if that's sorted out after 5pm Wisconsin time.
I feel sad at the general loss of the json patching - I once had a mind to just do json patching for all settings writing, but the SUI is good enough at this point that it doesn't really matter anymore. I'll miss the comments, but whatever.
osver.dwOSVersionInfoSize = sizeof(osver); | ||
osver.dwBuildNumber = 21359; | ||
osver.dwBuildNumber = 22000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea this is probably a good call
@@ -1,673 +0,0 @@ | |||
// Copyright (c) Microsoft Corporation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these guys didn't really get resurrected anywhere, did they?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TestGenGuidsForProfiles
was moved into ProfileTests.cpp
.
I've removed the others because the SettingsLoader
makes no distinction between the source of builtin profiles: "inbox" JSON data and generated profiles are treated the exact same way. SettingsLoader::GenerateProfiles
calls SettingsLoader::_executeGenerator
which basically only adds generated profiles straight to said inboxSettings.profiles
vector.
Considering the extensive coverage of inbox JSON related tests in SerializationTests.cpp
I've thus decided to not port these tests to the new API and saved myself some work.
Would you like me to restore the tests?
Yeah me too. It might be worthwhile to re-add such support in the future actually! Considering that we had no JSON comment support for our SUI convinced me that it's not a significant loss if we remove the remaining support for it (basically: splicing generated profiles into the settings file). |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RIP literally every pr
Hello @zadjii-msft! Because this pull request has the p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
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:
CascadiaSettings
intoSettingsLoader
This allows us to use STL containers for data model instances.
For instance profiles are now added to a hashmap for O(1) lookup.
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.
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 itsLayerJson
API was replacedwith
FromJson
, allowing us to remove JSON-based color scheme validation.Profile
s used to test their wish to layer usingShouldBeLayered
, whichwas replaced with a GUID-based hashmap lookup on previously parsed profiles.
Further changes were made as improvements upon the previous changes:
ColorScheme
til::color
gettersconstexpr
, allow better optimizationsThe result is a reduction of:
CascadiaSettings
classFurthermore this results in the following breaking changes:
warning will be created during load.
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
Validation Steps Performed
(Except for the settings.json file lacking comments.)