Reduce usage of Json::Value throughout Terminal.Settings.Model#11184
Conversation
| 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.
Hmm.. why the change here? I guess we never reload this in practice...
There was a problem hiding this comment.
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.
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.
Yep it refreshes _State.
7412dfc to
1efe485
Compare
DHowett
left a comment
There was a problem hiding this comment.
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.
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.
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.
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.
How do we validate that this invariant is still true?
There was a problem hiding this comment.
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?
carlos-zamora
left a comment
There was a problem hiding this comment.
Ok! I've taken a look at everything except...
- the tests
- Profile: copying the inheritance graph
A few memorable notes:
- Take a look at #7421 and #5276. You might be able to close them out.
userDefaults.jsonstill 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.SettingsLoaderwould 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.
| if (activeProfiles.empty()) | ||
| { | ||
| pScheme->LayerJson(schemeJson); | ||
| throw SettingsException(SettingsLoadErrors::AllProfilesHidden); |
There was a problem hiding this comment.
What if a user only wants their dynamic/fragment profiles? Won't that trigger this exception?
There was a problem hiding this comment.
For sure! But this code is equivalent to the current code.
carlos-zamora
left a comment
There was a problem hiding this comment.
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.
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.
or just always emplace in CopyInheritanceGraph.
There was a problem hiding this comment.
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"); |
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.
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.
This works even if defaults has been deleted (and is therefore a json null), correct?
There was a problem hiding this comment.
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.
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. 😄
carlos-zamora
left a comment
There was a problem hiding this comment.
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.
(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 |
zadjii-msft
left a comment
There was a problem hiding this comment.
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> |
| ParsedSettings inboxSettings; | ||
| ParsedSettings userSettings; |
There was a problem hiding this comment.
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.
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.
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.
can GeneratedProfiles be null (like the first time the file is created, for ex)?
There was a problem hiding this comment.
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.
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
zadjii-msft
left a comment
There was a problem hiding this comment.
I'm only blocking for:
- The
Opacitything - 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.
| OSVERSIONINFOEXW osver{}; | ||
| osver.dwOSVersionInfoSize = sizeof(osver); | ||
| osver.dwBuildNumber = 21359; | ||
| osver.dwBuildNumber = 22000; |
There was a problem hiding this comment.
yea this is probably a good call
| @@ -1,673 +0,0 @@ | |||
| // Copyright (c) Microsoft Corporation. | |||
There was a problem hiding this comment.
these guys didn't really get resurrected anywhere, did they?
There was a problem hiding this comment.
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.
|
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:
CascadiaSettingsintoSettingsLoaderThis allows us to use STL containers for data model instances.
For instance profiles are now added to a hashmap for O(1) lookup.
SettingsLoaderdoesn'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.
ColorSchemes cannot be layered. As such itsLayerJsonAPI was replacedwith
FromJson, allowing us to remove JSON-based color scheme validation.Profiles 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:
ColorSchemetil::colorgettersconstexpr, allow better optimizationsThe result is a reduction of:
CascadiaSettingsclassFurthermore 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.)