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 79c392f595..6b7e11561a 100644 --- a/src/cascadia/TerminalSettingsEditor/CommonResources.xaml +++ b/src/cascadia/TerminalSettingsEditor/CommonResources.xaml @@ -1199,7 +1199,7 @@ - + @@ -1222,7 +1222,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..f34eca968f --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Extensions.cpp @@ -0,0 +1,361 @@ +// 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() }; + if (viewModelProperty == L"CurrentExtensionPackage") + { + // 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(); + + // 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()) + { + _profilesModifiedView.Append(profile); + } + for (const auto& profile : ext.ProfilesAdded()) + { + _profilesAddedView.Append(profile); + } + for (const auto& scheme : ext.ColorSchemesAdded()) + { + _colorSchemesAddedView.Append(scheme); + } + } + }; + + if (const auto currentExtensionPackage = CurrentExtensionPackage()) + { + addPackageContentsToView(currentExtensionPackage); + } + else + { + for (const auto& extPkg : _extensionPackages) + { + addPackageContentsToView(extPkg); + } + } + + _NotifyChanges(L"IsExtensionView", L"CurrentExtensionPackageIdentifierTemplate"); + } + }); + } + + void ExtensionsViewModel::UpdateSettings(const Model::CascadiaSettings& settings, const Editor::ColorSchemesPageViewModel& colorSchemesPageVM) + { + _settings = settings; + _colorSchemesPageVM = colorSchemesPageVM; + _CurrentExtensionPackage = nullptr; + } + + 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); + 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; + + 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); + } + } + } + } + } + extPkgVM->FragmentExtensions().Append(winrt::make(fragExt, currentProfilesModified, currentProfilesAdded, currentColorSchemesAdded)); + } + } + + _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); + } + } + } + + 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"); + } + + 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; + } + + // 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 nullptr; + } +} diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.h b/src/cascadia/TerminalSettingsEditor/Extensions.h new file mode 100644 index 0000000000..7195ff191d --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Extensions.h @@ -0,0 +1,182 @@ +// 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: + 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; + }; + + struct ExtensionPackageViewModel : ExtensionPackageViewModelT, ViewModelHelper + { + public: + 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: + 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 } {} + + 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 } {} + + 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..1e110b9af1 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Extensions.idl @@ -0,0 +1,80 @@ +// 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; }; + } + + [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 NavigateToProfileRequested; + event Windows.Foundation.TypedEventHandler NavigateToColorSchemeRequested; + } + + [default_interface] runtimeclass ExtensionPackageViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + 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 + { + Microsoft.Terminal.Settings.Model.FragmentSettings Fragment { get; }; + IVectorView ProfilesModified { get; }; + IVectorView ProfilesAdded { get; }; + IVectorView ColorSchemesAdded { get; }; + } + + [default_interface] runtimeclass FragmentProfileViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + 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 + { + 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 new file mode 100644 index 0000000000..5a3411eba6 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Extensions.xaml @@ -0,0 +1,493 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 89e1afe88b..09af3fa5e1 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 "NewActions.h" #include "ProfileViewModel.h" @@ -47,6 +48,7 @@ static const std::wstring_view compatibilityTag{ L"Compatibility_Nav" }; static const std::wstring_view actionsTag{ L"Actions_Nav" }; static const std::wstring_view newActionsTag{ L"NewActions_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" }; @@ -131,6 +133,32 @@ 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"CurrentExtensionPackage") + { + if (const auto& currentExtensionPackage = _extensionsVM.CurrentExtensionPackage()) + { + 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 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); + } + 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 @@ -183,6 +211,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _colorSchemesPageVM.UpdateSettings(_settingsClone); _actionsVM.UpdateSettings(_settingsClone); _newTabMenuPageVM.UpdateSettings(_settingsClone); + _extensionsVM.UpdateSettings(_settingsClone, _colorSchemesPageVM); // We'll update the profile in the _profilesNavState whenever we actually navigate to one @@ -204,7 +233,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { // found the one that was selected before the refresh SettingsNav().SelectedItem(item); - _Navigate(*stringTag, crumb->SubPage()); + _Navigate(*breadcrumbStringTag, crumb->SubPage()); return; } } @@ -219,6 +248,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() }) { @@ -489,6 +529,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _breadcrumbs.Append(crumb); } } + else if (clickedItemTag == extensionsTag) + { + if (_extensionsVM.CurrentExtensionPackage()) + { + // Setting CurrentExtensionPackage triggers the PropertyChanged event, + // which will navigate to the correct page and update the breadcrumbs appropriately + _extensionsVM.CurrentExtensionPackage(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) { auto profileVM{ _viewModelForProfile(_settingsClone.ProfileDefaults(), _settingsClone, Dispatcher()) }; @@ -619,6 +674,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::SaveButton_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*args*/) { _settingsClone.LogSettingChanges(false); @@ -644,6 +733,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); @@ -841,6 +934,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 e50073a7c1..c99ef75036 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.h +++ b/src/cascadia/TerminalSettingsEditor/MainPage.h @@ -43,6 +43,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation winrt::Windows::UI::Xaml::Media::Brush BackgroundBrush(); Windows::Foundation::Collections::IObservableVector Breadcrumbs() noexcept; + Editor::ExtensionsViewModel ExtensionsVM() const noexcept { return _extensionsVM; } til::typed_event OpenJson; @@ -68,6 +69,9 @@ 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); void _UpdateBackgroundForMica(); void _MoveXamlParsedNavItemsIntoItemSource(); @@ -75,11 +79,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation winrt::Microsoft::Terminal::Settings::Editor::ColorSchemesPageViewModel _colorSchemesPageVM{ nullptr }; winrt::Microsoft::Terminal::Settings::Editor::ActionsViewModel _actionsVM{ 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 _actionsViewModelChangedRevoker; 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 e64a276239..d34428a275 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.idl +++ b/src/cascadia/TerminalSettingsEditor/MainPage.idl @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import "Extensions.idl"; + namespace Microsoft.Terminal.Settings.Editor { // Due to a XAML Compiler bug, it is hard for us to propagate an HWND into a XAML-using runtimeclass. @@ -21,7 +23,8 @@ namespace Microsoft.Terminal.Settings.Editor Profile_Advanced, ColorSchemes_Edit, Actions_Edit, - NewTabMenu_Folder + NewTabMenu_Folder, + Extensions_Extension }; runtimeclass Breadcrumb : Windows.Foundation.IStringable @@ -43,6 +46,7 @@ namespace Microsoft.Terminal.Settings.Editor void SetHostingWindow(UInt64 window); Windows.Foundation.Collections.IObservableVector Breadcrumbs { get; }; + ExtensionsViewModel ExtensionsVM { get; }; Windows.UI.Xaml.Media.Brush BackgroundBrush { get; }; } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.xaml b/src/cascadia/TerminalSettingsEditor/MainPage.xaml index 492e1505b2..65c9a11a3e 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.xaml +++ b/src/cascadia/TerminalSettingsEditor/MainPage.xaml @@ -120,7 +120,8 @@ - @@ -162,6 +163,17 @@ + + + + + + + + + NewTabMenuViewModel.idl Code + + Extensions.xaml + Code + Profiles_Base.xaml Code @@ -215,6 +219,9 @@ Designer + + Designer + Designer @@ -335,6 +342,10 @@ NewTabMenuViewModel.idl Code + + Extensions.xaml + Code + Profiles_Base.xaml Code @@ -445,6 +456,10 @@ + + Extensions.xaml + Code + Profiles_Base.xaml Code diff --git a/src/cascadia/TerminalSettingsEditor/NewTabMenu.cpp b/src/cascadia/TerminalSettingsEditor/NewTabMenu.cpp index 901b809f34..25b7f8ef77 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 cb54fd11b9..f305c44c88 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/NewTabMenu.xaml b/src/cascadia/TerminalSettingsEditor/NewTabMenu.xaml index 018a00d532..b8e46556b9 100644 --- a/src/cascadia/TerminalSettingsEditor/NewTabMenu.xaml +++ b/src/cascadia/TerminalSettingsEditor/NewTabMenu.xaml @@ -321,7 +321,7 @@ - + diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index f270b845d7..4cb4ee6b62 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -688,6 +688,10 @@ New Actions Header for the "new 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. @@ -2648,4 +2652,67 @@ 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. + + + 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 + + + Disabled + Text displayed when an extension is disabled + + + NEW + Text is used on an info badge for new navigation items. Must be all caps. + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml b/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml index c4d3c7a580..908d12ae2b 100644 --- a/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml +++ b/src/cascadia/TerminalSettingsEditor/SettingContainerStyle.xaml @@ -179,13 +179,18 @@ + + - @@ -228,6 +233,45 @@ + + + FindFragmentsAndMergeIntoUserSettings must be called after MergeInboxIntoUserSettings. - loader.FindFragmentsAndMergeIntoUserSettings(); + loader.FindFragmentsAndMergeIntoUserSettings(false); loader.FinalizeLayering(); // DisableDeletedProfiles returns true whenever we encountered any new generated/dynamic profiles. diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 8a699eb865..55af170355 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) { diff --git a/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h b/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h index db57944b10..9144bcb325 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 14878c8278..01e4c491bd 100644 --- a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp @@ -15,12 +15,14 @@ #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" }; static constexpr std::wstring_view PWSH_EXE{ L"pwsh.exe" }; static constexpr std::wstring_view POWERSHELL_ICON{ L"ms-appx:///ProfileIcons/pwsh.png" }; static constexpr std::wstring_view POWERSHELL_PREVIEW_ICON{ L"ms-appx:///ProfileIcons/pwsh-preview.png" }; +static constexpr std::wstring_view GENERATOR_POWERSHELL_ICON{ L"ms-appx:///ProfileGeneratorIcons/PowerShell.png" }; static constexpr std::wstring_view POWERSHELL_PREFERRED_PROFILE_NAME{ L"PowerShell" }; namespace @@ -294,6 +296,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 GENERATOR_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 eaec9dacea..d473292e15 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 330c84a322..a3fa2d007c 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -1010,4 +1010,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 + diff --git a/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp b/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp index 30dc19039f..45711c711e 100644 --- a/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp @@ -7,11 +7,13 @@ #include "../../inc/DefaultSettings.h" #include "DynamicProfileUtils.h" +#include static constexpr std::wstring_view SshHostGeneratorNamespace{ L"Windows.Terminal.SSH" }; static constexpr std::wstring_view PROFILE_TITLE_PREFIX = L"SSH - "; static constexpr std::wstring_view PROFILE_ICON_PATH = L"ms-appx:///ProfileIcons/{550ce7b8-d500-50ad-8a1a-c400c3262db3}.png"; +static constexpr std::wstring_view GENERATOR_ICON_PATH = L"ms-appx:///ProfileGeneratorIcons/SSH.png"; // OpenSSH is installed under System32 when installed via Optional Features static constexpr std::wstring_view SSH_EXE_PATH1 = L"%SystemRoot%\\System32\\OpenSSH\\ssh.exe"; @@ -132,6 +134,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 GENERATOR_ICON_PATH; +} + // 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 a355b0c430..d83e62535b 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 2fcfb1cc52..69feb6174e 100644 --- a/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.cpp @@ -6,16 +6,28 @@ #include "VisualStudioGenerator.h" #include "VsDevCmdGenerator.h" #include "VsDevShellGenerator.h" +#include using namespace winrt::Microsoft::Terminal::Settings::Model; std::wstring_view VisualStudioGenerator::Namespace{ L"Windows.Terminal.VisualStudio" }; +static constexpr std::wstring_view IconPath{ L"ms-appx:///ProfileGeneratorIcons/VisualStudio.png" }; std::wstring_view VisualStudioGenerator::GetNamespace() const noexcept { return Namespace; } +std::wstring_view VisualStudioGenerator::GetDisplayName() const noexcept +{ + return RS_(L"VisualStudioGeneratorDisplayName"); +} + +std::wstring_view VisualStudioGenerator::GetIcon() const noexcept +{ + return IconPath; +} + 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 ef36284db7..c89e7c6301 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; void GenerateProfiles(std::vector>& profiles) const override; class IVisualStudioProfileGenerator diff --git a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp index cb1d220a90..2e0c81e5a5 100644 --- a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp @@ -8,10 +8,13 @@ #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" }; +static constexpr std::wstring_view GeneratorIconPath{ L"ms-appx:///ProfileGeneratorIcons/WSL.png" }; // The WSL entries are structured as such: // HKCU\Software\Microsoft\Windows\CurrentVersion\Lxss @@ -47,6 +50,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 GeneratorIconPath; +} + static winrt::com_ptr makeProfile(const std::wstring& distName) { const auto WSLDistro{ CreateDynamicProfile(distName) }; @@ -65,7 +78,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 b46aac8203..123734523f 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; }; };