From 019bb766db616c62fc3ada36e88a5bb0c03deeae Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 13 Nov 2024 09:40:59 -0800 Subject: [PATCH 01/27] Add Extensions page to Settings UI --- .../TerminalSettingsEditor/AddProfile.xaml | 2 +- .../TerminalSettingsEditor/Extensions.cpp | 173 ++++++++++ .../TerminalSettingsEditor/Extensions.h | 112 +++++++ .../TerminalSettingsEditor/Extensions.idl | 56 ++++ .../TerminalSettingsEditor/Extensions.xaml | 304 ++++++++++++++++++ .../TerminalSettingsEditor/MainPage.cpp | 40 +++ .../TerminalSettingsEditor/MainPage.h | 2 + .../TerminalSettingsEditor/MainPage.xaml | 9 + ...Microsoft.Terminal.Settings.Editor.vcxproj | 15 + .../Resources/en-US/Resources.resw | 32 ++ .../SettingContainerStyle.xaml | 4 +- .../CascadiaSettings.cpp | 48 +++ .../TerminalSettingsModel/CascadiaSettings.h | 69 +++- .../CascadiaSettings.idl | 22 ++ .../CascadiaSettingsSerialization.cpp | 34 +- 15 files changed, 912 insertions(+), 10 deletions(-) create mode 100644 src/cascadia/TerminalSettingsEditor/Extensions.cpp create mode 100644 src/cascadia/TerminalSettingsEditor/Extensions.h create mode 100644 src/cascadia/TerminalSettingsEditor/Extensions.idl create mode 100644 src/cascadia/TerminalSettingsEditor/Extensions.xaml diff --git a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml b/src/cascadia/TerminalSettingsEditor/AddProfile.xaml index b278f71ae65..3ea92ad0f69 100644 --- a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml +++ b/src/cascadia/TerminalSettingsEditor/AddProfile.xaml @@ -59,7 +59,7 @@ + IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(EvaluatedIcon), Mode=OneTime}" /> diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.cpp b/src/cascadia/TerminalSettingsEditor/Extensions.cpp new file mode 100644 index 00000000000..726e895d2b9 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Extensions.cpp @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "Extensions.h" +#include "Extensions.g.cpp" +#include "ExtensionsViewModel.g.cpp" +#include "FragmentProfileViewModel.g.cpp" + +#include +#include "..\WinRTUtils\inc\Utils.h" + +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Controls; +using namespace winrt::Windows::UI::Xaml::Navigation; + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + Extensions::Extensions() + { + InitializeComponent(); + } + + void Extensions::OnNavigatedTo(const NavigationEventArgs& e) + { + _ViewModel = e.Parameter().as(); + } + + void Extensions::ExtensionLoaded(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& /*args*/) + { + const auto& toggleSwitch = sender.as(); + const auto& extensionSource = toggleSwitch.Tag().as(); + toggleSwitch.IsOn(_ViewModel.GetExtensionState(extensionSource)); + } + + void Extensions::ExtensionToggled(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& /*args*/) + { + const auto& toggleSwitch = sender.as(); + const auto& extensionSource = toggleSwitch.Tag().as(); + _ViewModel.SetExtensionState(extensionSource, toggleSwitch.IsOn()); + } + + ExtensionsViewModel::ExtensionsViewModel(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM) : + _settings{ settings }, + _colorSchemesPageVM{ colorSchemesPageVM } + { + std::vector fragmentExtensions; + fragmentExtensions.reserve(settings.FragmentExtensions().Size()); + + std::vector profilesModified; + std::vector profilesAdded; + std::vector colorSchemesAdded; + for (const auto&& fragExt : settings.FragmentExtensions()) + { + fragmentExtensions.push_back(fragExt); + + for (const auto&& entry : fragExt.ModifiedProfilesView()) + { + // Ensure entry successfully modifies a profile before creating and registering the object + if (const auto& deducedProfile = _settings.FindProfile(entry.ProfileGuid())) + { + auto vm = winrt::make_self(entry, fragExt, deducedProfile); + vm->NavigateToProfileRequested({ this, &ExtensionsViewModel::_NavigateToProfileHandler }); + profilesModified.push_back(*vm); + } + } + + for (const auto&& entry : fragExt.NewProfilesView()) + { + // Ensure entry successfully points to a profile before creating and registering the object. + // The profile may have been removed by the user. + if (const auto& deducedProfile = _settings.FindProfile(entry.ProfileGuid())) + { + auto vm = winrt::make_self(entry, fragExt, deducedProfile); + vm->NavigateToProfileRequested({ this, &ExtensionsViewModel::_NavigateToProfileHandler }); + profilesAdded.push_back(*vm); + } + // TODO CARLOS: if this fails, we should probably still display it, but just say it was removed + // possibly introduce a way to re-add it too? + } + + for (const auto&& entry : fragExt.ColorSchemesView()) + { + for (const auto& schemeVM : _colorSchemesPageVM.AllColorSchemes()) + { + if (schemeVM.Name() == entry.ColorSchemeName()) + { + auto vm = winrt::make_self(entry, fragExt, schemeVM); + vm->NavigateToColorSchemeRequested({ this, &ExtensionsViewModel::_NavigateToColorSchemeHandler }); + colorSchemesAdded.push_back(*vm); + } + } + // TODO CARLOS: if this fails, we should probably still display it, but just say it was removed + // possibly introduce a way to re-add it too? + } + } + + _fragmentExtensions = single_threaded_observable_vector(std::move(fragmentExtensions)); + _profilesModified = single_threaded_observable_vector(std::move(profilesModified)); + _profilesAdded = single_threaded_observable_vector(std::move(profilesAdded)); + _colorSchemesAdded = single_threaded_observable_vector(std::move(colorSchemesAdded)); + } + + // Returns true if the extension is enabled, false otherwise + bool ExtensionsViewModel::GetExtensionState(hstring extensionSource) const + { + if (const auto& disabledExtensions = _DisabledProfileSources()) + { + uint32_t ignored; + return !disabledExtensions.IndexOf(extensionSource, ignored); + } + // "disabledProfileSources" not defined --> all extensions are enabled + return true; + } + + // Enable/Disable an extension + void ExtensionsViewModel::SetExtensionState(hstring extensionSource, bool enableExt) + { + // get the current status of the extension + uint32_t idx; + bool currentlyEnabled = true; + const auto& disabledExtensions = _DisabledProfileSources(); + if (disabledExtensions) + { + currentlyEnabled = !disabledExtensions.IndexOf(extensionSource, idx); + } + + // current status mismatches the desired status, + // update the list of disabled extensions + if (currentlyEnabled != enableExt) + { + // If we're disabling an extension and we don't have "disabledProfileSources" defined, + // create it in the model directly + if (!disabledExtensions && !enableExt) + { + std::vector disabledProfileSources{ extensionSource }; + _settings.GlobalSettings().DisabledProfileSources(single_threaded_vector(std::move(disabledProfileSources))); + return; + } + + // Update the list of disabled extensions + if (enableExt) + { + disabledExtensions.RemoveAt(idx); + } + else + { + disabledExtensions.Append(extensionSource); + } + } + } + + void FragmentProfileViewModel::AttemptNavigateToProfile() + { + NavigateToProfileRequested.raise(*this, _deducedProfile.Guid()); + } + + void ExtensionsViewModel::_NavigateToProfileHandler(const IInspectable& /*sender*/, const guid profileGuid) + { + NavigateToProfileRequested.raise(*this, profileGuid); + } + + void FragmentColorSchemeViewModel::AttemptNavigateToColorScheme() + { + NavigateToColorSchemeRequested.raise(*this, _deducedSchemeVM); + } + + void ExtensionsViewModel::_NavigateToColorSchemeHandler(const IInspectable& /*sender*/, const Editor::ColorSchemeViewModel& schemeVM) + { + _colorSchemesPageVM.CurrentScheme(schemeVM); + NavigateToColorSchemeRequested.raise(*this, nullptr); + } +} diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.h b/src/cascadia/TerminalSettingsEditor/Extensions.h new file mode 100644 index 00000000000..077671a32bb --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Extensions.h @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "Extensions.g.h" +#include "ExtensionsViewModel.g.h" +#include "FragmentProfileViewModel.g.h" +#include "FragmentColorSchemeViewModel.g.h" +#include "ViewModelHelpers.h" +#include "Utils.h" + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + struct Extensions : public HasScrollViewer, ExtensionsT + { + public: + Extensions(); + + void OnNavigatedTo(const Windows::UI::Xaml::Navigation::NavigationEventArgs& e); + void ExtensionLoaded(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); + void ExtensionToggled(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); + + WINRT_PROPERTY(Editor::ExtensionsViewModel, ViewModel, nullptr); + }; + + struct ExtensionsViewModel : ExtensionsViewModelT, ViewModelHelper + { + public: + ExtensionsViewModel(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM); + + // Properties + bool NoActiveExtensions() const noexcept { return _fragmentExtensions.Size() == 0; } + bool NoProfilesModified() const noexcept { return _profilesModified.Size() == 0; } + bool NoProfilesAdded() const noexcept { return _profilesAdded.Size() == 0; } + bool NoSchemesAdded() const noexcept { return _colorSchemesAdded.Size() == 0; } + + // Views + Windows::Foundation::Collections::IVectorView FragmentExtensions() const noexcept { return _fragmentExtensions.GetView(); } + Windows::Foundation::Collections::IVectorView ProfilesModified() const noexcept { return _profilesModified.GetView(); } + Windows::Foundation::Collections::IVectorView ProfilesAdded() const noexcept { return _profilesAdded.GetView(); } + Windows::Foundation::Collections::IVectorView ColorSchemesAdded() const noexcept { return _colorSchemesAdded.GetView(); } + + // Methods + bool GetExtensionState(hstring extensionSource) const; + void SetExtensionState(hstring extensionSource, bool enableExt); + + til::typed_event NavigateToProfileRequested; + til::typed_event NavigateToColorSchemeRequested; + + private: + Model::CascadiaSettings _settings; + Editor::ColorSchemesPageViewModel _colorSchemesPageVM; + Windows::Foundation::Collections::IVector _fragmentExtensions; + Windows::Foundation::Collections::IVector _profilesModified; + Windows::Foundation::Collections::IVector _profilesAdded; + Windows::Foundation::Collections::IVector _colorSchemesAdded; + + Windows::Foundation::Collections::IVector _DisabledProfileSources() const noexcept { return _settings.GlobalSettings().DisabledProfileSources(); } + void _NavigateToProfileHandler(const IInspectable& sender, const guid profileGuid); + void _NavigateToColorSchemeHandler(const IInspectable& sender, const Editor::ColorSchemeViewModel& schemeVM); + }; + + struct FragmentProfileViewModel : FragmentProfileViewModelT, ViewModelHelper + { + public: + FragmentProfileViewModel(const Model::FragmentProfileEntry& entry, const Model::FragmentSettings& fragment, const Model::Profile& deducedProfile) : + _entry{ entry }, + _fragment{ fragment }, + _deducedProfile{ deducedProfile } {} + + Model::Profile Profile() const { return _deducedProfile; }; + hstring SourceName() const { return _fragment.Source(); } + hstring Json() const { return _entry.Json(); } + + void AttemptNavigateToProfile(); + + til::typed_event NavigateToProfileRequested; + + private: + Model::FragmentProfileEntry _entry; + Model::FragmentSettings _fragment; + Model::Profile _deducedProfile; + }; + + struct FragmentColorSchemeViewModel : FragmentColorSchemeViewModelT, ViewModelHelper + { + public: + FragmentColorSchemeViewModel(const Model::FragmentColorSchemeEntry& entry, const Model::FragmentSettings& fragment, const Editor::ColorSchemeViewModel& deducedSchemeVM) : + _entry{ entry }, + _fragment{ fragment }, + _deducedSchemeVM{ deducedSchemeVM } {} + + Editor::ColorSchemeViewModel ColorSchemeVM() const { return _deducedSchemeVM; }; + hstring SourceName() const { return _fragment.Source(); } + hstring Json() const { return _entry.Json(); } + + void AttemptNavigateToColorScheme(); + + til::typed_event NavigateToColorSchemeRequested; + + private: + Model::FragmentColorSchemeEntry _entry; + Model::FragmentSettings _fragment; + Editor::ColorSchemeViewModel _deducedSchemeVM; + }; +}; + +namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation +{ + BASIC_FACTORY(Extensions); +} diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.idl b/src/cascadia/TerminalSettingsEditor/Extensions.idl new file mode 100644 index 00000000000..9cf8e90c327 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Extensions.idl @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "ColorSchemeViewModel.idl"; + +namespace Microsoft.Terminal.Settings.Editor +{ + [default_interface] runtimeclass Extensions : Windows.UI.Xaml.Controls.Page + { + Extensions(); + ExtensionsViewModel ViewModel { get; }; + } + + [default_interface] runtimeclass ExtensionsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + // Properties + Boolean NoActiveExtensions { get; }; + Boolean NoProfilesModified { get; }; + Boolean NoProfilesAdded { get; }; + Boolean NoSchemesAdded { get; }; + + // Views + Windows.Foundation.Collections.IVectorView FragmentExtensions { get; }; + Windows.Foundation.Collections.IVectorView ProfilesModified { get; }; + Windows.Foundation.Collections.IVectorView ProfilesAdded { get; }; + Windows.Foundation.Collections.IVectorView ColorSchemesAdded { get; }; + + // Methods + Boolean GetExtensionState(String extensionSource); + void SetExtensionState(String extensionSource, Boolean enableExt); + + event Windows.Foundation.TypedEventHandler NavigateToProfileRequested; + } + + [default_interface] runtimeclass FragmentProfileViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + Microsoft.Terminal.Settings.Model.Profile Profile { get; }; + String SourceName { get; }; + String Json { get; }; + + void AttemptNavigateToProfile(); + + event Windows.Foundation.TypedEventHandler NavigateToProfileRequested; + } + + [default_interface] runtimeclass FragmentColorSchemeViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + ColorSchemeViewModel ColorSchemeVM { get; }; + String SourceName { get; }; + String Json { get; }; + + void AttemptNavigateToColorScheme(); + + event Windows.Foundation.TypedEventHandler NavigateToColorSchemeRequested; + } +} diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.xaml b/src/cascadia/TerminalSettingsEditor/Extensions.xaml new file mode 100644 index 00000000000..f65f31943be --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Extensions.xaml @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 5daf396b72e..08448e84c48 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -9,6 +9,7 @@ #include "Compatibility.h" #include "Rendering.h" #include "RenderingViewModel.h" +#include "Extensions.h" #include "Actions.h" #include "ProfileViewModel.h" #include "GlobalAppearance.h" @@ -44,6 +45,7 @@ static const std::wstring_view renderingTag{ L"Rendering_Nav" }; static const std::wstring_view compatibilityTag{ L"Compatibility_Nav" }; static const std::wstring_view actionsTag{ L"Actions_Nav" }; static const std::wstring_view newTabMenuTag{ L"NewTabMenu_Nav" }; +static const std::wstring_view extensionsTag{ L"Extensions_Nav" }; static const std::wstring_view globalProfileTag{ L"GlobalProfile_Nav" }; static const std::wstring_view addProfileTag{ L"AddProfile" }; static const std::wstring_view colorSchemesTag{ L"ColorSchemes_Nav" }; @@ -445,6 +447,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _breadcrumbs.Append(crumb); } } + else if (clickedItemTag == extensionsTag) + { + auto vm = winrt::make_self(_settingsClone, _colorSchemesPageVM); + vm->NavigateToProfileRequested({ this, &MainPage::_NavigateToProfileHandler }); + vm->NavigateToColorSchemeRequested({ this, &MainPage::_NavigateToColorSchemeHandler }); + contentFrame().Navigate(xaml_typename(), *vm); + const auto crumb = winrt::make(box_value(clickedItemTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None); + _breadcrumbs.Append(crumb); + } else if (clickedItemTag == globalProfileTag) { auto profileVM{ _viewModelForProfile(_settingsClone.ProfileDefaults(), _settingsClone, Dispatcher()) }; @@ -818,6 +829,35 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return _breadcrumbs; } + void MainPage::_NavigateToProfileHandler(const IInspectable& /*sender*/, winrt::guid profileGuid) + { + for (auto&& menuItem : _menuItemSource) + { + if (const auto& navViewItem{ menuItem.try_as() }) + { + if (const auto& tag{ navViewItem.Tag() }) + { + if (const auto& profileTag{ tag.try_as() }) + { + if (profileTag->OriginalProfileGuid() == profileGuid) + { + SettingsNav().SelectedItem(menuItem); + _Navigate(*profileTag, BreadcrumbSubPage::None); + return; + } + } + } + } + } + // Silently fail if the profile wasn't found + } + + void MainPage::_NavigateToColorSchemeHandler(const IInspectable& /*sender*/, const IInspectable& /*args*/) + { + SettingsNav().SelectedItem(ColorSchemesNavItem()); + _Navigate(hstring{ colorSchemesTag }, BreadcrumbSubPage::ColorSchemes_Edit); + } + winrt::Windows::UI::Xaml::Media::Brush MainPage::BackgroundBrush() { return SettingsNav().Background(); diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.h b/src/cascadia/TerminalSettingsEditor/MainPage.h index 81724effb11..1c981b1dad2 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.h +++ b/src/cascadia/TerminalSettingsEditor/MainPage.h @@ -70,6 +70,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void _Navigate(hstring clickedItemTag, BreadcrumbSubPage subPage); void _Navigate(const Editor::ProfileViewModel& profile, BreadcrumbSubPage subPage); void _Navigate(const Editor::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage); + void _NavigateToProfileHandler(const IInspectable& sender, winrt::guid profileGuid); + void _NavigateToColorSchemeHandler(const IInspectable& sender, const IInspectable& args); void _UpdateBackgroundForMica(); void _MoveXamlParsedNavItemsIntoItemSource(); diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.xaml b/src/cascadia/TerminalSettingsEditor/MainPage.xaml index 3a0773062aa..cf30439ab9d 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.xaml +++ b/src/cascadia/TerminalSettingsEditor/MainPage.xaml @@ -121,6 +121,7 @@ @@ -155,6 +156,14 @@ + + + + + + + NewTabMenuViewModel.idl Code + + Extensions.xaml + Code + Profiles_Base.xaml Code @@ -199,6 +203,9 @@ Designer + + Designer + Designer @@ -309,6 +316,10 @@ NewTabMenuViewModel.idl Code + + Extensions.xaml + Code + Profiles_Base.xaml Code @@ -408,6 +419,10 @@ + + Extensions.xaml + Code + Profiles_Base.xaml Code diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 5d98ce728ef..ba80d38ed93 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -684,6 +684,10 @@ Actions Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Extensions + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + Background opacity Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,4 +2344,32 @@ This option is managed by enterprise policy and cannot be changed here. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Active Extensions + + + Modified Profiles + + + Added Profiles + + + Added Color Schemes + + + None + Text displayed when no extensions are available. Shown in place of a list of entries. + + + None + Text displayed when no profiles were modified. Shown in place of a list of entries. + + + None + Text displayed when no color schemes were added. Shown in place of a list of entries. + + + None + Text displayed when no profiles were added. Shown in place of a list of entries. + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml b/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml index c4d3c7a5809..73dcec7f81e 100644 --- a/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml +++ b/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml @@ -302,8 +302,8 @@ - + @@ -233,8 +224,10 @@ @@ -254,52 +247,94 @@ - - - + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + - - + - - + - - diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 08448e84c48..05e3c00a0ce 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -113,6 +113,31 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } }); + auto extensionsVMImpl = winrt::make_self(_settingsClone, _colorSchemesPageVM); + extensionsVMImpl->NavigateToProfileRequested({ this, &MainPage::_NavigateToProfileHandler }); + extensionsVMImpl->NavigateToColorSchemeRequested({ this, &MainPage::_NavigateToColorSchemeHandler }); + _extensionsVM = *extensionsVMImpl; + _extensionsViewModelChangedRevoker = _extensionsVM.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) { + const auto settingName{ args.PropertyName() }; + if (settingName == L"CurrentExtension") + { + if (const auto& currentExtension = _extensionsVM.CurrentExtension()) + { + const auto crumb = winrt::make(box_value(currentExtension), currentExtension.Fragment().Source(), BreadcrumbSubPage::Extensions_Extension); + _breadcrumbs.Append(crumb); + SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0); + } + else + { + // If we don't have a current folder, we're at the root of the Extensions page + _breadcrumbs.Clear(); + const auto crumb = winrt::make(box_value(extensionsTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None); + _breadcrumbs.Append(crumb); + } + contentFrame().Navigate(xaml_typename(), _extensionsVM); + } + }); + // Make sure to initialize the profiles _after_ we have initialized the color schemes page VM, because we pass // that VM into the appearance VMs within the profiles _InitializeProfilesList(); @@ -163,6 +188,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // Update the Nav State with the new version of the settings _colorSchemesPageVM.UpdateSettings(_settingsClone); _newTabMenuPageVM.UpdateSettings(_settingsClone); + _extensionsVM.UpdateSettings(_settingsClone, _colorSchemesPageVM); // We'll update the profile in the _profilesNavState whenever we actually navigate to one @@ -199,6 +225,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return; } } + else if (const auto& breadcrumbExtensionTag{ crumb->Tag().try_as() }) + { + if (stringTag == extensionsTag) + { + // navigate to the Extensions page, + // _Navigate() will handle trying to find the right subpage + SettingsNav().SelectedItem(item); + _Navigate(breadcrumbExtensionTag, BreadcrumbSubPage::Extensions_Extension); + return; + } + } } else if (const auto& profileTag{ tag.try_as() }) { @@ -449,12 +486,18 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } else if (clickedItemTag == extensionsTag) { - auto vm = winrt::make_self(_settingsClone, _colorSchemesPageVM); - vm->NavigateToProfileRequested({ this, &MainPage::_NavigateToProfileHandler }); - vm->NavigateToColorSchemeRequested({ this, &MainPage::_NavigateToColorSchemeHandler }); - contentFrame().Navigate(xaml_typename(), *vm); - const auto crumb = winrt::make(box_value(clickedItemTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None); - _breadcrumbs.Append(crumb); + if (_extensionsVM.CurrentExtension()) + { + // Setting CurrentExtension triggers the PropertyChanged event, + // which will navigate to the correct page and update the breadcrumbs appropriately + _extensionsVM.CurrentExtension(nullptr); + } + else + { + contentFrame().Navigate(xaml_typename(), _extensionsVM); + const auto crumb = winrt::make(box_value(clickedItemTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None); + _breadcrumbs.Append(crumb); + } } else if (clickedItemTag == globalProfileTag) { @@ -586,6 +629,40 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } + void MainPage::_Navigate(const Editor::FragmentExtensionViewModel& extVM, BreadcrumbSubPage subPage) + { + _PreNavigateHelper(); + + contentFrame().Navigate(xaml_typename(), _extensionsVM); + const auto crumb = winrt::make(box_value(extensionsTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None); + _breadcrumbs.Append(crumb); + + if (subPage == BreadcrumbSubPage::None) + { + _extensionsVM.CurrentExtension(nullptr); + } + else if (subPage == BreadcrumbSubPage::Extensions_Extension && extVM) + { + bool found{ false }; + for (const auto& ext : _extensionsVM.FragmentExtensions()) + { + if (ext.Fragment().Source() == extVM.Fragment().Source()) + { + // Take advantage of the PropertyChanged event to navigate + // to the correct extension and build the breadcrumbs as we go + _extensionsVM.CurrentExtension(ext); + found = true; + break; + } + } + if (!found) + { + // Couldn't find a match, just go back to the root + _extensionsVM.CurrentExtension(nullptr); + } + } + } + void MainPage::OpenJsonTapped(const IInspectable& /*sender*/, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& /*args*/) { const auto window = CoreWindow::GetForCurrentThread(); @@ -632,6 +709,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _Navigate(*ntmEntryViewModel, subPage); } + else if (const auto extensionViewModel = tag.try_as()) + { + _Navigate(*extensionViewModel, subPage); + } else { _Navigate(tag.as(), subPage); diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.h b/src/cascadia/TerminalSettingsEditor/MainPage.h index 1c981b1dad2..9b1aaee2881 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.h +++ b/src/cascadia/TerminalSettingsEditor/MainPage.h @@ -70,6 +70,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void _Navigate(hstring clickedItemTag, BreadcrumbSubPage subPage); void _Navigate(const Editor::ProfileViewModel& profile, BreadcrumbSubPage subPage); void _Navigate(const Editor::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage); + void _Navigate(const Editor::FragmentExtensionViewModel& extVM, BreadcrumbSubPage subPage); void _NavigateToProfileHandler(const IInspectable& sender, winrt::guid profileGuid); void _NavigateToColorSchemeHandler(const IInspectable& sender, const IInspectable& args); @@ -78,10 +79,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation winrt::Microsoft::Terminal::Settings::Editor::ColorSchemesPageViewModel _colorSchemesPageVM{ nullptr }; winrt::Microsoft::Terminal::Settings::Editor::NewTabMenuViewModel _newTabMenuPageVM{ nullptr }; + winrt::Microsoft::Terminal::Settings::Editor::ExtensionsViewModel _extensionsVM{ nullptr }; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _profileViewModelChangedRevoker; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _colorSchemesPageViewModelChangedRevoker; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ntmViewModelChangedRevoker; + Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _extensionsViewModelChangedRevoker; }; } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.idl b/src/cascadia/TerminalSettingsEditor/MainPage.idl index 0f5a6e49b8f..06db5d9b74e 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.idl +++ b/src/cascadia/TerminalSettingsEditor/MainPage.idl @@ -20,7 +20,8 @@ namespace Microsoft.Terminal.Settings.Editor Profile_Terminal, Profile_Advanced, ColorSchemes_Edit, - NewTabMenu_Folder + NewTabMenu_Folder, + Extensions_Extension }; runtimeclass Breadcrumb : Windows.Foundation.IStringable diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.xaml b/src/cascadia/TerminalSettingsEditor/MainPage.xaml index 246c7732593..b22384af4ef 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.xaml +++ b/src/cascadia/TerminalSettingsEditor/MainPage.xaml @@ -120,8 +120,8 @@ - diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 1e125f670c8..44828131c45 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -2372,7 +2372,35 @@ None Text displayed when no profiles were added. Shown in place of a list of entries. - - Learn more about fragment extensions. + + Learn more about fragment extensions - + + Navigate to profile + + + Navigate to profile + + + Navigate to color scheme + + + Navigate to color scheme + + + Current User + Label for the installation scope of an extension. + + + All Users + Label for the installation scope of an extension + + + Scope + Header for the installation scope of the extension + + + JSON + {Locked="JSON"} + + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml b/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml index 73dcec7f81e..b5045dd43a2 100644 --- a/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml +++ b/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml @@ -228,6 +228,45 @@ + + + show all enabled extension components + // Otherwise, only show the ones for the selected extension + if (const auto extSrc = ext.Fragment().Source(); (currentExtensionSource.empty() && GetExtensionState(extSrc)) || extSrc == currentExtensionSource) { for (const auto& profile : ext.ProfilesModified()) { @@ -108,7 +99,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - _NotifyChanges(L"IsExtensionView", L"CurrentExtensionSource", L"CurrentExtensionJson"); + _NotifyChanges(L"IsExtensionView", L"CurrentExtensionFragments"); } }); } @@ -117,6 +108,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _settings = settings; _colorSchemesPageVM = colorSchemesPageVM; + _extensionSources.clear(); + _CurrentExtensionSource.clear(); std::vector fragmentExtensions; fragmentExtensions.reserve(settings.FragmentExtensions().Size()); @@ -127,6 +120,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation std::vector colorSchemesAddedTotal; for (const auto&& fragExt : settings.FragmentExtensions()) { + const auto extensionEnabled = GetExtensionState(fragExt.Source()); + // these vectors track everything the current extension attempted to bring in std::vector currentProfilesModified; std::vector currentProfilesAdded; @@ -138,10 +133,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation if (const auto& deducedProfile = _settings.FindProfile(entry.ProfileGuid())) { auto vm = winrt::make(entry, fragExt, deducedProfile); - profilesModifiedTotal.push_back(vm); currentProfilesModified.push_back(vm); + if (extensionEnabled) + { + profilesModifiedTotal.push_back(vm); + } } - // TODO CARLOS: if this fails, we should probably still add it to currentProfilesModified } for (const auto&& entry : fragExt.NewProfilesView()) @@ -151,11 +148,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation if (const auto& deducedProfile = _settings.FindProfile(entry.ProfileGuid())) { auto vm = winrt::make(entry, fragExt, deducedProfile); - profilesAddedTotal.push_back(vm); currentProfilesAdded.push_back(vm); + if (extensionEnabled) + { + profilesAddedTotal.push_back(vm); + } } - // TODO CARLOS: if this fails, we should probably still display it, but just say it was removed - // possibly introduce a way to re-add it too? } for (const auto&& entry : fragExt.ColorSchemesView()) @@ -165,14 +163,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation if (schemeVM.Name() == entry.ColorSchemeName()) { auto vm = winrt::make(entry, fragExt, schemeVM); - colorSchemesAddedTotal.push_back(vm); currentColorSchemesAdded.push_back(vm); + if (extensionEnabled) + { + colorSchemesAddedTotal.push_back(vm); + } } } - // TODO CARLOS: if this fails, we should probably still display it, but just say it was removed - // possibly introduce a way to re-add it too? } + _extensionSources.insert(fragExt.Source()); fragmentExtensions.push_back(winrt::make(fragExt, currentProfilesModified, currentProfilesAdded, currentColorSchemesAdded)); } @@ -182,15 +182,45 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _colorSchemesAddedView = single_threaded_observable_vector(std::move(colorSchemesAddedTotal)); } + IVector ExtensionsViewModel::CurrentExtensionFragments() const noexcept + { + std::vector fragmentExtensionVMs; + for (auto&& extVM : _fragmentExtensions) + { + if (_CurrentExtensionSource.empty() || extVM.Fragment().Source() == _CurrentExtensionSource) + { + fragmentExtensionVMs.push_back(extVM); + } + } + return winrt::single_threaded_vector(std::move(fragmentExtensionVMs)); + } + hstring ExtensionsViewModel::CurrentExtensionScope() const noexcept { - if (_CurrentExtension) + if (!_CurrentExtensionSource.empty()) { - return _CurrentExtension.Fragment().Scope() == Model::FragmentScope::User ? RS_(L"Extensions_ScopeUser") : RS_(L"Extensions_ScopeSystem"); + for (auto&& extVM : _fragmentExtensions) + { + const auto& fragExt = extVM.Fragment(); + if (fragExt.Source() == _CurrentExtensionSource) + { + return fragExt.Scope() == Model::FragmentScope::User ? RS_(L"Extensions_ScopeUser") : RS_(L"Extensions_ScopeSystem"); + } + } } return hstring{}; } + IObservableVector ExtensionsViewModel::ExtensionPackages() const noexcept + { + std::vector extensionPackages; + for (auto&& extSrc : _extensionSources) + { + extensionPackages.push_back(winrt::make(extSrc, GetExtensionState(extSrc))); + } + return winrt::single_threaded_observable_vector(std::move(extensionPackages)); + } + // Returns true if the extension is enabled, false otherwise bool ExtensionsViewModel::GetExtensionState(hstring extensionSource) const { @@ -250,4 +280,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _colorSchemesPageVM.CurrentScheme(schemeVM); NavigateToColorSchemeRequested.raise(*this, nullptr); } + + hstring ExtensionPackageViewModel::AccessibleName() const noexcept + { + if (_enabled) + { + return _source; + } + return hstring{ fmt::format(L"{}: {}", _source, RS_(L"Extension_StateDisabled/Text")) }; + } } diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.h b/src/cascadia/TerminalSettingsEditor/Extensions.h index ede533658d5..8bd9dfaf911 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.h +++ b/src/cascadia/TerminalSettingsEditor/Extensions.h @@ -5,6 +5,7 @@ #include "Extensions.g.h" #include "ExtensionsViewModel.g.h" +#include "ExtensionPackageViewModel.g.h" #include "FragmentExtensionViewModel.g.h" #include "FragmentProfileViewModel.g.h" #include "FragmentColorSchemeViewModel.g.h" @@ -22,7 +23,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void ExtensionLoaded(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); void ExtensionToggled(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); - void FragmentNavigator_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); + void ExtensionNavigator_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); void NavigateToProfile_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); void NavigateToColorScheme_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); @@ -35,9 +36,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation ExtensionsViewModel(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM); // Properties - bool IsExtensionView() const noexcept { return _CurrentExtension != nullptr; } - hstring CurrentExtensionSource() const noexcept { return _CurrentExtension ? _CurrentExtension.Fragment().Source() : hstring{}; } - hstring CurrentExtensionJson() const noexcept { return _CurrentExtension ? _CurrentExtension.Fragment().Json() : hstring{}; } + bool IsExtensionView() const noexcept { return _CurrentExtensionSource != hstring{}; } + Windows::Foundation::Collections::IVector CurrentExtensionFragments() const noexcept; hstring CurrentExtensionScope() const noexcept; bool NoActiveExtensions() const noexcept { return _fragmentExtensions.Size() == 0; } bool NoProfilesModified() const noexcept { return _profilesModifiedView.Size() == 0; } @@ -45,7 +45,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation bool NoSchemesAdded() const noexcept { return _colorSchemesAddedView.Size() == 0; } // Views - Windows::Foundation::Collections::IVectorView FragmentExtensions() const noexcept { return _fragmentExtensions.GetView(); } + Windows::Foundation::Collections::IObservableVector ExtensionPackages() const noexcept; Windows::Foundation::Collections::IObservableVector ProfilesModified() const noexcept { return _profilesModifiedView; } Windows::Foundation::Collections::IObservableVector ProfilesAdded() const noexcept { return _profilesAddedView; } Windows::Foundation::Collections::IObservableVector ColorSchemesAdded() const noexcept { return _colorSchemesAddedView; } @@ -60,11 +60,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation til::typed_event NavigateToProfileRequested; til::typed_event NavigateToColorSchemeRequested; - VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::FragmentExtensionViewModel, CurrentExtension, nullptr); + VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, CurrentExtensionSource); private: Model::CascadiaSettings _settings; Editor::ColorSchemesPageViewModel _colorSchemesPageVM; + std::unordered_set _extensionSources; Windows::Foundation::Collections::IVector _fragmentExtensions; Windows::Foundation::Collections::IObservableVector _profilesModifiedView; Windows::Foundation::Collections::IObservableVector _profilesAddedView; @@ -73,6 +74,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Windows::Foundation::Collections::IVector _DisabledProfileSources() const noexcept { return _settings.GlobalSettings().DisabledProfileSources(); } }; + struct ExtensionPackageViewModel : ExtensionPackageViewModelT, ViewModelHelper + { + public: + ExtensionPackageViewModel(hstring source, bool enabled) : + _source{ source }, + _enabled{ enabled } {} + hstring Source() const noexcept { return _source; } + bool Enabled() const noexcept { return _enabled; } + hstring AccessibleName() const noexcept; + + private: + hstring _source; + bool _enabled; + }; + struct FragmentExtensionViewModel : FragmentExtensionViewModelT, ViewModelHelper { public: @@ -89,7 +105,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Windows::Foundation::Collections::IVectorView ProfilesModified() const noexcept { return _profilesModified.GetView(); } Windows::Foundation::Collections::IVectorView ProfilesAdded() const noexcept { return _profilesAdded.GetView(); } Windows::Foundation::Collections::IVectorView ColorSchemesAdded() const noexcept { return _colorSchemesAdded.GetView(); } - hstring Json() const noexcept { return _fragment.Json(); } private: Model::FragmentSettings _fragment; diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.idl b/src/cascadia/TerminalSettingsEditor/Extensions.idl index b7b88ace3a0..ba5cac9885f 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.idl +++ b/src/cascadia/TerminalSettingsEditor/Extensions.idl @@ -14,10 +14,8 @@ namespace Microsoft.Terminal.Settings.Editor [default_interface] runtimeclass ExtensionsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged { // Properties - // TODO CARLOS: Replace "CurrentExtensionX" by just exposing them off of FragmentExtensionViewModel - FragmentExtensionViewModel CurrentExtension; - String CurrentExtensionSource { get; }; - String CurrentExtensionJson { get; }; + String CurrentExtensionSource; + IVector CurrentExtensionFragments { get; }; String CurrentExtensionScope { get; }; Boolean IsExtensionView { get; }; Boolean NoActiveExtensions { get; }; @@ -26,10 +24,10 @@ namespace Microsoft.Terminal.Settings.Editor Boolean NoSchemesAdded { get; }; // Views - Windows.Foundation.Collections.IVectorView FragmentExtensions { get; }; - Windows.Foundation.Collections.IObservableVector ProfilesModified { get; }; - Windows.Foundation.Collections.IObservableVector ProfilesAdded { get; }; - Windows.Foundation.Collections.IObservableVector ColorSchemesAdded { get; }; + IObservableVector ExtensionPackages { get; }; + IObservableVector ProfilesModified { get; }; + IObservableVector ProfilesAdded { get; }; + IObservableVector ColorSchemesAdded { get; }; // Methods void UpdateSettings(Microsoft.Terminal.Settings.Model.CascadiaSettings settings, ColorSchemesPageViewModel colorSchemesPageVM); @@ -40,15 +38,19 @@ namespace Microsoft.Terminal.Settings.Editor event Windows.Foundation.TypedEventHandler NavigateToColorSchemeRequested; } + [default_interface] runtimeclass ExtensionPackageViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + String Source { get; }; + Boolean Enabled { get; }; + String AccessibleName { get; }; + } + [default_interface] runtimeclass FragmentExtensionViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged { Microsoft.Terminal.Settings.Model.FragmentSettings Fragment { get; }; - Windows.Foundation.Collections.IVectorView ProfilesModified { get; }; - Windows.Foundation.Collections.IVectorView ProfilesAdded { get; }; - Windows.Foundation.Collections.IVectorView ColorSchemesAdded { get; }; - - // TODO CARLOS: JSON should be an IVector (requires settings model changes) - String Json { get; }; + IVectorView ProfilesModified { get; }; + IVectorView ProfilesAdded { get; }; + IVectorView ColorSchemesAdded { get; }; } [default_interface] runtimeclass FragmentProfileViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.xaml b/src/cascadia/TerminalSettingsEditor/Extensions.xaml index b719215b2fb..84c191bde3b 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Extensions.xaml @@ -39,12 +39,26 @@ - - + + + + + + + + + @@ -273,9 +302,9 @@ - - + - - - - - + diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 05e3c00a0ce..8fc9817c3a8 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -119,17 +119,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _extensionsVM = *extensionsVMImpl; _extensionsViewModelChangedRevoker = _extensionsVM.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) { const auto settingName{ args.PropertyName() }; - if (settingName == L"CurrentExtension") + if (settingName == L"CurrentExtensionSource") { - if (const auto& currentExtension = _extensionsVM.CurrentExtension()) + if (const auto& currentExtensionSource = _extensionsVM.CurrentExtensionSource(); currentExtensionSource != hstring{}) { - const auto crumb = winrt::make(box_value(currentExtension), currentExtension.Fragment().Source(), BreadcrumbSubPage::Extensions_Extension); + const auto crumb = winrt::make(box_value(currentExtensionSource), currentExtensionSource, BreadcrumbSubPage::Extensions_Extension); _breadcrumbs.Append(crumb); SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0); } else { - // If we don't have a current folder, we're at the root of the Extensions page + // If we don't have a current extension source, we're at the root of the Extensions page _breadcrumbs.Clear(); const auto crumb = winrt::make(box_value(extensionsTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None); _breadcrumbs.Append(crumb); @@ -206,11 +206,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { if (const auto& breadcrumbStringTag{ crumb->Tag().try_as() }) { - if (stringTag == breadcrumbStringTag) + if (stringTag == breadcrumbStringTag || (crumb->SubPage() == BreadcrumbSubPage::Extensions_Extension && stringTag == extensionsTag)) { // found the one that was selected before the refresh + // If the subpage was Extensions_Extension, we need to navigate to the Extensions page (stringTag is the CurrentExtensionSource) SettingsNav().SelectedItem(item); - _Navigate(*stringTag, crumb->SubPage()); + _Navigate(*breadcrumbStringTag, crumb->SubPage()); return; } } @@ -225,17 +226,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return; } } - else if (const auto& breadcrumbExtensionTag{ crumb->Tag().try_as() }) - { - if (stringTag == extensionsTag) - { - // navigate to the Extensions page, - // _Navigate() will handle trying to find the right subpage - SettingsNav().SelectedItem(item); - _Navigate(breadcrumbExtensionTag, BreadcrumbSubPage::Extensions_Extension); - return; - } - } } else if (const auto& profileTag{ tag.try_as() }) { @@ -484,13 +474,39 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _breadcrumbs.Append(crumb); } } - else if (clickedItemTag == extensionsTag) + else if (clickedItemTag == extensionsTag || subPage == BreadcrumbSubPage::Extensions_Extension) { - if (_extensionsVM.CurrentExtension()) + if (subPage == BreadcrumbSubPage::Extensions_Extension) + { + bool found = false; + for (const auto& fragExtVM : _extensionsVM.CurrentExtensionFragments()) + { + // clickedItemTag may be an extension source. Check if it exists. + if (fragExtVM.as().Fragment().Source() == clickedItemTag) + { + // Add "Extensions" main breadcrumb first + contentFrame().Navigate(xaml_typename(), _extensionsVM); + const auto crumb = winrt::make(box_value(extensionsTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None); + _breadcrumbs.Append(crumb); + + // Take advantage of the PropertyChanged event to navigate + // to the correct extension and build the breadcrumbs as we go + _extensionsVM.CurrentExtensionSource(clickedItemTag); + found = true; + break; + } + } + if (!found) + { + // Couldn't find the extension, so navigate back to the Extensions page + _extensionsVM.CurrentExtensionSource(hstring{}); + } + } + else if (_extensionsVM.CurrentExtensionSource() != hstring{}) { - // Setting CurrentExtension triggers the PropertyChanged event, + // Setting CurrentExtensionSource triggers the PropertyChanged event, // which will navigate to the correct page and update the breadcrumbs appropriately - _extensionsVM.CurrentExtension(nullptr); + _extensionsVM.CurrentExtensionSource(hstring{}); } else { @@ -629,40 +645,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - void MainPage::_Navigate(const Editor::FragmentExtensionViewModel& extVM, BreadcrumbSubPage subPage) - { - _PreNavigateHelper(); - - contentFrame().Navigate(xaml_typename(), _extensionsVM); - const auto crumb = winrt::make(box_value(extensionsTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None); - _breadcrumbs.Append(crumb); - - if (subPage == BreadcrumbSubPage::None) - { - _extensionsVM.CurrentExtension(nullptr); - } - else if (subPage == BreadcrumbSubPage::Extensions_Extension && extVM) - { - bool found{ false }; - for (const auto& ext : _extensionsVM.FragmentExtensions()) - { - if (ext.Fragment().Source() == extVM.Fragment().Source()) - { - // Take advantage of the PropertyChanged event to navigate - // to the correct extension and build the breadcrumbs as we go - _extensionsVM.CurrentExtension(ext); - found = true; - break; - } - } - if (!found) - { - // Couldn't find a match, just go back to the root - _extensionsVM.CurrentExtension(nullptr); - } - } - } - void MainPage::OpenJsonTapped(const IInspectable& /*sender*/, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& /*args*/) { const auto window = CoreWindow::GetForCurrentThread(); @@ -709,10 +691,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _Navigate(*ntmEntryViewModel, subPage); } - else if (const auto extensionViewModel = tag.try_as()) - { - _Navigate(*extensionViewModel, subPage); - } else { _Navigate(tag.as(), subPage); diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.h b/src/cascadia/TerminalSettingsEditor/MainPage.h index 9b1aaee2881..997237bbe04 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.h +++ b/src/cascadia/TerminalSettingsEditor/MainPage.h @@ -70,7 +70,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void _Navigate(hstring clickedItemTag, BreadcrumbSubPage subPage); void _Navigate(const Editor::ProfileViewModel& profile, BreadcrumbSubPage subPage); void _Navigate(const Editor::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage); - void _Navigate(const Editor::FragmentExtensionViewModel& extVM, BreadcrumbSubPage subPage); void _NavigateToProfileHandler(const IInspectable& sender, winrt::guid profileGuid); void _NavigateToColorSchemeHandler(const IInspectable& sender, const IInspectable& args); diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 44828131c45..71b541bc755 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -2399,8 +2399,8 @@ Scope Header for the installation scope of the extension - - JSON - {Locked="JSON"} + + Disabled + Text displayed when an extension is disabled \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index c6f304094a3..8d76d42dd65 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -144,7 +144,7 @@ Model::CascadiaSettings CascadiaSettings::Copy() const Model::FragmentSettings FragmentSettings::Copy() const { - auto fragment{ winrt::make_self(_source, _json, _scope) }; + auto fragment{ winrt::make_self(_source, _json, _jsonSource, _scope) }; std::vector modifiedProfiles; modifiedProfiles.reserve(_modifiedProfiles.Size()); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index 766b359ef3d..f772303cbc8 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -92,7 +92,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static const Json::Value& _getJSONValue(const Json::Value& json, const std::string_view& key) noexcept; std::span> _getNonUserOriginProfiles() const; void _parse(const OriginTag origin, const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings); - void _parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings, FragmentScope scope, bool applyToSettings); + void _parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings, FragmentScope scope, std::wstring_view jsonFilename, bool applyToSettings); static JsonSettings _parseJson(const std::string_view& content); static winrt::com_ptr _parseProfile(const OriginTag origin, const winrt::hstring& source, const Json::Value& profileJson); void _appendProfile(winrt::com_ptr&& profile, const winrt::guid& guid, ParsedSettings& settings); @@ -240,9 +240,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation struct FragmentSettings : FragmentSettingsT { public: - FragmentSettings(hstring source, hstring json, FragmentScope scope) : + FragmentSettings(hstring source, hstring json, hstring jsonSource, FragmentScope scope) : _source{ source }, _json{ json }, + _jsonSource{ jsonSource }, _scope{ scope }, _modifiedProfiles{ winrt::single_threaded_vector() }, _newProfiles{ winrt::single_threaded_vector() }, @@ -253,6 +254,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation hstring Source() const noexcept { return _source; } FragmentScope Scope() const noexcept { return _scope; } hstring Json() const noexcept { return _json; } + hstring JsonSource() const noexcept { return _jsonSource; } Windows::Foundation::Collections::IVector ModifiedProfiles() const noexcept { return _modifiedProfiles; } Windows::Foundation::Collections::IVector NewProfiles() const noexcept { return _newProfiles; } Windows::Foundation::Collections::IVector ColorSchemes() const noexcept { return _colorSchemes; } @@ -265,6 +267,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation private: hstring _source; hstring _json; + hstring _jsonSource; FragmentScope _scope; Windows::Foundation::Collections::IVector _modifiedProfiles; Windows::Foundation::Collections::IVector _newProfiles; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl index ec918dbeefa..25b65e1ad18 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl @@ -82,6 +82,7 @@ namespace Microsoft.Terminal.Settings.Model String Source { get; }; FragmentScope Scope { get; }; String Json { get; }; + String JsonSource { get; }; Windows.Foundation.Collections.IVectorView ModifiedProfilesView { get; }; Windows.Foundation.Collections.IVectorView NewProfilesView { get; }; Windows.Foundation.Collections.IVectorView ColorSchemesView { get; }; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 1aef61adf6b..0997a37a966 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -256,7 +256,7 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings() const auto content = til::io::read_file_as_utf8_string_if_exists(fragmentExt.path()); if (!content.empty()) { - _parseFragment(source, content, fragmentSettings, scope, layerFragment); + _parseFragment(source, content, fragmentSettings, scope, fragmentExt.path().filename().wstring(), layerFragment); } } CATCH_LOG(); @@ -338,7 +338,7 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings() parseAndLayerFragmentFiles(path, packageName, FragmentScope::User, - _ignoredNamespaces.contains(std::wstring_view{ packageName })); // layerFragment + !_ignoredNamespaces.contains(std::wstring_view{ packageName })); // layerFragment } } } @@ -349,7 +349,7 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings() void SettingsLoader::MergeFragmentIntoUserSettings(const winrt::hstring& source, const std::string_view& content) { ParsedSettings fragmentSettings; - _parseFragment(source, content, fragmentSettings, FragmentScope::User, true); + _parseFragment(source, content, fragmentSettings, FragmentScope::User, hstring{ L"filename.json" }, true); } // Call this method before passing SettingsLoader to the CascadiaSettings constructor. @@ -729,15 +729,16 @@ void SettingsLoader::_parse(const OriginTag origin, const winrt::hstring& source // Just like _parse, but is to be used for fragment files, which don't support anything but color // schemes and profiles. Additionally this function supports profiles which specify an "updates" key. // - scope: The scope of the fragment file (user or machine). +// - jsonFilename: The filename of the JSON file being parsed. // - applyToSettings: If true, the parsed settings will be applied to the user settings. Otherwise, load the fragment for the settings UI, but don't apply it. -void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings, FragmentScope scope, bool applyToSettings) +void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings, FragmentScope scope, std::wstring_view jsonFilename, bool applyToSettings) { auto json = _parseJson(content); Json::StreamWriterBuilder styledWriter; styledWriter["indentation"] = " "; styledWriter["commentStyle"] = "All"; - auto fragmentSettings = winrt::make_self(source, hstring{ til::u8u16(Json::writeString(styledWriter, json.root)) }, scope); + auto fragmentSettings = winrt::make_self(source, hstring{ til::u8u16(Json::writeString(styledWriter, json.root)) }, hstring{ jsonFilename }, scope); settings.clear(); From e6c43c0d4cd93e36e9ce92729552c2614710e657 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 19 Feb 2025 12:29:35 -0800 Subject: [PATCH 05/27] copy over DisabledProfileSources --- src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 8a699eb8656..55af1703558 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -92,6 +92,14 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_NewTabMenu->Append(get_self(entry)->Copy()); } } + if (_DisabledProfileSources) + { + globals->_DisabledProfileSources = winrt::single_threaded_vector(); + for (const auto& src : *_DisabledProfileSources) + { + globals->_DisabledProfileSources->Append(src); + } + } for (const auto& parent : _parents) { From 6b83fa705af710d1e381c6eb575d33392a9272ce Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 24 Feb 2025 16:04:06 -0800 Subject: [PATCH 06/27] wil::to_vector --- src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 55af1703558..0880873a0a4 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -94,11 +94,7 @@ winrt::com_ptr GlobalAppSettings::Copy() const } if (_DisabledProfileSources) { - globals->_DisabledProfileSources = winrt::single_threaded_vector(); - for (const auto& src : *_DisabledProfileSources) - { - globals->_DisabledProfileSources->Append(src); - } + globals->_DisabledProfileSources = winrt::single_threaded_vector(std::move(wil::to_vector(*globals->_DisabledProfileSources))); } for (const auto& parent : _parents) From 5afc9bc86aec56195b305da953d789b92a7e9b0f Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 24 Feb 2025 17:45:25 -0800 Subject: [PATCH 07/27] add dynamic profile generators --- .../TerminalSettingsEditor/Extensions.cpp | 21 +++++++-- .../CascadiaSettings.cpp | 16 +++++++ .../TerminalSettingsModel/CascadiaSettings.h | 3 ++ .../CascadiaSettings.idl | 1 + .../CascadiaSettingsSerialization.cpp | 47 ++++++++++++++----- 5 files changed, 70 insertions(+), 18 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.cpp b/src/cascadia/TerminalSettingsEditor/Extensions.cpp index 1347c070d2f..1792785f93a 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.cpp +++ b/src/cascadia/TerminalSettingsEditor/Extensions.cpp @@ -111,14 +111,25 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _extensionSources.clear(); _CurrentExtensionSource.clear(); - std::vector fragmentExtensions; - fragmentExtensions.reserve(settings.FragmentExtensions().Size()); + std::vector extensions; + extensions.reserve(settings.FragmentExtensions().Size() + settings.DynamicProfileGenerators().Size()); + for (auto ext : settings.FragmentExtensions()) + { + extensions.push_back(ext); + } + for (auto ext : settings.DynamicProfileGenerators()) + { + extensions.push_back(ext); + } + + std::vector extensionVMs; + extensionVMs.reserve(extensions.size()); // these vectors track components all extensions successfully added std::vector profilesModifiedTotal; std::vector profilesAddedTotal; std::vector colorSchemesAddedTotal; - for (const auto&& fragExt : settings.FragmentExtensions()) + for (const auto& fragExt : extensions) { const auto extensionEnabled = GetExtensionState(fragExt.Source()); @@ -173,10 +184,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } _extensionSources.insert(fragExt.Source()); - fragmentExtensions.push_back(winrt::make(fragExt, currentProfilesModified, currentProfilesAdded, currentColorSchemesAdded)); + extensionVMs.push_back(winrt::make(fragExt, currentProfilesModified, currentProfilesAdded, currentColorSchemesAdded)); } - _fragmentExtensions = single_threaded_observable_vector(std::move(fragmentExtensions)); + _fragmentExtensions = single_threaded_observable_vector(std::move(extensionVMs)); _profilesModifiedView = single_threaded_observable_vector(std::move(profilesModifiedTotal)); _profilesAddedView = single_threaded_observable_vector(std::move(profilesAddedTotal)); _colorSchemesAddedView = single_threaded_observable_vector(std::move(colorSchemesAddedTotal)); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index 8d76d42dd65..c7a5f8d2928 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -124,6 +124,17 @@ Model::CascadiaSettings CascadiaSettings::Copy() const } settings->_fragmentExtensions = winrt::single_threaded_vector(std::move(fragmentExtensions)); } + + // copy dynamic profile generators + { + std::vector dynamicProfileGenerators; + dynamicProfileGenerators.reserve(_dynamicProfileGeneratorExtensions.Size()); + for (const auto& fragment : _dynamicProfileGeneratorExtensions) + { + dynamicProfileGenerators.emplace_back(get_self(fragment)->Copy()); + } + settings->_dynamicProfileGeneratorExtensions = winrt::single_threaded_vector(std::move(dynamicProfileGenerators)); + } } // load errors @@ -220,6 +231,11 @@ IVectorView CascadiaSettings::FragmentExtensions() cons return _fragmentExtensions.GetView(); } +IVectorView CascadiaSettings::DynamicProfileGenerators() const noexcept +{ + return _dynamicProfileGeneratorExtensions.GetView(); +} + // Method Description: // - Returns the globally configured keybindings // Arguments: diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index f772303cbc8..22238092bb7 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -74,6 +74,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation ParsedSettings inboxSettings; ParsedSettings userSettings; std::vector fragmentExtensions; + std::vector dynamicProfileGeneratorExtensions; bool duplicateProfile = false; private: @@ -132,6 +133,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::Windows::Foundation::Collections::IObservableVector ActiveProfiles() const noexcept; Model::ActionMap ActionMap() const noexcept; winrt::Windows::Foundation::Collections::IVectorView FragmentExtensions() const noexcept; + winrt::Windows::Foundation::Collections::IVectorView DynamicProfileGenerators() const noexcept; void WriteSettingsToDisk(); Json::Value ToJson() const; Model::Profile ProfileDefaults() const; @@ -190,6 +192,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::Windows::Foundation::Collections::IObservableVector _allProfiles = winrt::single_threaded_observable_vector(); winrt::Windows::Foundation::Collections::IObservableVector _activeProfiles = winrt::single_threaded_observable_vector(); winrt::Windows::Foundation::Collections::IVector _fragmentExtensions = winrt::single_threaded_vector(); + winrt::Windows::Foundation::Collections::IVector _dynamicProfileGeneratorExtensions = winrt::single_threaded_vector(); std::set _themesChangeLog{}; // load errors diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl index 25b65e1ad18..5671ed21d42 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl @@ -45,6 +45,7 @@ namespace Microsoft.Terminal.Settings.Model ActionMap ActionMap { get; }; Windows.Foundation.Collections.IVectorView FragmentExtensions { get; }; + Windows.Foundation.Collections.IVectorView DynamicProfileGenerators { get; }; IVectorView Warnings { get; }; Windows.Foundation.IReference GetLoadingError { get; }; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 0997a37a966..9ec727253a8 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -991,31 +991,51 @@ bool SettingsLoader::_addOrMergeUserColorScheme(const winrt::com_ptr> generatedProfiles; try { - generator.GenerateProfiles(inboxSettings.profiles); + generator.GenerateProfiles(generatedProfiles); } CATCH_LOG_MSG("Dynamic Profile Namespace: \"%.*s\"", gsl::narrow(generatorNamespace.size()), generatorNamespace.data()) + // These are needed for the FragmentSettings object + std::vector profileEntries; + Json::Value profilesListJson{ Json::ValueType::arrayValue }; + Json::StreamWriterBuilder styledWriter; + styledWriter["indentation"] = " "; + // If the generator produced some profiles we're going to give them default attributes. // By setting the Origin/Source/etc. here, we deduplicate some code and ensure they aren't missing accidentally. - if (inboxSettings.profiles.size() > previousSize) + const winrt::hstring source{ generatorNamespace }; + for (const auto& profile : generatedProfiles) { - const winrt::hstring source{ generatorNamespace }; + profile->Origin(OriginTag::Generated); + profile->Source(source); + + const auto profileJson = profile->ToJson(); + profilesListJson.append(profileJson); + profileEntries.push_back(winrt::make(profile->Guid(), hstring{ til::u8u16(Json::writeString(styledWriter, profileJson)) })); + } - for (const auto& profile : std::span(inboxSettings.profiles).subspan(previousSize)) + if (!_ignoredNamespaces.contains(generatorNamespace)) + { + // Add generated profiles to the user settings + for (auto& profile : generatedProfiles) { - profile->Origin(OriginTag::Generated); - profile->Source(source); + inboxSettings.profiles.push_back(profile); } } + + // Manually construct the JSON for the FragmentSettings object + Json::Value json{ Json::ValueType::objectValue }; + json[JsonKey(ProfilesKey)] = profilesListJson; + + auto generatorExtension = winrt::make_self(hstring{ generatorNamespace }, hstring{ til::u8u16(Json::writeString(styledWriter, json)) }, hstring{ L"settings.json" }, FragmentScope::Machine); + for (const auto& entry : profileEntries) + { + generatorExtension->NewProfiles().Append(entry); + } + dynamicProfileGeneratorExtensions.emplace_back(*generatorExtension); } // Method Description: @@ -1308,6 +1328,7 @@ CascadiaSettings::CascadiaSettings(SettingsLoader&& loader) : _warnings = winrt::single_threaded_vector(std::move(warnings)); _themesChangeLog = std::move(loader.userSettings.themesChangeLog); _fragmentExtensions = winrt::single_threaded_vector(std::move(loader.fragmentExtensions)); + _dynamicProfileGeneratorExtensions = winrt::single_threaded_vector(std::move(loader.dynamicProfileGeneratorExtensions)); _resolveDefaultProfile(); _resolveNewTabMenuProfiles(); From 7cdbb7c795c9f79f1f8261f3160d31d6a97a02df Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 27 Feb 2025 10:17:11 -0800 Subject: [PATCH 08/27] address feedback --- src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 0880873a0a4..55af1703558 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -94,7 +94,11 @@ winrt::com_ptr GlobalAppSettings::Copy() const } if (_DisabledProfileSources) { - globals->_DisabledProfileSources = winrt::single_threaded_vector(std::move(wil::to_vector(*globals->_DisabledProfileSources))); + globals->_DisabledProfileSources = winrt::single_threaded_vector(); + for (const auto& src : *_DisabledProfileSources) + { + globals->_DisabledProfileSources->Append(src); + } } for (const auto& parent : _parents) From b0a7ef1d3935da40415edb03f6b992cf925174bb Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 21 Apr 2025 14:22:19 -0700 Subject: [PATCH 09/27] apply feedback from Dustin --- .../TerminalSettingsEditor/Extensions.cpp | 53 +++++++++++-------- .../CascadiaSettings.cpp | 39 ++++++++------ .../TerminalSettingsModel/CascadiaSettings.h | 16 +++--- .../CascadiaSettingsSerialization.cpp | 42 +++++++-------- 4 files changed, 83 insertions(+), 67 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.cpp b/src/cascadia/TerminalSettingsEditor/Extensions.cpp index 1792785f93a..38980653e87 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.cpp +++ b/src/cascadia/TerminalSettingsEditor/Extensions.cpp @@ -138,46 +138,55 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation std::vector currentProfilesAdded; std::vector currentColorSchemesAdded; - for (const auto&& entry : fragExt.ModifiedProfilesView()) + if (fragExt.ModifiedProfilesView()) { - // Ensure entry successfully modifies a profile before creating and registering the object - if (const auto& deducedProfile = _settings.FindProfile(entry.ProfileGuid())) + for (const auto&& entry : fragExt.ModifiedProfilesView()) { - auto vm = winrt::make(entry, fragExt, deducedProfile); - currentProfilesModified.push_back(vm); - if (extensionEnabled) + // Ensure entry successfully modifies a profile before creating and registering the object + if (const auto& deducedProfile = _settings.FindProfile(entry.ProfileGuid())) { - profilesModifiedTotal.push_back(vm); + auto vm = winrt::make(entry, fragExt, deducedProfile); + currentProfilesModified.push_back(vm); + if (extensionEnabled) + { + profilesModifiedTotal.push_back(vm); + } } } } - for (const auto&& entry : fragExt.NewProfilesView()) + if (fragExt.NewProfilesView()) { - // Ensure entry successfully points to a profile before creating and registering the object. - // The profile may have been removed by the user. - if (const auto& deducedProfile = _settings.FindProfile(entry.ProfileGuid())) + for (const auto&& entry : fragExt.NewProfilesView()) { - auto vm = winrt::make(entry, fragExt, deducedProfile); - currentProfilesAdded.push_back(vm); - if (extensionEnabled) + // Ensure entry successfully points to a profile before creating and registering the object. + // The profile may have been removed by the user. + if (const auto& deducedProfile = _settings.FindProfile(entry.ProfileGuid())) { - profilesAddedTotal.push_back(vm); + auto vm = winrt::make(entry, fragExt, deducedProfile); + currentProfilesAdded.push_back(vm); + if (extensionEnabled) + { + profilesAddedTotal.push_back(vm); + } } } } - for (const auto&& entry : fragExt.ColorSchemesView()) + if (fragExt.ColorSchemesView()) { - for (const auto& schemeVM : _colorSchemesPageVM.AllColorSchemes()) + for (const auto&& entry : fragExt.ColorSchemesView()) { - if (schemeVM.Name() == entry.ColorSchemeName()) + for (const auto& schemeVM : _colorSchemesPageVM.AllColorSchemes()) { - auto vm = winrt::make(entry, fragExt, schemeVM); - currentColorSchemesAdded.push_back(vm); - if (extensionEnabled) + if (schemeVM.Name() == entry.ColorSchemeName()) { - colorSchemesAddedTotal.push_back(vm); + auto vm = winrt::make(entry, fragExt, schemeVM); + currentColorSchemesAdded.push_back(vm); + if (extensionEnabled) + { + colorSchemesAddedTotal.push_back(vm); + } } } } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index c7a5f8d2928..c00418652d0 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -157,29 +157,38 @@ Model::FragmentSettings FragmentSettings::Copy() const { auto fragment{ winrt::make_self(_source, _json, _jsonSource, _scope) }; - std::vector modifiedProfiles; - modifiedProfiles.reserve(_modifiedProfiles.Size()); - for (const auto& entry : _modifiedProfiles) + if (_modifiedProfiles) { - modifiedProfiles.emplace_back(winrt::make(entry.ProfileGuid(), entry.Json())); + std::vector modifiedProfiles; + modifiedProfiles.reserve(_modifiedProfiles.Size()); + for (const auto& entry : _modifiedProfiles) + { + modifiedProfiles.emplace_back(winrt::make(entry.ProfileGuid(), entry.Json())); + } + fragment->_modifiedProfiles = winrt::single_threaded_vector(std::move(modifiedProfiles)); } - fragment->_modifiedProfiles = winrt::single_threaded_observable_vector(std::move(modifiedProfiles)); - std::vector newProfiles; - newProfiles.reserve(_newProfiles.Size()); - for (const auto& entry : _newProfiles) + if (_newProfiles) { - newProfiles.emplace_back(winrt::make(entry.ProfileGuid(), entry.Json())); + std::vector newProfiles; + newProfiles.reserve(_newProfiles.Size()); + for (const auto& entry : _newProfiles) + { + newProfiles.emplace_back(winrt::make(entry.ProfileGuid(), entry.Json())); + } + fragment->_newProfiles = winrt::single_threaded_vector(std::move(newProfiles)); } - fragment->_newProfiles = winrt::single_threaded_observable_vector(std::move(newProfiles)); - std::vector colorSchemes; - colorSchemes.reserve(_colorSchemes.Size()); - for (const auto& entry : _colorSchemes) + if (_colorSchemes) { - colorSchemes.emplace_back(winrt::make(entry.ColorSchemeName(), entry.Json())); + std::vector colorSchemes; + colorSchemes.reserve(_colorSchemes.Size()); + for (const auto& entry : _colorSchemes) + { + colorSchemes.emplace_back(winrt::make(entry.ColorSchemeName(), entry.Json())); + } + fragment->_colorSchemes = winrt::single_threaded_vector(std::move(colorSchemes)); } - fragment->_colorSchemes = winrt::single_threaded_observable_vector(std::move(colorSchemes)); return *fragment; } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index 22238092bb7..d01999559eb 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -191,7 +191,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::com_ptr _baseLayerProfile = winrt::make_self(); winrt::Windows::Foundation::Collections::IObservableVector _allProfiles = winrt::single_threaded_observable_vector(); winrt::Windows::Foundation::Collections::IObservableVector _activeProfiles = winrt::single_threaded_observable_vector(); - winrt::Windows::Foundation::Collections::IVector _fragmentExtensions = winrt::single_threaded_vector(); + winrt::Windows::Foundation::Collections::IVector _fragmentExtensions; winrt::Windows::Foundation::Collections::IVector _dynamicProfileGeneratorExtensions = winrt::single_threaded_vector(); std::set _themesChangeLog{}; @@ -247,10 +247,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _source{ source }, _json{ json }, _jsonSource{ jsonSource }, - _scope{ scope }, - _modifiedProfiles{ winrt::single_threaded_vector() }, - _newProfiles{ winrt::single_threaded_vector() }, - _colorSchemes{ winrt::single_threaded_vector() } {} + _scope{ scope } {} Model::FragmentSettings Copy() const; @@ -259,13 +256,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation hstring Json() const noexcept { return _json; } hstring JsonSource() const noexcept { return _jsonSource; } Windows::Foundation::Collections::IVector ModifiedProfiles() const noexcept { return _modifiedProfiles; } + void ModifiedProfiles(const Windows::Foundation::Collections::IVector& modifiedProfiles) noexcept { _modifiedProfiles = modifiedProfiles; } Windows::Foundation::Collections::IVector NewProfiles() const noexcept { return _newProfiles; } + void NewProfiles(const Windows::Foundation::Collections::IVector& newProfiles) noexcept { _newProfiles = newProfiles; } Windows::Foundation::Collections::IVector ColorSchemes() const noexcept { return _colorSchemes; } + void ColorSchemes(const Windows::Foundation::Collections::IVector& colorSchemes) noexcept { _colorSchemes = colorSchemes; } // views - Windows::Foundation::Collections::IVectorView ModifiedProfilesView() const noexcept { return _modifiedProfiles.GetView(); } - Windows::Foundation::Collections::IVectorView NewProfilesView() const noexcept { return _newProfiles.GetView(); } - Windows::Foundation::Collections::IVectorView ColorSchemesView() const noexcept { return _colorSchemes.GetView(); } + Windows::Foundation::Collections::IVectorView ModifiedProfilesView() const noexcept { return _modifiedProfiles ? _modifiedProfiles.GetView() : nullptr; } + Windows::Foundation::Collections::IVectorView NewProfilesView() const noexcept { return _newProfiles ? _newProfiles.GetView() : nullptr; } + Windows::Foundation::Collections::IVectorView ColorSchemesView() const noexcept { return _colorSchemes ? _colorSchemes.GetView() : nullptr; } private: hstring _source; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 9ec727253a8..4f5d7d5091c 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -246,17 +246,18 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings() { ParsedSettings fragmentSettings; - const auto parseAndLayerFragmentFiles = [&](const std::filesystem::path& path, const winrt::hstring& source, FragmentScope scope, bool layerFragment) { + const auto parseAndLayerFragmentFiles = [&](const std::filesystem::path& path, const winrt::hstring& source, FragmentScope scope, bool applyToSettings) { for (const auto& fragmentExt : std::filesystem::directory_iterator{ path }) { - if (fragmentExt.path().extension() == jsonExtension) + const auto fragExtPath = fragmentExt.path(); + if (fragExtPath.extension() == jsonExtension) { try { - const auto content = til::io::read_file_as_utf8_string_if_exists(fragmentExt.path()); + const auto content = til::io::read_file_as_utf8_string_if_exists(fragExtPath); if (!content.empty()) { - _parseFragment(source, content, fragmentSettings, scope, fragmentExt.path().filename().wstring(), layerFragment); + _parseFragment(source, content, fragmentSettings, scope, fragExtPath.filename().wstring(), applyToSettings); } } CATCH_LOG(); @@ -283,7 +284,7 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings() parseAndLayerFragmentFiles(fragmentExtFolder.path(), winrt::hstring{ source }, rfid == FOLDERID_LocalAppData ? FragmentScope::User : FragmentScope::Machine, // scope - !_ignoredNamespaces.contains(std::wstring_view{ source })); // layerFragment + !_ignoredNamespaces.contains(std::wstring_view{ source })); // applyToSettings } } } @@ -338,7 +339,7 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings() parseAndLayerFragmentFiles(path, packageName, FragmentScope::User, - !_ignoredNamespaces.contains(std::wstring_view{ packageName })); // layerFragment + !_ignoredNamespaces.contains(std::wstring_view{ packageName })); // applyToSettings } } } @@ -349,7 +350,7 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings() void SettingsLoader::MergeFragmentIntoUserSettings(const winrt::hstring& source, const std::string_view& content) { ParsedSettings fragmentSettings; - _parseFragment(source, content, fragmentSettings, FragmentScope::User, hstring{ L"filename.json" }, true); + _parseFragment(source, content, fragmentSettings, FragmentScope::User, L"filename.json", true); } // Call this method before passing SettingsLoader to the CascadiaSettings constructor. @@ -745,6 +746,7 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str { settings.globals = winrt::make_self(); + std::vector fragmentColorSchemes; for (const auto& schemeJson : json.colorSchemes) { try @@ -759,11 +761,12 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str // (search for STAGED COLORS to find the next step) settings.colorSchemes.emplace(scheme->Name(), std::move(scheme)); } - fragmentSettings->ColorSchemes().Append(winrt::make(scheme->Name(), hstring{ til::u8u16(Json::writeString(styledWriter, schemeJson)) })); + fragmentColorSchemes.emplace_back(winrt::make(scheme->Name(), hstring{ til::u8u16(Json::writeString(styledWriter, schemeJson)) })); } } CATCH_LOG() } + fragmentSettings->ColorSchemes(fragmentColorSchemes.empty() ? nullptr : single_threaded_vector(std::move(fragmentColorSchemes))); if (applyToSettings) { @@ -779,6 +782,8 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str settings.profiles.reserve(size); settings.profilesByGuid.reserve(size); + std::vector newProfiles; + std::vector modifiedProfiles; for (const auto& profileJson : json.profilesList) { try @@ -788,25 +793,21 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str // We need to make sure to only call Guid() if HasGuid() is true, // as Guid() will dynamically generate a return value otherwise. auto profile = _parseProfile(OriginTag::Fragment, source, profileJson); - if (const auto guid = profile->Guid(); profile->HasGuid() && guid != winrt::guid{}) + const auto guid = profile->HasGuid() ? profile->Guid() : profile->Updates(); + auto destinationSet = profile->HasGuid() ? newProfiles : modifiedProfiles; + if (guid != winrt::guid{}) { if (applyToSettings) { _appendProfile(std::move(profile), guid, settings); } - fragmentSettings->NewProfiles().Append(winrt::make(guid, hstring{ til::u8u16(Json::writeString(styledWriter, profileJson)) })); - } - else if (const auto guid = profile->Updates(); guid != winrt::guid{}) - { - if (applyToSettings) - { - _appendProfile(std::move(profile), guid, settings); - } - fragmentSettings->ModifiedProfiles().Append(winrt::make(guid, hstring{ til::u8u16(Json::writeString(styledWriter, profileJson)) })); + destinationSet.emplace_back(winrt::make(guid, hstring{ til::u8u16(Json::writeString(styledWriter, profileJson)) })); } } CATCH_LOG() } + fragmentSettings->NewProfiles(newProfiles.empty() ? nullptr : single_threaded_vector(std::move(newProfiles))); + fragmentSettings->ModifiedProfiles(modifiedProfiles.empty() ? nullptr : single_threaded_vector(std::move(modifiedProfiles))); } if (applyToSettings) @@ -1031,10 +1032,7 @@ void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator json[JsonKey(ProfilesKey)] = profilesListJson; auto generatorExtension = winrt::make_self(hstring{ generatorNamespace }, hstring{ til::u8u16(Json::writeString(styledWriter, json)) }, hstring{ L"settings.json" }, FragmentScope::Machine); - for (const auto& entry : profileEntries) - { - generatorExtension->NewProfiles().Append(entry); - } + generatorExtension->NewProfiles(winrt::single_threaded_vector(std::move(profileEntries))); dynamicProfileGeneratorExtensions.emplace_back(*generatorExtension); } From 0af4eb0e21d140ef17e10d9db4c72ddd2a0660ca Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 21 Apr 2025 14:32:08 -0700 Subject: [PATCH 10/27] Fix XAML crash for release builds (applied incompatible styling) --- src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml b/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml index b5045dd43a2..02b84ae9b51 100644 --- a/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml +++ b/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml @@ -341,8 +341,7 @@ - +