diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.cpp b/src/cascadia/TerminalSettingsEditor/Extensions.cpp index 1792785f93a..99c7d5e80f2 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.cpp +++ b/src/cascadia/TerminalSettingsEditor/Extensions.cpp @@ -7,6 +7,7 @@ #include "ExtensionPackageViewModel.g.cpp" #include "ExtensionsViewModel.g.cpp" #include "FragmentProfileViewModel.g.cpp" +#include "ExtensionPackageTemplateSelector.g.cpp" #include #include "..\WinRTUtils\inc\Utils.h" @@ -22,31 +23,25 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Extensions::Extensions() { InitializeComponent(); - } - void Extensions::OnNavigatedTo(const NavigationEventArgs& e) - { - _ViewModel = e.Parameter().as(); - } + _extensionPackageIdentifierTemplateSelector = Resources().Lookup(box_value(L"ExtensionPackageIdentifierTemplateSelector")).as(); - void Extensions::ExtensionLoaded(const IInspectable& sender, const RoutedEventArgs& /*args*/) - { - const auto& toggleSwitch = sender.as(); - const auto& extensionSource = toggleSwitch.Tag().as(); - toggleSwitch.IsOn(_ViewModel.GetExtensionState(extensionSource)); + Automation::AutomationProperties::SetName(ActiveExtensionsList(), RS_(L"Extensions_ActiveExtensionsHeader/Text")); + Automation::AutomationProperties::SetName(ModifiedProfilesList(), RS_(L"Extensions_ModifiedProfilesHeader/Text")); + Automation::AutomationProperties::SetName(AddedProfilesList(), RS_(L"Extensions_AddedProfilesHeader/Text")); + Automation::AutomationProperties::SetName(AddedColorSchemesList(), RS_(L"Extensions_AddedColorSchemesHeader/Text")); } - void Extensions::ExtensionToggled(const IInspectable& sender, const RoutedEventArgs& /*args*/) + void Extensions::OnNavigatedTo(const NavigationEventArgs& e) { - const auto& toggleSwitch = sender.as(); - const auto& extensionSource = toggleSwitch.Tag().as(); - _ViewModel.SetExtensionState(extensionSource, toggleSwitch.IsOn()); + _ViewModel = e.Parameter().as(); + get_self(_ViewModel)->ExtensionPackageIdentifierTemplateSelector(_extensionPackageIdentifierTemplateSelector); } void Extensions::ExtensionNavigator_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/) { - const auto source = sender.as().Tag().as(); - _ViewModel.CurrentExtensionSource(source); + const auto extPkgVM = sender.as().Tag().as(); + _ViewModel.CurrentExtensionPackage(extPkgVM); } void Extensions::NavigateToProfile_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/) @@ -69,20 +64,18 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) { const auto viewModelProperty{ args.PropertyName() }; - if (viewModelProperty == L"CurrentExtensionSource") + if (viewModelProperty == L"CurrentExtensionPackage") { - // Update the views to reflect the current extension source, if one is selected. + // Update the views to reflect the current extension package, if one is selected. // Otherwise, show components from all extensions _profilesModifiedView.Clear(); _profilesAddedView.Clear(); _colorSchemesAddedView.Clear(); - const auto currentExtensionSource = CurrentExtensionSource(); - for (const auto& ext : _fragmentExtensions) - { - // No extension selected --> 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) + // Helper lambda to add the contents of an extension package to the current view + auto addPackageContentsToView = [&](const Editor::ExtensionPackageViewModel& extPkg) { + auto extPkgVM = get_self(extPkg); + for (const auto& ext : extPkgVM->FragmentExtensions()) { for (const auto& profile : ext.ProfilesModified()) { @@ -97,9 +90,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _colorSchemesAddedView.Append(scheme); } } + }; + + if (const auto currentExtensionPackage = CurrentExtensionPackage()) + { + addPackageContentsToView(currentExtensionPackage); + } + else + { + for (const auto& extPkg : _extensionPackages) + { + addPackageContentsToView(extPkg); + } } - _NotifyChanges(L"IsExtensionView", L"CurrentExtensionFragments"); + _NotifyChanges(L"IsExtensionView", L"CurrentExtensionPackageIdentifierTemplate"); } }); } @@ -108,134 +113,91 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _settings = settings; _colorSchemesPageVM = colorSchemesPageVM; - _extensionSources.clear(); - _CurrentExtensionSource.clear(); + _CurrentExtensionPackage = nullptr; - 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()); + std::vector extensions = wil::to_vector(settings.Extensions()); // these vectors track components all extensions successfully added + std::vector extensionPackages; std::vector profilesModifiedTotal; std::vector profilesAddedTotal; std::vector colorSchemesAddedTotal; - for (const auto& fragExt : extensions) + for (const auto& extPkg : extensions) { - const auto extensionEnabled = GetExtensionState(fragExt.Source()); + auto extPkgVM = winrt::make_self(extPkg, settings); + extensionPackages.push_back(*extPkgVM); + for (const auto& fragExt : extPkg.FragmentsView()) + { + const auto extensionEnabled = GetExtensionState(fragExt.Source(), _settings); - // these vectors track everything the current extension attempted to bring in - std::vector currentProfilesModified; - std::vector currentProfilesAdded; - std::vector currentColorSchemesAdded; + // these vectors track everything the current extension attempted to bring in + std::vector currentProfilesModified; + std::vector currentProfilesAdded; + std::vector currentColorSchemesAdded; - 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())) + 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()) - { - // 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()) - { - 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); + } } } } + extPkgVM->FragmentExtensions().Append(winrt::make(fragExt, currentProfilesModified, currentProfilesAdded, currentColorSchemesAdded)); } - - _extensionSources.insert(fragExt.Source()); - extensionVMs.push_back(winrt::make(fragExt, currentProfilesModified, currentProfilesAdded, currentColorSchemesAdded)); } - _fragmentExtensions = single_threaded_observable_vector(std::move(extensionVMs)); + _extensionPackages = single_threaded_observable_vector(std::move(extensionPackages)); _profilesModifiedView = single_threaded_observable_vector(std::move(profilesModifiedTotal)); _profilesAddedView = single_threaded_observable_vector(std::move(profilesAddedTotal)); _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 (!_CurrentExtensionSource.empty()) - { - 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 + Windows::UI::Xaml::DataTemplate ExtensionsViewModel::CurrentExtensionPackageIdentifierTemplate() const { - std::vector extensionPackages; - for (auto&& extSrc : _extensionSources) - { - extensionPackages.push_back(winrt::make(extSrc, GetExtensionState(extSrc))); - } - return winrt::single_threaded_observable_vector(std::move(extensionPackages)); + return _ExtensionPackageIdentifierTemplateSelector.SelectTemplate(CurrentExtensionPackage()); } // Returns true if the extension is enabled, false otherwise - bool ExtensionsViewModel::GetExtensionState(hstring extensionSource) const + bool ExtensionsViewModel::GetExtensionState(hstring extensionSource, const Model::CascadiaSettings& settings) { - if (const auto& disabledExtensions = _DisabledProfileSources()) + if (const auto& disabledExtensions = settings.GlobalSettings().DisabledProfileSources()) { uint32_t ignored; return !disabledExtensions.IndexOf(extensionSource, ignored); @@ -245,12 +207,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } // Enable/Disable an extension - void ExtensionsViewModel::SetExtensionState(hstring extensionSource, bool enableExt) + void ExtensionsViewModel::SetExtensionState(hstring extensionSource, const Model::CascadiaSettings& settings, bool enableExt) { // get the current status of the extension uint32_t idx; bool currentlyEnabled = true; - const auto& disabledExtensions = _DisabledProfileSources(); + const auto& disabledExtensions = settings.GlobalSettings().DisabledProfileSources(); if (disabledExtensions) { currentlyEnabled = !disabledExtensions.IndexOf(extensionSource, idx); @@ -265,7 +227,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation if (!disabledExtensions && !enableExt) { std::vector disabledProfileSources{ extensionSource }; - _settings.GlobalSettings().DisabledProfileSources(single_threaded_vector(std::move(disabledProfileSources))); + settings.GlobalSettings().DisabledProfileSources(single_threaded_vector(std::move(disabledProfileSources))); return; } @@ -292,12 +254,74 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation NavigateToColorSchemeRequested.raise(*this, nullptr); } + hstring ExtensionPackageViewModel::Scope() const noexcept + { + return _package.Scope() == Model::FragmentScope::User ? RS_(L"Extensions_ScopeUser") : RS_(L"Extensions_ScopeSystem"); + } + + bool ExtensionPackageViewModel::Enabled() const + { + return ExtensionsViewModel::GetExtensionState(_package.Source(), _settings); + } + + void ExtensionPackageViewModel::Enabled(bool val) + { + if (Enabled() != val) + { + ExtensionsViewModel::SetExtensionState(_package.Source(), _settings, val); + _NotifyChanges(L"Enabled"); + } + } + + // Returns the accessible name for the extension package in the following format: + // ", " hstring ExtensionPackageViewModel::AccessibleName() const noexcept { - if (_enabled) + hstring name; + const auto source = _package.Source(); + if (const auto displayName = _package.DisplayName(); !displayName.empty()) { - return _source; + return hstring{ fmt::format(FMT_COMPILE(L"{}, {}"), displayName, source) }; + } + return source; + } + + // Returns the accessible name for the extension package with the disabled state (if disabled) in the following format: + // ", : " + hstring ExtensionPackageViewModel::AccessibleNameWithStatus() const noexcept + { + if (Enabled()) + { + return AccessibleName(); + } + return hstring{ fmt::format(FMT_COMPILE(L"{}: {}"), AccessibleName(), RS_(L"Extension_StateDisabled/Text")) }; + } + + hstring FragmentProfileViewModel::AccessibleName() const noexcept + { + return hstring{ fmt::format(FMT_COMPILE(L"{}, {}"), Profile().Name(), SourceName()) }; + } + + hstring FragmentColorSchemeViewModel::AccessibleName() const noexcept + { + return hstring{ fmt::format(FMT_COMPILE(L"{}, {}"), ColorSchemeVM().Name(), SourceName()) }; + } + + DataTemplate ExtensionPackageTemplateSelector::SelectTemplateCore(const IInspectable& item, const DependencyObject& /*container*/) + { + return SelectTemplateCore(item); + } + + DataTemplate ExtensionPackageTemplateSelector::SelectTemplateCore(const IInspectable& item) + { + if (const auto extPkgVM = item.try_as()) + { + if (!extPkgVM.Package().DisplayName().empty()) + { + return ComplexTemplate(); + } + return DefaultTemplate(); } - return hstring{ fmt::format(L"{}: {}", _source, RS_(L"Extension_StateDisabled/Text")) }; + return nullptr; } } diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.h b/src/cascadia/TerminalSettingsEditor/Extensions.h index 8bd9dfaf911..799618c2b08 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.h +++ b/src/cascadia/TerminalSettingsEditor/Extensions.h @@ -9,6 +9,7 @@ #include "FragmentExtensionViewModel.g.h" #include "FragmentProfileViewModel.g.h" #include "FragmentColorSchemeViewModel.g.h" +#include "ExtensionPackageTemplateSelector.g.h" #include "ViewModelHelpers.h" #include "Utils.h" @@ -20,14 +21,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation 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); 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); WINRT_PROPERTY(Editor::ExtensionsViewModel, ViewModel, nullptr); + + private: + Editor::ExtensionPackageTemplateSelector _extensionPackageIdentifierTemplateSelector; }; struct ExtensionsViewModel : ExtensionsViewModelT, ViewModelHelper @@ -36,57 +38,62 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation ExtensionsViewModel(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM); // Properties - 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; } + Windows::UI::Xaml::DataTemplate CurrentExtensionPackageIdentifierTemplate() const; + bool IsExtensionView() const noexcept { return _CurrentExtensionPackage != nullptr; } + bool NoExtensionPackages() const noexcept { return _extensionPackages.Size() == 0; } bool NoProfilesModified() const noexcept { return _profilesModifiedView.Size() == 0; } bool NoProfilesAdded() const noexcept { return _profilesAddedView.Size() == 0; } bool NoSchemesAdded() const noexcept { return _colorSchemesAddedView.Size() == 0; } // Views - Windows::Foundation::Collections::IObservableVector ExtensionPackages() const noexcept; + Windows::Foundation::Collections::IObservableVector ExtensionPackages() const noexcept { return _extensionPackages; } 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; } // Methods void UpdateSettings(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM); - bool GetExtensionState(hstring extensionSource) const; - void SetExtensionState(hstring extensionSource, bool enableExt); void NavigateToProfile(const guid profileGuid); void NavigateToColorScheme(const Editor::ColorSchemeViewModel& schemeVM); + static bool GetExtensionState(hstring extensionSource, const Model::CascadiaSettings& settings); + static void SetExtensionState(hstring extensionSource, const Model::CascadiaSettings& settings, bool enableExt); + til::typed_event NavigateToProfileRequested; til::typed_event NavigateToColorSchemeRequested; - VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, CurrentExtensionSource); + VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::ExtensionPackageViewModel, CurrentExtensionPackage, nullptr); + WINRT_PROPERTY(Editor::ExtensionPackageTemplateSelector, ExtensionPackageIdentifierTemplateSelector, nullptr); private: Model::CascadiaSettings _settings; Editor::ColorSchemesPageViewModel _colorSchemesPageVM; - std::unordered_set _extensionSources; - Windows::Foundation::Collections::IVector _fragmentExtensions; + Windows::Foundation::Collections::IObservableVector _extensionPackages; Windows::Foundation::Collections::IObservableVector _profilesModifiedView; Windows::Foundation::Collections::IObservableVector _profilesAddedView; Windows::Foundation::Collections::IObservableVector _colorSchemesAddedView; - - 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; } + ExtensionPackageViewModel(const Model::ExtensionPackage& pkg, const Model::CascadiaSettings& settings) : + _package{ pkg }, + _settings{ settings }, + _fragmentExtensions{ single_threaded_observable_vector() } {} + + Model::ExtensionPackage Package() const noexcept { return _package; } + hstring Scope() const noexcept; + bool Enabled() const; + void Enabled(bool val); hstring AccessibleName() const noexcept; + hstring AccessibleNameWithStatus() const noexcept; + Windows::Foundation::Collections::IObservableVector FragmentExtensions() { return _fragmentExtensions; } private: - hstring _source; - bool _enabled; + Model::ExtensionPackage _package; + Model::CascadiaSettings _settings; + Windows::Foundation::Collections::IObservableVector _fragmentExtensions; }; struct FragmentExtensionViewModel : FragmentExtensionViewModelT, ViewModelHelper @@ -124,6 +131,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Model::Profile Profile() const { return _deducedProfile; }; hstring SourceName() const { return _fragment.Source(); } hstring Json() const { return _entry.Json(); } + hstring AccessibleName() const noexcept; private: Model::FragmentProfileEntry _entry; @@ -142,15 +150,29 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Editor::ColorSchemeViewModel ColorSchemeVM() const { return _deducedSchemeVM; }; hstring SourceName() const { return _fragment.Source(); } hstring Json() const { return _entry.Json(); } + hstring AccessibleName() const noexcept; private: Model::FragmentColorSchemeEntry _entry; Model::FragmentSettings _fragment; Editor::ColorSchemeViewModel _deducedSchemeVM; }; + + struct ExtensionPackageTemplateSelector : public ExtensionPackageTemplateSelectorT + { + public: + ExtensionPackageTemplateSelector() = default; + + Windows::UI::Xaml::DataTemplate SelectTemplateCore(const Windows::Foundation::IInspectable& item, const Windows::UI::Xaml::DependencyObject& container); + Windows::UI::Xaml::DataTemplate SelectTemplateCore(const Windows::Foundation::IInspectable& item); + + WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, DefaultTemplate, nullptr); + WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, ComplexTemplate, nullptr); + }; }; namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation { BASIC_FACTORY(Extensions); + BASIC_FACTORY(ExtensionPackageTemplateSelector); } diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.idl b/src/cascadia/TerminalSettingsEditor/Extensions.idl index ba5cac9885f..ffea5d0f073 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.idl +++ b/src/cascadia/TerminalSettingsEditor/Extensions.idl @@ -14,25 +14,22 @@ namespace Microsoft.Terminal.Settings.Editor [default_interface] runtimeclass ExtensionsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged { // Properties - String CurrentExtensionSource; - IVector CurrentExtensionFragments { get; }; - String CurrentExtensionScope { get; }; + ExtensionPackageViewModel CurrentExtensionPackage; + Windows.UI.Xaml.DataTemplate CurrentExtensionPackageIdentifierTemplate { get; }; Boolean IsExtensionView { get; }; - Boolean NoActiveExtensions { get; }; + Boolean NoExtensionPackages { get; }; Boolean NoProfilesModified { get; }; Boolean NoProfilesAdded { get; }; Boolean NoSchemesAdded { get; }; // Views - IObservableVector ExtensionPackages { get; }; + IVector ExtensionPackages { get; }; IObservableVector ProfilesModified { get; }; IObservableVector ProfilesAdded { get; }; IObservableVector ColorSchemesAdded { get; }; // Methods void UpdateSettings(Microsoft.Terminal.Settings.Model.CascadiaSettings settings, ColorSchemesPageViewModel colorSchemesPageVM); - Boolean GetExtensionState(String extensionSource); - void SetExtensionState(String extensionSource, Boolean enableExt); event Windows.Foundation.TypedEventHandler NavigateToProfileRequested; event Windows.Foundation.TypedEventHandler NavigateToColorSchemeRequested; @@ -40,9 +37,12 @@ namespace Microsoft.Terminal.Settings.Editor [default_interface] runtimeclass ExtensionPackageViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged { - String Source { get; }; - Boolean Enabled { get; }; + Microsoft.Terminal.Settings.Model.ExtensionPackage Package { get; }; + Boolean Enabled; + String Scope { get; }; String AccessibleName { get; }; + String AccessibleNameWithStatus { get; }; + IVector FragmentExtensions { get; }; } [default_interface] runtimeclass FragmentExtensionViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged @@ -58,6 +58,7 @@ namespace Microsoft.Terminal.Settings.Editor Microsoft.Terminal.Settings.Model.Profile Profile { get; }; String SourceName { get; }; String Json { get; }; + String AccessibleName { get; }; } [default_interface] runtimeclass FragmentColorSchemeViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged @@ -65,5 +66,14 @@ namespace Microsoft.Terminal.Settings.Editor ColorSchemeViewModel ColorSchemeVM { get; }; String SourceName { get; }; String Json { get; }; + String AccessibleName { get; }; + } + + [default_interface] runtimeclass ExtensionPackageTemplateSelector : Windows.UI.Xaml.Controls.DataTemplateSelector + { + ExtensionPackageTemplateSelector(); + + Windows.UI.Xaml.DataTemplate DefaultTemplate; + Windows.UI.Xaml.DataTemplate ComplexTemplate; } } diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.xaml b/src/cascadia/TerminalSettingsEditor/Extensions.xaml index 84c191bde3b..b7a1296f567 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Extensions.xaml @@ -33,28 +33,99 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + - - + + + + + - - + @@ -246,29 +335,41 @@ - - - - - - - - + + + + + + + + + + + + + + + + + - - + + @@ -291,10 +392,12 @@ Style="{StaticResource TextBlockSubHeaderStyle}" /> - + Visibility="{x:Bind ViewModel.NoExtensionPackages, Mode=OneWay}" /> + @@ -303,29 +406,49 @@ Margin="0,-20,0,0" Visibility="{x:Bind ViewModel.IsExtensionView, Mode=OneWay}"> - - - - - + + + + + + + + + + + + + + + + ItemsSource="{x:Bind ViewModel.CurrentExtensionPackage.FragmentExtensions, Mode=OneWay}" /> - - + + @@ -335,7 +458,8 @@ - @@ -347,7 +471,8 @@ - @@ -359,7 +484,8 @@ - diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 8fc9817c3a8..1a95e90346e 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -119,17 +119,19 @@ 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"CurrentExtensionSource") + if (settingName == L"CurrentExtensionPackage") { - if (const auto& currentExtensionSource = _extensionsVM.CurrentExtensionSource(); currentExtensionSource != hstring{}) + if (const auto& currentExtensionPackage = _extensionsVM.CurrentExtensionPackage()) { - const auto crumb = winrt::make(box_value(currentExtensionSource), currentExtensionSource, BreadcrumbSubPage::Extensions_Extension); + const auto& pkg = currentExtensionPackage.Package(); + const auto label = pkg.DisplayName().empty() ? pkg.Source() : pkg.DisplayName(); + const auto crumb = winrt::make(box_value(currentExtensionPackage), label, BreadcrumbSubPage::Extensions_Extension); _breadcrumbs.Append(crumb); SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0); } else { - // If we don't have a current extension source, we're at the root of the Extensions page + // If we don't have a current extension package, 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,10 +208,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { if (const auto& breadcrumbStringTag{ crumb->Tag().try_as() }) { - if (stringTag == breadcrumbStringTag || (crumb->SubPage() == BreadcrumbSubPage::Extensions_Extension && stringTag == extensionsTag)) + if (stringTag == breadcrumbStringTag) { // 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(*breadcrumbStringTag, crumb->SubPage()); return; @@ -226,6 +227,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return; } } + else if (const auto& breadcrumbExtensionPackage{ crumb->Tag().try_as() }) + { + if (stringTag == extensionsTag) + { + // navigate to the NewTabMenu page, + // _Navigate() will handle trying to find the right subpage + SettingsNav().SelectedItem(item); + _Navigate(breadcrumbExtensionPackage, BreadcrumbSubPage::NewTabMenu_Folder); + return; + } + } } else if (const auto& profileTag{ tag.try_as() }) { @@ -474,39 +486,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _breadcrumbs.Append(crumb); } } - else if (clickedItemTag == extensionsTag || subPage == BreadcrumbSubPage::Extensions_Extension) + else if (clickedItemTag == extensionsTag) { - 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{}) + if (_extensionsVM.CurrentExtensionPackage()) { - // Setting CurrentExtensionSource triggers the PropertyChanged event, + // Setting CurrentExtensionPackage triggers the PropertyChanged event, // which will navigate to the correct page and update the breadcrumbs appropriately - _extensionsVM.CurrentExtensionSource(hstring{}); + _extensionsVM.CurrentExtensionPackage(nullptr); } else { @@ -645,6 +631,40 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } + void MainPage::_Navigate(const Editor::ExtensionPackageViewModel& extPkgVM, 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.CurrentExtensionPackage(nullptr); + } + else + { + bool found = false; + for (const auto& pkgVM : _extensionsVM.ExtensionPackages()) + { + if (pkgVM.Package().Source() == extPkgVM.Package().Source()) + { + // Take advantage of the PropertyChanged event to navigate + // to the correct extension package and build the breadcrumbs as we go + _extensionsVM.CurrentExtensionPackage(pkgVM); + found = true; + break; + } + } + if (!found) + { + // If we couldn't find a reasonable match, just go back to the root + _extensionsVM.CurrentExtensionPackage(nullptr); + } + } + } + void MainPage::OpenJsonTapped(const IInspectable& /*sender*/, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& /*args*/) { const auto window = CoreWindow::GetForCurrentThread(); @@ -691,6 +711,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _Navigate(*ntmEntryViewModel, subPage); } + else if (const auto extPkgViewModel = tag.try_as()) + { + _Navigate(*extPkgViewModel, subPage); + } else { _Navigate(tag.as(), subPage); diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.h b/src/cascadia/TerminalSettingsEditor/MainPage.h index 997237bbe04..1da226c0436 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::ExtensionPackageViewModel& extPkgVM, BreadcrumbSubPage subPage); void _NavigateToProfileHandler(const IInspectable& sender, winrt::guid profileGuid); void _NavigateToColorSchemeHandler(const IInspectable& sender, const IInspectable& args); diff --git a/src/cascadia/TerminalSettingsEditor/NewTabMenu.cpp b/src/cascadia/TerminalSettingsEditor/NewTabMenu.cpp index 901b809f34b..25b7f8ef77d 100644 --- a/src/cascadia/TerminalSettingsEditor/NewTabMenu.cpp +++ b/src/cascadia/TerminalSettingsEditor/NewTabMenu.cpp @@ -22,8 +22,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { InitializeComponent(); - _entryTemplateSelector = Resources().Lookup(box_value(L"NewTabMenuEntryTemplateSelector")).as(); - // Ideally, we'd bind IsEnabled to something like mtu:Converters.isEmpty(NewTabMenuListView.SelectedItems.Size) in the XAML, // but the XAML compiler can't find NewTabMenuListView when we try that. Rather than copying the list of selected items over // to the view model, we'll just do this instead (much simpler). diff --git a/src/cascadia/TerminalSettingsEditor/NewTabMenu.h b/src/cascadia/TerminalSettingsEditor/NewTabMenu.h index cb54fd11b9f..f305c44c885 100644 --- a/src/cascadia/TerminalSettingsEditor/NewTabMenu.h +++ b/src/cascadia/TerminalSettingsEditor/NewTabMenu.h @@ -42,7 +42,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation WINRT_OBSERVABLE_PROPERTY(Editor::NewTabMenuViewModel, ViewModel, _PropertyChangedHandlers, nullptr); private: - Editor::NewTabMenuEntryTemplateSelector _entryTemplateSelector{ nullptr }; Editor::NewTabMenuEntryViewModel _draggedEntry{ nullptr }; void _ScrollToEntry(const Editor::NewTabMenuEntryViewModel& entry); diff --git a/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml b/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml index b5045dd43a2..bf998e60ebf 100644 --- a/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml +++ b/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml @@ -179,13 +179,18 @@ + + - diff --git a/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.cpp b/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.cpp index 948b1822f8c..9401c0137d3 100644 --- a/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.cpp @@ -8,6 +8,7 @@ #include "../../inc/DefaultSettings.h" #include "DynamicProfileUtils.h" +#include using namespace ::Microsoft::Terminal::Settings::Model; using namespace winrt::Microsoft::Terminal::Settings::Model; @@ -18,6 +19,11 @@ std::wstring_view AzureCloudShellGenerator::GetNamespace() const noexcept return AzureGeneratorNamespace; } +std::wstring_view AzureCloudShellGenerator::GetDisplayName() const noexcept +{ + return RS_(L"AzureCloudShellGeneratorDisplayName"); +} + // Method Description: // - Checks if the Azure Cloud shell is available on this platform, and if it // is, creates a profile to be able to launch it. diff --git a/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.h b/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.h index f62b66fce74..2f61624bc6f 100644 --- a/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.h +++ b/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.h @@ -25,6 +25,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model { public: std::wstring_view GetNamespace() const noexcept override; + std::wstring_view GetDisplayName() const noexcept override; + std::wstring_view GetIcon() const noexcept override { return {}; }; void GenerateProfiles(std::vector>& profiles) const override; }; }; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index c7a5f8d2928..97b652a5aaa 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -114,26 +114,15 @@ Model::CascadiaSettings CascadiaSettings::Copy() const settings->_allProfiles = winrt::single_threaded_observable_vector(std::move(allProfiles)); settings->_activeProfiles = winrt::single_threaded_observable_vector(std::move(activeProfiles)); - // copy fragment extensions + // copy extension packages { - std::vector fragmentExtensions; - fragmentExtensions.reserve(_fragmentExtensions.Size()); - for (const auto& fragment : _fragmentExtensions) + std::vector extensions; + extensions.reserve(_extensionPackages.Size()); + for (const auto& extension : _extensionPackages) { - fragmentExtensions.emplace_back(get_self(fragment)->Copy()); + extensions.emplace_back(get_self(extension)->Copy()); } - 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)); + settings->_extensionPackages = winrt::single_threaded_vector(std::move(extensions)); } } @@ -155,7 +144,7 @@ Model::CascadiaSettings CascadiaSettings::Copy() const Model::FragmentSettings FragmentSettings::Copy() const { - auto fragment{ winrt::make_self(_source, _json, _jsonSource, _scope) }; + auto fragment{ winrt::make_self(_source, _json, _jsonSource) }; std::vector modifiedProfiles; modifiedProfiles.reserve(_modifiedProfiles.Size()); @@ -184,6 +173,20 @@ Model::FragmentSettings FragmentSettings::Copy() const return *fragment; } +Model::ExtensionPackage ExtensionPackage::Copy() const +{ + auto extPkg{ winrt::make_self(_source, _scope) }; + extPkg->Icon(_Icon); + extPkg->DisplayName(_DisplayName); + + for (const auto& frag : _fragments) + { + extPkg->Fragments().Append(get_self(frag)->Copy()); + } + + return *extPkg; +} + // Method Description: // - Finds a profile that matches the given GUID. If there is no profile in this // settings object that matches, returns nullptr. @@ -226,14 +229,9 @@ IObservableVector CascadiaSettings::ActiveProfiles() const noexc return _activeProfiles; } -IVectorView CascadiaSettings::FragmentExtensions() const noexcept -{ - return _fragmentExtensions.GetView(); -} - -IVectorView CascadiaSettings::DynamicProfileGenerators() const noexcept +IVectorView CascadiaSettings::Extensions() const noexcept { - return _dynamicProfileGeneratorExtensions.GetView(); + return _extensionPackages.GetView(); } // Method Description: diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index 22238092bb7..88a3133f96f 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -21,6 +21,7 @@ Author(s): #include "FragmentSettings.g.h" #include "FragmentProfileEntry.g.h" #include "FragmentColorSchemeEntry.g.h" +#include "ExtensionPackage.g.h" #include "GlobalAppSettings.h" #include "Profile.h" @@ -42,6 +43,30 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::runtime_error(message) {} }; + struct ExtensionPackage : ExtensionPackageT + { + public: + ExtensionPackage(hstring source, FragmentScope scope) : + _source{ source }, + _scope{ scope }, + _fragments{ winrt::single_threaded_vector() } {} + + Model::ExtensionPackage Copy() const; + + hstring Source() const noexcept { return _source; } + FragmentScope Scope() const noexcept { return _scope; } + Windows::Foundation::Collections::IVectorView FragmentsView() const noexcept { return _fragments.GetView(); } + Windows::Foundation::Collections::IVector Fragments() const noexcept { return _fragments; } + + WINRT_PROPERTY(hstring, Icon); + WINRT_PROPERTY(hstring, DisplayName); + + private: + hstring _source; + FragmentScope _scope; + Windows::Foundation::Collections::IVector _fragments; + }; + struct ParsedSettings { winrt::com_ptr globals; @@ -73,8 +98,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation ParsedSettings inboxSettings; ParsedSettings userSettings; - std::vector fragmentExtensions; - std::vector dynamicProfileGeneratorExtensions; + std::unordered_map> extensionPackageMap; bool duplicateProfile = false; private: @@ -100,6 +124,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void _addUserProfileParent(const winrt::com_ptr& profile); bool _addOrMergeUserColorScheme(const winrt::com_ptr& colorScheme); void _executeGenerator(const IDynamicProfileGenerator& generator); + winrt::com_ptr _registerFragment(const winrt::Microsoft::Terminal::Settings::Model::FragmentSettings& fragment, FragmentScope scope); std::unordered_set _ignoredNamespaces; std::set themesChangeLog; @@ -132,8 +157,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::Windows::Foundation::Collections::IObservableVector AllProfiles() const noexcept; 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; + winrt::Windows::Foundation::Collections::IVectorView Extensions() const noexcept; void WriteSettingsToDisk(); Json::Value ToJson() const; Model::Profile ProfileDefaults() const; @@ -191,8 +215,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 _dynamicProfileGeneratorExtensions = winrt::single_threaded_vector(); + winrt::Windows::Foundation::Collections::IVector _extensionPackages = winrt::single_threaded_vector(); std::set _themesChangeLog{}; // load errors @@ -243,11 +266,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation struct FragmentSettings : FragmentSettingsT { public: - FragmentSettings(hstring source, hstring json, hstring jsonSource, FragmentScope scope) : + FragmentSettings(hstring source, hstring json, hstring jsonSource) : _source{ source }, _json{ json }, _jsonSource{ jsonSource }, - _scope{ scope }, _modifiedProfiles{ winrt::single_threaded_vector() }, _newProfiles{ winrt::single_threaded_vector() }, _colorSchemes{ winrt::single_threaded_vector() } {} @@ -255,7 +277,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Model::FragmentSettings Copy() const; 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; } @@ -271,7 +292,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation hstring _source; hstring _json; hstring _jsonSource; - FragmentScope _scope; Windows::Foundation::Collections::IVector _modifiedProfiles; Windows::Foundation::Collections::IVector _newProfiles; Windows::Foundation::Collections::IVector _colorSchemes; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl index 5671ed21d42..2869b785e41 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl @@ -44,8 +44,7 @@ namespace Microsoft.Terminal.Settings.Model Profile DuplicateProfile(Profile sourceProfile); ActionMap ActionMap { get; }; - Windows.Foundation.Collections.IVectorView FragmentExtensions { get; }; - Windows.Foundation.Collections.IVectorView DynamicProfileGenerators { get; }; + Windows.Foundation.Collections.IVectorView Extensions { get; }; IVectorView Warnings { get; }; Windows.Foundation.IReference GetLoadingError { get; }; @@ -81,11 +80,19 @@ namespace Microsoft.Terminal.Settings.Model [default_interface] runtimeclass FragmentSettings { 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; }; + IVectorView ModifiedProfilesView { get; }; + IVectorView NewProfilesView { get; }; + IVectorView ColorSchemesView { get; }; + } + + [default_interface] runtimeclass ExtensionPackage + { + String Source { get; }; + String DisplayName { get; }; + String Icon { get; }; + FragmentScope Scope { get; }; + IVectorView FragmentsView { get; }; } } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 9ec727253a8..2a2d9af5c09 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -315,7 +315,8 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings() for (const auto& ext : extensions) { - const auto packageName = ext.Package().Id().FamilyName(); + const auto& package = ext.Package(); + const auto packageName = package.Id().FamilyName(); // Likewise, getting the public folder from an extension is an async operation. auto foundFolder = extractValueFromTaskWithoutMainThreadAwait(ext.GetPublicFolderAsync()); @@ -339,6 +340,10 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings() packageName, FragmentScope::User, !_ignoredNamespaces.contains(std::wstring_view{ packageName })); // layerFragment + + auto extPkg = extensionPackageMap[packageName]; + extPkg->Icon(package.Logo().AbsoluteUri()); + extPkg->DisplayName(package.DisplayName()); } } } @@ -738,7 +743,7 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str Json::StreamWriterBuilder styledWriter; styledWriter["indentation"] = " "; styledWriter["commentStyle"] = "All"; - auto fragmentSettings = winrt::make_self(source, hstring{ til::u8u16(Json::writeString(styledWriter, json.root)) }, hstring{ jsonFilename }, scope); + auto fragmentSettings = winrt::make_self(source, hstring{ til::u8u16(Json::writeString(styledWriter, json.root)) }, hstring{ jsonFilename }); settings.clear(); @@ -851,7 +856,7 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str // the fragments being applied before the user's own settings. userSettings.globals->AddLeastImportantParent(settings.globals); } - fragmentExtensions.emplace_back(std::move(*fragmentSettings)); + _registerFragment(std::move(*fragmentSettings), scope); } SettingsLoader::JsonSettings SettingsLoader::_parseJson(const std::string_view& content) @@ -1030,12 +1035,31 @@ void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator 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); + auto generatorExtension = winrt::make_self(hstring{ generatorNamespace }, hstring{ til::u8u16(Json::writeString(styledWriter, json)) }, hstring{ L"settings.json" }); for (const auto& entry : profileEntries) { generatorExtension->NewProfiles().Append(entry); } - dynamicProfileGeneratorExtensions.emplace_back(*generatorExtension); + auto extPkg = _registerFragment(std::move(*generatorExtension), FragmentScope::Machine); + extPkg->DisplayName(hstring{ generator.GetDisplayName() }); + extPkg->Icon(hstring{ generator.GetIcon() }); +} + +winrt::com_ptr SettingsLoader::_registerFragment(const winrt::Microsoft::Terminal::Settings::Model::FragmentSettings& fragment, FragmentScope scope) +{ + const auto src = fragment.Source(); + if (auto extPkg = extensionPackageMap[src]) + { + extPkg->Fragments().Append(fragment); + return extPkg; + } + else + { + auto newExtPkg = winrt::make_self(src, scope); + newExtPkg->Fragments().Append(fragment); + extensionPackageMap[src] = newExtPkg; + return newExtPkg; + } } // Method Description: @@ -1327,8 +1351,11 @@ CascadiaSettings::CascadiaSettings(SettingsLoader&& loader) : _activeProfiles = winrt::single_threaded_observable_vector(std::move(activeProfiles)); _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)); + + for (auto [_, extPkg] : loader.extensionPackageMap) + { + _extensionPackages.Append(extPkg->Copy()); + } _resolveDefaultProfile(); _resolveNewTabMenuProfiles(); diff --git a/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h b/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h index db57944b104..9144bcb3259 100644 --- a/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h +++ b/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h @@ -30,6 +30,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model public: virtual ~IDynamicProfileGenerator() = default; virtual std::wstring_view GetNamespace() const noexcept = 0; + virtual std::wstring_view GetDisplayName() const noexcept = 0; + virtual std::wstring_view GetIcon() const noexcept = 0; virtual void GenerateProfiles(std::vector>& profiles) const = 0; }; }; diff --git a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp index 14878c8278f..196dc7b36a0 100644 --- a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp @@ -15,6 +15,7 @@ #include #include #include +#include static constexpr std::wstring_view POWERSHELL_PFN{ L"Microsoft.PowerShell_8wekyb3d8bbwe" }; static constexpr std::wstring_view POWERSHELL_PREVIEW_PFN{ L"Microsoft.PowerShellPreview_8wekyb3d8bbwe" }; @@ -294,6 +295,16 @@ std::wstring_view PowershellCoreProfileGenerator::GetNamespace() const noexcept return PowershellCoreGeneratorNamespace; } +std::wstring_view PowershellCoreProfileGenerator::GetDisplayName() const noexcept +{ + return RS_(L"PowershellCoreProfileGeneratorDisplayName"); +} + +std::wstring_view PowershellCoreProfileGenerator::GetIcon() const noexcept +{ + return POWERSHELL_ICON; +} + // Method Description: // - Checks if pwsh is installed, and if it is, creates a profile to launch it. // Arguments: diff --git a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h index eaec9dacea9..d473292e151 100644 --- a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h +++ b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h @@ -26,6 +26,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model static const std::wstring_view GetPreferredPowershellProfileName(); std::wstring_view GetNamespace() const noexcept override; + std::wstring_view GetDisplayName() const noexcept override; + std::wstring_view GetIcon() const noexcept override; void GenerateProfiles(std::vector>& profiles) const override; }; }; diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index b1c24a54d0f..37d9b2629b4 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -740,4 +740,24 @@ Open current working directory - + + WSL Distro Profile Generator + The display name of a dynamic profile generator for WSL distros + + + PowerShell Profile Generator + The display name of a dynamic profile generator for PowerShell + + + Azure Cloud Shell Profile Generator + The display name of a dynamic profile generator for Azure Cloud Shell + + + Visual Studio Profile Generator + The display name of a dynamic profile generator for Visual Studio + + + SSH Host Profile Generator + The display name of a dynamic profile generator for SSH hosts + + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp b/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp index 30dc19039f0..2bd52789959 100644 --- a/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp @@ -7,6 +7,7 @@ #include "../../inc/DefaultSettings.h" #include "DynamicProfileUtils.h" +#include static constexpr std::wstring_view SshHostGeneratorNamespace{ L"Windows.Terminal.SSH" }; @@ -132,6 +133,16 @@ std::wstring_view SshHostGenerator::GetNamespace() const noexcept return SshHostGeneratorNamespace; } +std::wstring_view SshHostGenerator::GetDisplayName() const noexcept +{ + return RS_(L"SshHostGeneratorDisplayName"); +} + +std::wstring_view SshHostGenerator::GetIcon() const noexcept +{ + return _getProfileIconPath(); +} + // Method Description: // - Generate a list of profiles for each detected OpenSSH host. // Arguments: diff --git a/src/cascadia/TerminalSettingsModel/SshHostGenerator.h b/src/cascadia/TerminalSettingsModel/SshHostGenerator.h index a355b0c4302..d83e62535ba 100644 --- a/src/cascadia/TerminalSettingsModel/SshHostGenerator.h +++ b/src/cascadia/TerminalSettingsModel/SshHostGenerator.h @@ -24,6 +24,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model { public: std::wstring_view GetNamespace() const noexcept override; + std::wstring_view GetDisplayName() const noexcept override; + std::wstring_view GetIcon() const noexcept override; void GenerateProfiles(std::vector>& profiles) const override; private: diff --git a/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.cpp b/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.cpp index 2fcfb1cc528..ad09059f9e9 100644 --- a/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.cpp @@ -6,6 +6,7 @@ #include "VisualStudioGenerator.h" #include "VsDevCmdGenerator.h" #include "VsDevShellGenerator.h" +#include using namespace winrt::Microsoft::Terminal::Settings::Model; @@ -16,6 +17,11 @@ std::wstring_view VisualStudioGenerator::GetNamespace() const noexcept return Namespace; } +std::wstring_view VisualStudioGenerator::GetDisplayName() const noexcept +{ + return RS_(L"VisualStudioGeneratorDisplayName"); +} + void VisualStudioGenerator::GenerateProfiles(std::vector>& profiles) const { const auto instances = VsSetupConfiguration::QueryInstances(); diff --git a/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.h b/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.h index ef36284db74..2f5c2274c6c 100644 --- a/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.h +++ b/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.h @@ -28,6 +28,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model public: static std::wstring_view Namespace; std::wstring_view GetNamespace() const noexcept override; + std::wstring_view GetDisplayName() const noexcept override; + std::wstring_view GetIcon() const noexcept override { return {}; }; void GenerateProfiles(std::vector>& profiles) const override; class IVisualStudioProfileGenerator diff --git a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp index cb1d220a904..f571b08f668 100644 --- a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp @@ -8,10 +8,12 @@ #include "../../inc/DefaultSettings.h" #include "DynamicProfileUtils.h" +#include static constexpr std::wstring_view WslHomeDirectory{ L"~" }; static constexpr std::wstring_view DockerDistributionPrefix{ L"docker-desktop" }; static constexpr std::wstring_view RancherDistributionPrefix{ L"rancher-desktop" }; +static constexpr std::wstring_view IconPath{ L"ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png" }; // The WSL entries are structured as such: // HKCU\Software\Microsoft\Windows\CurrentVersion\Lxss @@ -47,6 +49,16 @@ std::wstring_view WslDistroGenerator::GetNamespace() const noexcept return WslGeneratorNamespace; } +std::wstring_view WslDistroGenerator::GetDisplayName() const noexcept +{ + return RS_(L"WslDistroGeneratorDisplayName"); +} + +std::wstring_view WslDistroGenerator::GetIcon() const noexcept +{ + return IconPath; +} + static winrt::com_ptr makeProfile(const std::wstring& distName) { const auto WSLDistro{ CreateDynamicProfile(distName) }; @@ -65,7 +77,7 @@ static winrt::com_ptr makeProfile(const std::wstring& d { WSLDistro->StartingDirectory(winrt::hstring{ DEFAULT_STARTING_DIRECTORY }); } - WSLDistro->Icon(L"ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png"); + WSLDistro->Icon(winrt::hstring{ IconPath }); WSLDistro->PathTranslationStyle(winrt::Microsoft::Terminal::Control::PathTranslationStyle::WSL); return WSLDistro; } diff --git a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h index b46aac82038..123734523fa 100644 --- a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h +++ b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h @@ -24,6 +24,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model { public: std::wstring_view GetNamespace() const noexcept override; + std::wstring_view GetDisplayName() const noexcept override; + std::wstring_view GetIcon() const noexcept override; void GenerateProfiles(std::vector>& profiles) const override; }; };