Skip to content
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

Make Global and Profile settings inheritable #7923

Merged
31 commits merged into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
62b2a90
Make Global and Profile settings inheritable
carlos-zamora Oct 12, 2020
570afa2
Update IDL. Fix Generate Stub. More inheritance
carlos-zamora Oct 15, 2020
374ad80
address PR Comments; inherit schemes/actions
carlos-zamora Oct 15, 2020
ae97064
fix BI Alignment bug
carlos-zamora Oct 16, 2020
cb59ec1
Merge branch 'master' into dev/cazamor/tsm/inheritance
carlos-zamora Oct 16, 2020
d1ddab5
fix StartingDirectory
carlos-zamora Oct 16, 2020
2866b4e
Introduce Multi-Parent Inheritance (LOGIC ERROR)
carlos-zamora Oct 20, 2020
d74f236
fix logic error (needed see-thru guid)
carlos-zamora Oct 20, 2020
9685108
startingDirectory is just a setting
carlos-zamora Oct 20, 2020
298636b
fix up some tests
carlos-zamora Oct 20, 2020
49b98b3
polish
carlos-zamora Oct 20, 2020
e026f32
first draft of CloneInheritanceGraph
carlos-zamora Oct 21, 2020
dd8a2cb
fix inheritance linker error; optimize inheritance tree
carlos-zamora Oct 21, 2020
07351bb
fix inheritance copy
carlos-zamora Oct 21, 2020
6d34980
fix remaining PR comments
carlos-zamora Oct 21, 2020
6e1849b
fix startingDirectory
carlos-zamora Oct 21, 2020
80e234b
polish
carlos-zamora Oct 21, 2020
7150e52
move guid generation to getter
carlos-zamora Oct 21, 2020
0a806fd
address more of Dustin's comments
carlos-zamora Oct 22, 2020
401efb8
adios comment
carlos-zamora Oct 22, 2020
1292633
fix failing tests
carlos-zamora Oct 22, 2020
0c549f0
Merge branch 'main' into dev/cazamor/tsm/inheritance
carlos-zamora Oct 22, 2020
5c594f3
fix tests
carlos-zamora Oct 22, 2020
01e2c49
address Zadji PR comments
carlos-zamora Oct 23, 2020
e0e3943
make ConnectionType Inheritable
carlos-zamora Oct 23, 2020
b4ec4da
identify connectiontype-ed profiles
carlos-zamora Oct 23, 2020
2716fa0
fix defaultProfile
carlos-zamora Oct 26, 2020
36211ba
add optimization: don't inherit PD if it's empty
carlos-zamora Oct 26, 2020
b55856e
dynamic generator guids should have their own namespace
carlos-zamora Oct 26, 2020
6f55ae5
code format
carlos-zamora Oct 26, 2020
4cf3b1c
remove a few comments
carlos-zamora Oct 27, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spell-check/dictionary/apis.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ ICustom
IDialog
IDirect
IExplorer
IInheritable
IMap
IObject
IStorage
Expand Down
5 changes: 1 addition & 4 deletions src/cascadia/TerminalApp/TerminalSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,7 @@ namespace winrt::TerminalApp::implementation

_Commandline = profile.Commandline();

if (!profile.StartingDirectory().empty())
{
_StartingDirectory = profile.EvaluatedStartingDirectory();
}
_StartingDirectory = profile.EvaluatedStartingDirectory();

