diff --git a/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/AzureCloudShell.png b/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/AzureCloudShell.png
new file mode 100644
index 0000000000..c7ee454ce9
Binary files /dev/null and b/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/AzureCloudShell.png differ
diff --git a/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/PowerShell.png b/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/PowerShell.png
new file mode 100644
index 0000000000..72f5e54e31
Binary files /dev/null and b/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/PowerShell.png differ
diff --git a/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/SSH.png b/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/SSH.png
new file mode 100644
index 0000000000..ebba22951f
Binary files /dev/null and b/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/SSH.png differ
diff --git a/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/VisualStudio.png b/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/VisualStudio.png
new file mode 100644
index 0000000000..c41b6cb581
Binary files /dev/null and b/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/VisualStudio.png differ
diff --git a/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/WSL.png b/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/WSL.png
new file mode 100644
index 0000000000..b572346ff5
Binary files /dev/null and b/src/cascadia/CascadiaPackage/ProfileGeneratorIcons/WSL.png differ
diff --git a/src/cascadia/CascadiaResources.build.items b/src/cascadia/CascadiaResources.build.items
index cfd11c79f4..336fad5048 100644
--- a/src/cascadia/CascadiaResources.build.items
+++ b/src/cascadia/CascadiaResources.build.items
@@ -21,6 +21,11 @@
true
ProfileIcons\%(RecursiveDir)%(FileName)%(Extension)
+
+
+ true
+ ProfileGeneratorIcons\%(RecursiveDir)%(FileName)%(Extension)
+
true
diff --git a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml b/src/cascadia/TerminalSettingsEditor/AddProfile.xaml
index b278f71ae6..3ea92ad0f6 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/CommonResources.xaml b/src/cascadia/TerminalSettingsEditor/CommonResources.xaml
index 7655deb559..8f26f152ba 100644
--- a/src/cascadia/TerminalSettingsEditor/CommonResources.xaml
+++ b/src/cascadia/TerminalSettingsEditor/CommonResources.xaml
@@ -1204,7 +1204,7 @@
-
+
@@ -1227,7 +1227,8 @@
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}" />
-
+
+
diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.cpp b/src/cascadia/TerminalSettingsEditor/Extensions.cpp
new file mode 100644
index 0000000000..ffaff9a33d
--- /dev/null
+++ b/src/cascadia/TerminalSettingsEditor/Extensions.cpp
@@ -0,0 +1,509 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+#include "Extensions.h"
+#include "Extensions.g.cpp"
+#include "ExtensionPackageViewModel.g.cpp"
+#include "ExtensionsViewModel.g.cpp"
+#include "FragmentProfileViewModel.g.cpp"
+#include "ExtensionPackageTemplateSelector.g.cpp"
+
+#include
+#include "..\WinRTUtils\inc\Utils.h"
+
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Windows::Foundation::Collections;
+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
+{
+ static constexpr std::wstring_view ExtensionPageId{ L"page.extensions" };
+
+ Extensions::Extensions()
+ {
+ InitializeComponent();
+
+ _extensionPackageIdentifierTemplateSelector = Resources().Lookup(box_value(L"ExtensionPackageIdentifierTemplateSelector")).as();
+
+ 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::OnNavigatedTo(const NavigationEventArgs& e)
+ {
+ _ViewModel = e.Parameter().as();
+ auto vmImpl = get_self(_ViewModel);
+ vmImpl->ExtensionPackageIdentifierTemplateSelector(_extensionPackageIdentifierTemplateSelector);
+ vmImpl->LazyLoadExtensions();
+ vmImpl->MarkAsVisited();
+ }
+
+ void Extensions::ExtensionNavigator_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/)
+ {
+ const auto extPkgVM = sender.as().Tag().as();
+ _ViewModel.CurrentExtensionPackage(extPkgVM);
+ }
+
+ void Extensions::NavigateToProfile_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/)
+ {
+ const auto& profileGuid = sender.as().Tag().as();
+ get_self(_ViewModel)->NavigateToProfile(profileGuid);
+ }
+
+ void Extensions::NavigateToColorScheme_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/)
+ {
+ const auto& schemeVM = sender.as().Tag().as();
+ get_self(_ViewModel)->NavigateToColorScheme(schemeVM);
+ }
+
+ ExtensionsViewModel::ExtensionsViewModel(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM) :
+ _settings{ settings },
+ _colorSchemesPageVM{ colorSchemesPageVM },
+ _extensionsLoaded{ false }
+ {
+ UpdateSettings(settings, colorSchemesPageVM);
+
+ PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) {
+ const auto viewModelProperty{ args.PropertyName() };
+
+ const bool extensionPackageChanged = viewModelProperty == L"CurrentExtensionPackage";
+ const bool profilesModifiedChanged = viewModelProperty == L"ProfilesModified";
+ const bool profilesAddedChanged = viewModelProperty == L"ProfilesAdded";
+ const bool colorSchemesAddedChanged = viewModelProperty == L"ColorSchemesAdded";
+ if (extensionPackageChanged || (!IsExtensionView() && (profilesModifiedChanged || profilesAddedChanged || colorSchemesAddedChanged)))
+ {
+ // Use these booleans to track which of our observable vectors need to be refreshed.
+ // This prevents a full refresh of the UI when enabling/disabling extensions.
+ // If the CurrentExtensionPackage changed, we want to update all components.
+ // Otherwise, just update the ones that we were notified about.
+ const bool updateProfilesModified = extensionPackageChanged || profilesModifiedChanged;
+ const bool updateProfilesAdded = extensionPackageChanged || profilesAddedChanged;
+ const bool updateColorSchemesAdded = extensionPackageChanged || colorSchemesAddedChanged;
+ _UpdateListViews(updateProfilesModified, updateProfilesAdded, updateColorSchemesAdded);
+
+ if (extensionPackageChanged)
+ {
+ _NotifyChanges(L"IsExtensionView", L"CurrentExtensionPackageIdentifierTemplate");
+ }
+ else if (profilesModifiedChanged)
+ {
+ _NotifyChanges(L"NoProfilesModified");
+ }
+ else if (profilesAddedChanged)
+ {
+ _NotifyChanges(L"NoProfilesAdded");
+ }
+ else if (colorSchemesAddedChanged)
+ {
+ _NotifyChanges(L"NoSchemesAdded");
+ }
+ }
+ });
+ }
+
+ void ExtensionsViewModel::_UpdateListViews(bool updateProfilesModified, bool updateProfilesAdded, bool updateColorSchemesAdded)
+ {
+ // STL vectors to track relevant components for extensions to display in UI
+ std::vector profilesModifiedTotal;
+ std::vector profilesAddedTotal;
+ std::vector colorSchemesAddedTotal;
+
+ // 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())
+ {
+ if (updateProfilesModified)
+ {
+ for (const auto& profile : ext.ProfilesModified())
+ {
+ profilesModifiedTotal.push_back(profile);
+ }
+ }
+ if (updateProfilesAdded)
+ {
+ for (const auto& profile : ext.ProfilesAdded())
+ {
+ profilesAddedTotal.push_back(profile);
+ }
+ }
+ if (updateColorSchemesAdded)
+ {
+ for (const auto& scheme : ext.ColorSchemesAdded())
+ {
+ colorSchemesAddedTotal.push_back(scheme);
+ }
+ }
+ }
+ };
+
+ // Populate the STL vectors that we want to update
+ if (const auto currentExtensionPackage = CurrentExtensionPackage())
+ {
+ // Update all of the views to reflect the current extension package, if one is selected.
+ addPackageContentsToView(currentExtensionPackage);
+ }
+ else
+ {
+ // Only populate the views with components from enabled extensions
+ for (const auto& extPkg : _extensionPackages)
+ {
+ if (extPkg.Enabled())
+ {
+ addPackageContentsToView(extPkg);
+ }
+ }
+ }
+
+ // Sort the lists linguistically for nicer presentation.
+ // Update the WinRT lists bound to UI.
+ if (updateProfilesModified)
+ {
+ std::sort(profilesModifiedTotal.begin(), profilesModifiedTotal.end(), FragmentProfileViewModel::SortAscending);
+ _profilesModifiedView = winrt::single_threaded_observable_vector(std::move(profilesModifiedTotal));
+ }
+ if (updateProfilesAdded)
+ {
+ std::sort(profilesAddedTotal.begin(), profilesAddedTotal.end(), FragmentProfileViewModel::SortAscending);
+ _profilesAddedView = winrt::single_threaded_observable_vector(std::move(profilesAddedTotal));
+ }
+ if (updateColorSchemesAdded)
+ {
+ std::sort(colorSchemesAddedTotal.begin(), colorSchemesAddedTotal.end(), FragmentColorSchemeViewModel::SortAscending);
+ _colorSchemesAddedView = winrt::single_threaded_observable_vector(std::move(colorSchemesAddedTotal));
+ }
+ }
+
+ void ExtensionsViewModel::UpdateSettings(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM)
+ {
+ _settings = settings;
+ _colorSchemesPageVM = colorSchemesPageVM;
+ _CurrentExtensionPackage = nullptr;
+
+ // The extension packages may not be loaded yet because we want to wait until we actually navigate to the page to do so.
+ // In that case, omit "updating" them. They'll get the proper references when we lazy load them.
+ if (_extensionPackages)
+ {
+ for (const auto& extPkg : _extensionPackages)
+ {
+ get_self(extPkg)->UpdateSettings(_settings);
+ }
+ }
+ }
+
+ void ExtensionsViewModel::LazyLoadExtensions()
+ {
+ if (_extensionsLoaded)
+ {
+ return;
+ }
+ 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& extPkg : extensions)
+ {
+ auto extPkgVM = winrt::make_self(extPkg, _settings);
+ 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;
+
+ if (fragExt.ModifiedProfilesView())
+ {
+ 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(entry, fragExt, deducedProfile);
+ currentProfilesModified.push_back(vm);
+ if (extensionEnabled)
+ {
+ profilesModifiedTotal.push_back(vm);
+ }
+ }
+ }
+ }
+
+ if (fragExt.NewProfilesView())
+ {
+ 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(entry, fragExt, deducedProfile);
+ currentProfilesAdded.push_back(vm);
+ if (extensionEnabled)
+ {
+ profilesAddedTotal.push_back(vm);
+ }
+ }
+ }
+ }
+
+ if (fragExt.ColorSchemesView())
+ {
+ for (const auto&& entry : fragExt.ColorSchemesView())
+ {
+ for (const auto& schemeVM : _colorSchemesPageVM.AllColorSchemes())
+ {
+ if (schemeVM.Name() == entry.ColorSchemeName())
+ {
+ auto vm = winrt::make(entry, fragExt, schemeVM);
+ currentColorSchemesAdded.push_back(vm);
+ if (extensionEnabled)
+ {
+ colorSchemesAddedTotal.push_back(vm);
+ }
+ }
+ }
+ }
+ }
+
+ // sort the lists linguistically for nicer presentation
+ std::sort(currentProfilesModified.begin(), currentProfilesModified.end(), FragmentProfileViewModel::SortAscending);
+ std::sort(currentProfilesAdded.begin(), currentProfilesAdded.end(), FragmentProfileViewModel::SortAscending);
+ std::sort(currentColorSchemesAdded.begin(), currentColorSchemesAdded.end(), FragmentColorSchemeViewModel::SortAscending);
+
+ extPkgVM->FragmentExtensions().Append(winrt::make(fragExt, currentProfilesModified, currentProfilesAdded, currentColorSchemesAdded));
+ extPkgVM->PropertyChanged([&](const IInspectable& sender, const PropertyChangedEventArgs& args) {
+ const auto viewModelProperty{ args.PropertyName() };
+ if (viewModelProperty == L"Enabled")
+ {
+ // If the extension was enabled/disabled,
+ // check if any of its fragments modified profiles, added profiles, or added color schemes.
+ // Only notify what was affected!
+ bool hasModifiedProfiles = false;
+ bool hasAddedProfiles = false;
+ bool hasAddedColorSchemes = false;
+ for (const auto& fragExtVM : sender.as()->FragmentExtensions())
+ {
+ const auto profilesModified = fragExtVM.ProfilesModified();
+ const auto profilesAdded = fragExtVM.ProfilesAdded();
+ const auto colorSchemesAdded = fragExtVM.ColorSchemesAdded();
+ hasModifiedProfiles |= profilesModified && profilesModified.Size() > 0;
+ hasAddedProfiles |= profilesAdded && profilesAdded.Size() > 0;
+ hasAddedColorSchemes |= colorSchemesAdded && colorSchemesAdded.Size() > 0;
+ }
+ if (hasModifiedProfiles)
+ {
+ _NotifyChanges(L"ProfilesModified");
+ }
+ if (hasAddedProfiles)
+ {
+ _NotifyChanges(L"ProfilesAdded");
+ }
+ if (hasAddedColorSchemes)
+ {
+ _NotifyChanges(L"ColorSchemesAdded");
+ }
+ }
+ });
+ }
+ extensionPackages.push_back(*extPkgVM);
+ }
+
+ // sort the lists linguistically for nicer presentation
+ std::sort(extensionPackages.begin(), extensionPackages.end(), ExtensionPackageViewModel::SortAscending);
+ std::sort(profilesModifiedTotal.begin(), profilesModifiedTotal.end(), FragmentProfileViewModel::SortAscending);
+ std::sort(profilesAddedTotal.begin(), profilesAddedTotal.end(), FragmentProfileViewModel::SortAscending);
+ std::sort(colorSchemesAddedTotal.begin(), colorSchemesAddedTotal.end(), FragmentColorSchemeViewModel::SortAscending);
+
+ _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));
+ _extensionsLoaded = true;
+ }
+
+ Windows::UI::Xaml::DataTemplate ExtensionsViewModel::CurrentExtensionPackageIdentifierTemplate() const
+ {
+ return _ExtensionPackageIdentifierTemplateSelector.SelectTemplate(CurrentExtensionPackage());
+ }
+
+ bool ExtensionsViewModel::DisplayBadge() const noexcept
+ {
+ return !Model::ApplicationState::SharedInstance().BadgeDismissed(ExtensionPageId);
+ }
+
+ // Returns true if the extension is enabled, false otherwise
+ bool ExtensionsViewModel::GetExtensionState(hstring extensionSource, const Model::CascadiaSettings& settings)
+ {
+ if (const auto& disabledExtensions = settings.GlobalSettings().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, const Model::CascadiaSettings& settings, bool enableExt)
+ {
+ // get the current status of the extension
+ uint32_t idx;
+ bool currentlyEnabled = true;
+ const auto& disabledExtensions = settings.GlobalSettings().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);
+ }
+ }
+ }
+
+ Thickness Extensions::CalculateMargin(bool hidden)
+ {
+ return ThicknessHelper::FromLengths(/*left*/ 0,
+ /*top*/ hidden ? 0 : 20,
+ /*right*/ 0,
+ /*bottom*/ 0);
+ }
+
+ void ExtensionsViewModel::NavigateToProfile(const guid profileGuid)
+ {
+ NavigateToProfileRequested.raise(*this, profileGuid);
+ }
+
+ void ExtensionsViewModel::NavigateToColorScheme(const Editor::ColorSchemeViewModel& schemeVM)
+ {
+ _colorSchemesPageVM.CurrentScheme(schemeVM);
+ NavigateToColorSchemeRequested.raise(*this, nullptr);
+ }
+
+ void ExtensionsViewModel::MarkAsVisited()
+ {
+ Model::ApplicationState::SharedInstance().DismissBadge(ExtensionPageId);
+ _NotifyChanges(L"DisplayBadge");
+ }
+
+ bool ExtensionPackageViewModel::SortAscending(const Editor::ExtensionPackageViewModel& lhs, const Editor::ExtensionPackageViewModel& rhs)
+ {
+ auto getKey = [&](const Editor::ExtensionPackageViewModel& pkgVM) {
+ const auto pkg = pkgVM.Package();
+ const auto displayName = pkg.DisplayName();
+ return displayName.empty() ? pkg.Source() : displayName;
+ };
+
+ return til::compare_linguistic_insensitive(getKey(lhs), getKey(rhs)) < 0;
+ }
+
+ void ExtensionPackageViewModel::UpdateSettings(const Model::CascadiaSettings& settings)
+ {
+ const auto oldEnabled = Enabled();
+ _settings = settings;
+ if (oldEnabled != Enabled())
+ {
+ // The enabled state of the extension has changed, notify the UI
+ _NotifyChanges(L"Enabled");
+ }
+ }
+
+ 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
+ {
+ hstring name;
+ const auto source = _package.Source();
+ if (const auto displayName = _package.DisplayName(); !displayName.empty())
+ {
+ return hstring{ fmt::format(FMT_COMPILE(L"{}, {}"), displayName, source) };
+ }
+ return source;
+ }
+
+ bool FragmentProfileViewModel::SortAscending(const Editor::FragmentProfileViewModel& lhs, const Editor::FragmentProfileViewModel& rhs)
+ {
+ return til::compare_linguistic_insensitive(lhs.Profile().Name(), rhs.Profile().Name()) < 0;
+ }
+
+ hstring FragmentProfileViewModel::AccessibleName() const noexcept
+ {
+ return hstring{ fmt::format(FMT_COMPILE(L"{}, {}"), Profile().Name(), SourceName()) };
+ }
+
+ bool FragmentColorSchemeViewModel::SortAscending(const Editor::FragmentColorSchemeViewModel& lhs, const Editor::FragmentColorSchemeViewModel& rhs)
+ {
+ return til::compare_linguistic_insensitive(lhs.ColorSchemeVM().Name(), rhs.ColorSchemeVM().Name()) < 0;
+ }
+
+ 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 nullptr;
+ }
+}
diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.h b/src/cascadia/TerminalSettingsEditor/Extensions.h
new file mode 100644
index 0000000000..6b7eafad57
--- /dev/null
+++ b/src/cascadia/TerminalSettingsEditor/Extensions.h
@@ -0,0 +1,193 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+
+#include "Extensions.g.h"
+#include "ExtensionsViewModel.g.h"
+#include "ExtensionPackageViewModel.g.h"
+#include "FragmentExtensionViewModel.g.h"
+#include "FragmentProfileViewModel.g.h"
+#include "FragmentColorSchemeViewModel.g.h"
+#include "ExtensionPackageTemplateSelector.g.h"
+#include "ViewModelHelpers.h"
+#include "Utils.h"
+
+namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
+{
+ struct Extensions : public HasScrollViewer, ExtensionsT
+ {
+ public:
+ Windows::UI::Xaml::Thickness CalculateMargin(bool hidden);
+
+ Extensions();
+
+ void OnNavigatedTo(const Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
+
+ 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
+ {
+ public:
+ ExtensionsViewModel(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM);
+
+ // Properties
+ 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; }
+ bool DisplayBadge() const noexcept;
+
+ // Views
+ 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 LazyLoadExtensions();
+ void UpdateSettings(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM);
+ void NavigateToProfile(const guid profileGuid);
+ void NavigateToColorScheme(const Editor::ColorSchemeViewModel& schemeVM);
+ void MarkAsVisited();
+
+ 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(Editor::ExtensionPackageViewModel, CurrentExtensionPackage, nullptr);
+ WINRT_PROPERTY(Editor::ExtensionPackageTemplateSelector, ExtensionPackageIdentifierTemplateSelector, nullptr);
+
+ private:
+ Model::CascadiaSettings _settings;
+ Editor::ColorSchemesPageViewModel _colorSchemesPageVM;
+ Windows::Foundation::Collections::IObservableVector _extensionPackages;
+ Windows::Foundation::Collections::IObservableVector _profilesModifiedView;
+ Windows::Foundation::Collections::IObservableVector _profilesAddedView;
+ Windows::Foundation::Collections::IObservableVector _colorSchemesAddedView;
+ bool _extensionsLoaded;
+
+ void _UpdateListViews(bool updateProfilesModified, bool updateProfilesAdded, bool updateColorSchemesAdded);
+ };
+
+ struct ExtensionPackageViewModel : ExtensionPackageViewModelT, ViewModelHelper
+ {
+ public:
+ ExtensionPackageViewModel(const Model::ExtensionPackage& pkg, const Model::CascadiaSettings& settings) :
+ _package{ pkg },
+ _settings{ settings },
+ _fragmentExtensions{ single_threaded_observable_vector() } {}
+
+ static bool SortAscending(const Editor::ExtensionPackageViewModel& lhs, const Editor::ExtensionPackageViewModel& rhs);
+
+ void UpdateSettings(const Model::CascadiaSettings& settings);
+
+ Model::ExtensionPackage Package() const noexcept { return _package; }
+ hstring Scope() const noexcept;
+ bool Enabled() const;
+ void Enabled(bool val);
+ hstring AccessibleName() const noexcept;
+ Windows::Foundation::Collections::IObservableVector FragmentExtensions() { return _fragmentExtensions; }
+
+ private:
+ Model::ExtensionPackage _package;
+ Model::CascadiaSettings _settings;
+ Windows::Foundation::Collections::IObservableVector _fragmentExtensions;
+ };
+
+ struct FragmentExtensionViewModel : FragmentExtensionViewModelT, ViewModelHelper
+ {
+ public:
+ FragmentExtensionViewModel(const Model::FragmentSettings& fragment,
+ std::vector& profilesModified,
+ std::vector& profilesAdded,
+ std::vector& colorSchemesAdded) :
+ _fragment{ fragment },
+ _profilesModified{ single_threaded_vector(std::move(profilesModified)) },
+ _profilesAdded{ single_threaded_vector(std::move(profilesAdded)) },
+ _colorSchemesAdded{ single_threaded_vector(std::move(colorSchemesAdded)) } {}
+
+ Model::FragmentSettings Fragment() const noexcept { return _fragment; }
+ 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(); }
+
+ private:
+ Model::FragmentSettings _fragment;
+ Windows::Foundation::Collections::IVector _profilesModified;
+ Windows::Foundation::Collections::IVector _profilesAdded;
+ Windows::Foundation::Collections::IVector _colorSchemesAdded;
+ };
+
+ struct FragmentProfileViewModel : FragmentProfileViewModelT, ViewModelHelper
+ {
+ public:
+ FragmentProfileViewModel(const Model::FragmentProfileEntry& entry, const Model::FragmentSettings& fragment, const Model::Profile& deducedProfile) :
+ _entry{ entry },
+ _fragment{ fragment },
+ _deducedProfile{ deducedProfile } {}
+
+ static bool SortAscending(const Editor::FragmentProfileViewModel& lhs, const Editor::FragmentProfileViewModel& rhs);
+
+ 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;
+ 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 } {}
+
+ static bool SortAscending(const Editor::FragmentColorSchemeViewModel& lhs, const Editor::FragmentColorSchemeViewModel& rhs);
+
+ 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
new file mode 100644
index 0000000000..4dc3018c36
--- /dev/null
+++ b/src/cascadia/TerminalSettingsEditor/Extensions.idl
@@ -0,0 +1,81 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import "ColorSchemesPageViewModel.idl";
+
+namespace Microsoft.Terminal.Settings.Editor
+{
+ [default_interface] runtimeclass Extensions : Windows.UI.Xaml.Controls.Page
+ {
+ Extensions();
+ ExtensionsViewModel ViewModel { get; };
+
+ Windows.UI.Xaml.Thickness CalculateMargin(Boolean hidden);
+ }
+
+ [default_interface] runtimeclass ExtensionsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
+ {
+ // Properties
+ ExtensionPackageViewModel CurrentExtensionPackage;
+ Windows.UI.Xaml.DataTemplate CurrentExtensionPackageIdentifierTemplate { get; };
+ Boolean IsExtensionView { get; };
+ Boolean NoExtensionPackages { get; };
+ Boolean NoProfilesModified { get; };
+ Boolean NoProfilesAdded { get; };
+ Boolean NoSchemesAdded { get; };
+ Boolean DisplayBadge { get; };
+
+ // Views
+ IVector ExtensionPackages { get; };
+ IObservableVector ProfilesModified { get; };
+ IObservableVector ProfilesAdded { get; };
+ IObservableVector ColorSchemesAdded { get; };
+
+ // Methods
+ void UpdateSettings(Microsoft.Terminal.Settings.Model.CascadiaSettings settings, ColorSchemesPageViewModel colorSchemesPageVM);
+
+ event Windows.Foundation.TypedEventHandler