// GH#2373: Use the tabTitle as the starting title if it exists, otherwise
// use the profile name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ std::vector<Profile> AzureCloudShellGenerator::GenerateProfiles()
{
auto azureCloudShellProfile{ CreateDefaultProfile(L"Azure Cloud Shell") };
azureCloudShellProfile.Commandline(L"Azure");
azureCloudShellProfile.StartingDirectory(DEFAULT_STARTING_DIRECTORY);
const auto startDir{ winrt::Windows::Foundation::PropertyValue::CreateString(DEFAULT_STARTING_DIRECTORY) };
azureCloudShellProfile.StartingDirectory(startDir);
azureCloudShellProfile.ColorSchemeName(L"Vintage");
azureCloudShellProfile.ConnectionType(AzureConnection::ConnectionType());
profiles.emplace_back(azureCloudShellProfile);
Expand Down
3 changes: 2 additions & 1 deletion src/cascadia/TerminalSettingsModel/CascadiaSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
std::string _userSettingsString;
Json::Value _userSettings;
Json::Value _defaultSettings;
Json::Value _userDefaultProfileSettings{ Json::Value::null };
winrt::com_ptr<Profile> _userDefaultProfileSettings{ nullptr };

void _LayerOrCreateProfile(const Json::Value& profileJson);
winrt::com_ptr<implementation::Profile> _FindMatchingProfile(const Json::Value& profileJson);
std::optional<uint32_t> _FindMatchingProfileIndex(const Json::Value& profileJson);
void _LayerOrCreateColorScheme(const Json::Value& schemeJson);
winrt::com_ptr<implementation::ColorScheme> _FindMatchingColorScheme(const Json::Value& schemeJson);
void _ParseJsonString(std::string_view fileData, const bool isDefaultSettings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,8 @@ winrt::com_ptr<CascadiaSettings> CascadiaSettings::FromJson(const Json::Value& j
// <none>
void CascadiaSettings::LayerJson(const Json::Value& json)
{
// add a new inheritance layer, and apply json values to child
_globals = _globals->CreateChild();
_globals->LayerJson(json);

if (auto schemes{ json[SchemesKey.data()] })
Expand Down Expand Up @@ -627,10 +629,17 @@ void CascadiaSettings::LayerJson(const Json::Value& json)
void CascadiaSettings::_LayerOrCreateProfile(const Json::Value& profileJson)
{
// Layer the json on top of an existing profile, if we have one:
auto pProfile = _FindMatchingProfile(profileJson);
if (pProfile)
auto profileIndex{ _FindMatchingProfileIndex(profileJson) };
if (profileIndex)
{
pProfile->LayerJson(profileJson);
// add a new inheritance layer, and apply json values to child
auto parentProj{ _profiles.GetAt(*profileIndex) };
auto parent{ winrt::get_self<Profile>(parentProj) };
auto childImpl{ parent->CreateChild() };
childImpl->LayerJson(profileJson);

// replace parent in _profiles with child
_profiles.SetAt(*profileIndex, *childImpl);
}
else
{
Expand All @@ -639,13 +648,13 @@ void CascadiaSettings::_LayerOrCreateProfile(const Json::Value& profileJson)
// `source`. Dynamic profiles _must_ be layered on an existing profile.
if (!Profile::IsDynamicProfileObject(profileJson))
{
auto profile = winrt::make_self<Profile>();
auto profile{ winrt::make_self<Profile>() };

// GH#2325: If we have a set of default profile settings, apply them here.
// We _won't_ have these settings yet for defaults, dynamic profiles.
if (_userDefaultProfileSettings)
{
profile->LayerJson(_userDefaultProfileSettings);
profile = _userDefaultProfileSettings->CreateChild();
}

profile->LayerJson(profileJson);
Expand All @@ -667,15 +676,38 @@ void CascadiaSettings::_LayerOrCreateProfile(const Json::Value& profileJson)
// profile exists.
winrt::com_ptr<Profile> CascadiaSettings::_FindMatchingProfile(const Json::Value& profileJson)
{
for (auto profile : _profiles)
auto index{ _FindMatchingProfileIndex(profileJson) };
if (index)
{
auto profileImpl = winrt::get_self<Profile>(profile);
auto profile{ _profiles.GetAt(*index) };
auto profileImpl{ winrt::get_self<Profile>(profile) };
return profileImpl->get_strong();
}
return nullptr;
}

// Method Description:
// - Finds a profile from our list of profiles that matches the given json
// object. Uses Profile::ShouldBeLayered to determine if the Json::Value is a
// match or not. This method should be used to find a profile to layer the
// given settings upon.
// - Returns nullopt if no such match exists.
// Arguments:
// - json: an object which may be a partial serialization of a Profile object.
// Return Value:
// - The index for the matching Profile, iff it exists. Otherwise, nullopt.
std::optional<uint32_t> CascadiaSettings::_FindMatchingProfileIndex(const Json::Value& profileJson)
{
for (uint32_t i = 0; i < _profiles.Size(); ++i)
{
const auto profile{ _profiles.GetAt(i) };
const auto profileImpl = winrt::get_self<Profile>(profile);
if (profileImpl->ShouldBeLayered(profileJson))
{
return profileImpl->get_strong();
return i;
}
}
return nullptr;
return std::nullopt;
}

// Method Description:
Expand Down Expand Up @@ -706,16 +738,26 @@ void CascadiaSettings::_ApplyDefaultsFromUserSettings()
// from user settings file
if (defaultSettings)
{
_userDefaultProfileSettings = defaultSettings;

// Remove the `guid` member from the default settings. That'll
// hyper-explode, so just don't let them do that.
_userDefaultProfileSettings.removeMember({ "guid" });
defaultSettings.removeMember({ "guid" });

_userDefaultProfileSettings = winrt::make_self<Profile>();
_userDefaultProfileSettings->LayerJson(defaultSettings);

for (auto profile : _profiles)
const auto numOfProfiles{ _profiles.Size() };
for (uint32_t profileIndex = 0; profileIndex < numOfProfiles; ++profileIndex)
{
auto profileImpl = winrt::get_self<Profile>(profile);
profileImpl->LayerJson(_userDefaultProfileSettings);
// create a child, so we inherit from the defaults.json layer
auto parentProj{ _profiles.GetAt(profileIndex) };
auto parentImpl{ winrt::get_self<Profile>(parentProj) };
auto childImpl{ parentImpl->CreateChild() };

// Add profile.defaults as the _first_ parent to the child
childImpl->InsertParent(0, _userDefaultProfileSettings);

// replace parent in _profiles with child
_profiles.SetAt(profileIndex, *childImpl);
}
}
}
Expand Down
104 changes: 97 additions & 7 deletions src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,45 @@ static constexpr bool debugFeaturesDefault{ false };
GlobalAppSettings::GlobalAppSettings() :
_keymap{ winrt::make_self<KeyMapping>() },
_keybindingsWarnings{},
_unparsedDefaultProfile{},
_validDefaultProfile{ false },
_defaultProfile{},
_DebugFeaturesEnabled{ debugFeaturesDefault }
{
_commands = winrt::single_threaded_map<winrt::hstring, Model::Command>();
_colorSchemes = winrt::single_threaded_map<winrt::hstring, Model::ColorScheme>();
}

// Method Description:
// - Copies any extraneous data from the parent before completing a CreateChild call
// Arguments:
// - <none>
// Return Value:
// - <none>
void GlobalAppSettings::_FinalizeInheritance()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, to get this right, every node in the GlobalAppSettings inheritance graph has a full copy of the parent's schemes and commands, correct? So for these, we can't really tell what layer a command was actually defined at, right? But there's no other way of doing this, because we can't easily express "Layer 2 undefined {command} from layer 1", right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, to get this right, every node in the GlobalAppSettings inheritance graph has a full copy of the parent's schemes and commands, correct?

Minor thing I messed up. These should be std::move instead of copies. That way we don't hold on to an extra copy of color schemes, commands, etc. that we'll never touch. So instead we'll just have one copy of all of this and pass it down to the leaf.

So for these, we can't really tell what layer a command was actually defined at, right? But there's no other way of doing this, because we can't easily express "Layer 2 undefined {command} from layer 1", right?

Exactly. This goes back to the discussion we were having about doing action IDs sooner than later. With action IDs, we should be able to layer an action onto our ancestors. Additionally, we'll have to design our own data structure solution that...

  • checks the leaf for an action
  • if none are found, checks the parent's
    while also maintaining more complex functionality like "unbind" and "rebind" a command. These two work items are pretty intertwined IMO.

{
// TODO CARLOS: Crash invoking IActionAndArgs->Copy
//_keymap = _parent->_keymap->Copy();

// Globals only ever has 1 parent
FAIL_FAST_IF(_parents.size() > 1);
for (auto parent : _parents)
{
_keymap = parent->_keymap;
std::copy(parent->_keybindingsWarnings.begin(), parent->_keybindingsWarnings.end(), std::back_inserter(_keybindingsWarnings));
for (auto kv : parent->_colorSchemes)
{
const auto schemeImpl{ winrt::get_self<ColorScheme>(kv.Value()) };
_colorSchemes.Insert(kv.Key(), *schemeImpl->Copy());
}

for (auto kv : parent->_commands)
{
const auto commandImpl{ winrt::get_self<Command>(kv.Value()) };
_commands.Insert(kv.Key(), *commandImpl->Copy());
}
}
}

winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
{
auto globals{ winrt::make_self<GlobalAppSettings>() };
Expand Down Expand Up @@ -91,9 +122,11 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
globals->_UseTabSwitcher = _UseTabSwitcher;
globals->_DisableAnimations = _DisableAnimations;

globals->_unparsedDefaultProfile = _unparsedDefaultProfile;
globals->_UnparsedDefaultProfile = _UnparsedDefaultProfile;
globals->_defaultProfile = _defaultProfile;
globals->_keymap = _keymap->Copy();
// TODO CARLOS: Crash invoking IActionAndArgs->Copy
//globals->_keymap = _keymap->Copy();
globals->_keymap = _keymap;
std::copy(_keybindingsWarnings.begin(), _keybindingsWarnings.end(), std::back_inserter(globals->_keybindingsWarnings));

for (auto kv : _colorSchemes)
Expand All @@ -107,6 +140,13 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
const auto commandImpl{ winrt::get_self<Command>(kv.Value()) };
globals->_commands.Insert(kv.Key(), *commandImpl->Copy());
}

// Globals only ever has 1 parent
FAIL_FAST_IF(_parents.size() > 1);
for (auto parent : _parents)
{
globals->InsertParent(parent->Copy());
}
return globals;
}

Expand All @@ -115,23 +155,70 @@ winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, winrt::Microso
return _colorSchemes.GetView();
}

#pragma region DefaultProfile
void GlobalAppSettings::DefaultProfile(const winrt::guid& defaultProfile) noexcept
{
_unparsedDefaultProfile.clear();
_validDefaultProfile = true;
_defaultProfile = defaultProfile;
}

winrt::guid GlobalAppSettings::DefaultProfile() const
{
// If we have an unresolved default profile, we should likely explode.
THROW_HR_IF(E_INVALIDARG, !_unparsedDefaultProfile.empty());
THROW_HR_IF(E_INVALIDARG, !_validDefaultProfile);
return _defaultProfile;
}

bool GlobalAppSettings::HasUnparsedDefaultProfile() const
{
return _UnparsedDefaultProfile.has_value();
}

winrt::hstring GlobalAppSettings::UnparsedDefaultProfile() const
{
return _unparsedDefaultProfile;
const auto val{ _getUnparsedDefaultProfileImpl() };
return val ? *val : hstring{ L"" };
}

void GlobalAppSettings::UnparsedDefaultProfile(const hstring& value)
{
if (_UnparsedDefaultProfile != value)
{
_UnparsedDefaultProfile = value;
_validDefaultProfile = false;
}
}

void GlobalAppSettings::ClearUnparsedDefaultProfile()
{
if (HasUnparsedDefaultProfile())
{
_UnparsedDefaultProfile = std::nullopt;
}
}

std::optional<winrt::hstring> GlobalAppSettings::_getUnparsedDefaultProfileImpl() const
{
/*return user set value*/
if (_UnparsedDefaultProfile)
{
return _UnparsedDefaultProfile;
}

/*user set value was not set*/
/*iterate through parents to find a value*/
for (auto parent : _parents)
{
if (auto val{ parent->_getUnparsedDefaultProfileImpl() })
{
return val;
}
}

/*no value was found*/
return std::nullopt;
}
#pragma endregion

winrt::Microsoft::Terminal::Settings::Model::KeyMapping GlobalAppSettings::KeyMap() const noexcept
{
Expand All @@ -153,7 +240,10 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::FromJson(const Json::Value&

void GlobalAppSettings::LayerJson(const Json::Value& json)
{
JsonUtils::GetValueForKey(json, DefaultProfileKey, _unparsedDefaultProfile);
// _validDefaultProfile keeps track of whether we've verified that DefaultProfile points to something
// CascadiaSettings::_ResolveDefaultProfile performs a validation and updates DefaultProfile() with the
// resolved value, then making it valid.
_validDefaultProfile = !JsonUtils::GetValueForKey(json, DefaultProfileKey, _UnparsedDefaultProfile);

JsonUtils::GetValueForKey(json, AlwaysShowTabsKey, _AlwaysShowTabs);

Expand Down
Loading