Compare commits

...

97 Commits

Author SHA1 Message Date
Carlos Zamora
32252145f2 PRE-MERGE #18639 Introduce PowerShell installer stub 2025-05-05 18:15:20 -07:00
Carlos Zamora
1734d13cc0 PRE-MERGE #18814 Enable SSH Generator Feature Flag 2025-05-05 18:14:44 -07:00
Carlos Zamora
c8f1050e21 PRE-MERGE #18559 Add Extensions page to Settings UI 2025-05-05 18:14:33 -07:00
Carlos Zamora
8db1805e17 Merge branch 'dev/cazamor/sui/extensions-page' into dev/cazamor/sui/ext-page/powershell-stub 2025-05-05 18:01:43 -07:00
Carlos Zamora
27e1aee881 Merge branch 'main' into dev/cazamor/sui/extensions-page 2025-05-05 17:59:48 -07:00
Carlos Zamora
5ae13b3e42 Add toggle switches to root of Extensions page 2025-05-05 17:59:13 -07:00
Pankaj Bhojwani
bfecd07bdb Merge branch 'main' of https://github.com/microsoft/terminal into dev/pabhoj/sui_action_overhaul 2025-05-05 15:34:00 -07:00
Pankaj Bhojwani
23e056a727 some visual fixes 2025-05-05 15:33:54 -07:00
Carlos Zamora
831313c959 fix propagating CascadiaSettings reference & Enabled notification 2025-05-05 13:55:12 -07:00
Carlos Zamora
2d077ca892 Linguistic sorting! 2025-05-05 11:35:51 -07:00
Carlos Zamora
3353323880 apply feedback from bug bash 2025-05-02 13:32:56 -07:00
Pankaj Bhojwani
d9927ca6ad add,delete,modify keychord fixes 2025-05-01 15:01:39 -07:00
Pankaj Bhojwani
3b5bed8331 edit inbox action works 2025-04-30 16:14:20 -07:00
Pankaj Bhojwani
410ef5ae8d make sure name is not empty 2025-04-29 19:32:11 -07:00
Pankaj Bhojwani
378ed3b970 display name and generated id conflicts 2025-04-29 19:30:54 -07:00
Pankaj Bhojwani
fef53512aa new terminal args fix 2025-04-29 17:52:10 -07:00
Pankaj Bhojwani
43f16efbab add new fixes 2025-04-29 16:54:51 -07:00
Carlos Zamora
244550044a Merge branch 'dev/cazamor/sui/extensions-page' into dev/cazamor/sui/ext-page/powershell-stub 2025-04-28 17:02:56 -07:00
Pankaj Bhojwani
2c75721447 Merge branch 'main' of https://github.com/microsoft/terminal into dev/pabhoj/sui_action_overhaul 2025-04-28 16:48:46 -07:00
Pankaj Bhojwani
c8e2e08763 new tab, new window, split pane 2025-04-28 16:48:23 -07:00
Carlos Zamora
34c6115a59 Merge branch 'main' into dev/cazamor/sui/extensions-page 2025-04-28 15:06:31 -07:00
Carlos Zamora
fc0eb78f5f simplify AppState code; display 'NEW' 2025-04-24 15:17:01 -07:00
Carlos Zamora
debaceee2f Add Badge to highlight new Extensions Page 2025-04-24 15:16:58 -07:00
Carlos Zamora
057128e661 Lazy load extension objects for SUI 2025-04-24 14:38:07 -07:00
Carlos Zamora
36d28e2ba9 fix rebase 2025-04-22 16:49:56 -07:00
Carlos Zamora
0fe444e07a add feature flag 2025-04-22 16:48:56 -07:00
Carlos Zamora
cc1d632598 fix ARM64 2025-04-22 16:48:56 -07:00
Carlos Zamora
a7d6e27a48 apply feedback 2025-04-22 16:48:55 -07:00
Carlos Zamora
2e7e37ae09 Introduce PowerShell installer stub 2025-04-22 16:48:53 -07:00
Carlos Zamora
92bccdfa4a Enable SSH Generator Feature Flag 2025-04-22 16:34:45 -07:00
Carlos Zamora
f25e6fe2d1 [fix rebase] remove deep clone of ext pkgs 2025-04-22 15:12:33 -07:00
Carlos Zamora
e9f83fc4eb Explicitly introduce and use profile generator icons; add VS logo 2025-04-22 14:58:40 -07:00
Carlos Zamora
c371c484a3 use high resolution assets 2025-04-22 14:58:40 -07:00
Carlos Zamora
1072d69fb7 a11y check: screen reader, keyboard navigation, FastPass 2025-04-22 14:58:40 -07:00
Carlos Zamora
b472d9fed9 add DisplayName and Icon to profile generators; improve identifier system 2025-04-22 14:58:37 -07:00
Carlos Zamora
7fcdeaac40 Add display name and icon to SUI extensions page 2025-04-22 14:57:41 -07:00
Carlos Zamora
2766f21d31 JsonSource->Filename; remove deep copy of fragments & generators 2025-04-22 14:51:12 -07:00
Pankaj Bhojwani
f1424a471b ignore unimplemented args 2025-04-21 17:49:20 -07:00
Pankaj Bhojwani
7b5e633d57 add new works 2025-04-21 17:29:00 -07:00
Carlos Zamora
0af4eb0e21 Fix XAML crash for release builds (applied incompatible styling) 2025-04-21 14:32:08 -07:00
Carlos Zamora
b0a7ef1d39 apply feedback from Dustin 2025-04-21 14:22:19 -07:00
Pankaj Bhojwani
65bf620d34 fix debugger issue, remove edit button 2025-04-16 04:47:01 -07:00
Pankaj Bhojwani
8092f4644b Merge branch 'main' of https://github.com/microsoft/terminal into dev/pabhoj/sui_action_overhaul 2025-03-10 17:26:02 -07:00
Pankaj Bhojwani
bc84287fcb has args 2025-03-10 17:25:51 -07:00
Pankaj Bhojwani
055ffabcdc use grids 2025-02-28 10:16:26 -08:00
Pankaj Bhojwani
14c6f36285 various cleanups 2025-02-27 15:48:01 -08:00
Carlos Zamora
7cdbb7c795 address feedback 2025-02-27 10:17:11 -08:00
Carlos Zamora
5afc9bc86a add dynamic profile generators 2025-02-27 10:06:09 -08:00
Carlos Zamora
6b83fa705a wil::to_vector 2025-02-27 10:06:09 -08:00
Carlos Zamora
e6c43c0d4c copy over DisabledProfileSources 2025-02-27 10:06:09 -08:00
Carlos Zamora
75d02c29bd add nested page (part 2) + support for multiple json files 2025-02-27 10:06:09 -08:00
Carlos Zamora
f11515e692 add nested page 2025-02-27 10:06:09 -08:00
Carlos Zamora
7f0c9e5374 rename some resources; remove some TODOs 2025-02-27 10:06:07 -08:00
Carlos Zamora
019bb766db Add Extensions page to Settings UI 2025-02-27 10:04:50 -08:00
Pankaj Bhojwani
4cebcc193e optional enums and flags 2025-02-26 17:02:48 -08:00
Pankaj Bhojwani
b5eafa6912 optional uint32 2025-02-21 12:31:28 -08:00
Pankaj Bhojwani
6d550b36ff remove bool template, only need optional 2025-02-21 10:54:31 -08:00
Pankaj Bhojwani
289ecec719 move from converter to argwrapper 2025-02-20 17:11:18 -08:00
Pankaj Bhojwani
4a780e189c selection color to future 2025-02-20 12:28:02 -08:00
Pankaj Bhojwani
c4ac493617 core color and winui color 2025-02-20 10:22:40 -08:00
Pankaj Bhojwani
e2336d4d00 merge main 2025-02-14 13:04:38 -08:00
Pankaj Bhojwani
4623575b79 flags work 2025-02-13 16:03:00 -08:00
Pankaj Bhojwani
87c79b3b18 almost all 2025-02-12 15:35:19 -08:00
Pankaj Bhojwani
ed85ebc41a a few more 2025-02-11 17:44:41 -08:00
Pankaj Bhojwani
a2ec2f31cc resize pane works 2025-02-11 13:43:07 -08:00
Pankaj Bhojwani
d8360a3789 resize dir 2025-02-10 09:40:02 -08:00
Pankaj Bhojwani
27fc46d895 bools and optional bools work 2025-02-03 16:32:14 -08:00
Pankaj Bhojwani
2cea85d22e little more cleanup 2025-01-31 11:30:48 -08:00
Pankaj Bhojwani
762c24f102 default args 2025-01-30 16:30:11 -08:00
Pankaj Bhojwani
43ccd382ec abit of cleanup 2025-01-30 10:25:17 -08:00
Pankaj Bhojwani
9cafea210c fix macro 2025-01-29 15:29:21 -08:00
Pankaj Bhojwani
54292253c8 can change action, can edit args 2025-01-29 13:03:18 -08:00
Pankaj Bhojwani
14f12098fe arg wrapper boilerplate 2025-01-14 12:40:20 -08:00
Pankaj Bhojwani
94a463e3e7 lookup 2025-01-08 10:08:42 -08:00
Pankaj Bhojwani
059f8ebb7e other shortcut actions show up 2025-01-06 16:05:34 -08:00
Pankaj Bhojwani
6c76f05da8 conflict 2024-12-10 10:57:15 -08:00
Pankaj Bhojwani
6b93fb645e even better way to do it 2024-10-11 14:17:18 -07:00
Pankaj Bhojwani
847d44c59b macros work 2024-10-10 13:03:22 -07:00
Pankaj Bhojwani
be99303d3c better way 2024-10-09 11:38:59 -07:00
Pankaj Bhojwani
a953ed8ea3 get arg count works 2024-10-09 11:12:20 -07:00
Pankaj Bhojwani
26289f4206 setting args works 2024-10-02 16:11:33 -07:00
Pankaj Bhojwani
eed9d1c6b4 more settings model support 2024-09-27 11:42:57 -07:00
Pankaj Bhojwani
e433f52224 id changed handler 2024-09-26 16:26:46 -07:00
Pankaj Bhojwani
e21edd6d63 remove this weird temp thing 2024-09-24 12:32:49 -07:00
Pankaj Bhojwani
96158194bd template updating _works_ but its jank, needs cleanup/better design 2024-09-24 11:30:53 -07:00
Pankaj Bhojwani
11bd5beb7e add keychord 2024-09-19 19:40:39 -07:00
Pankaj Bhojwani
8d21f06ae1 kcvm and cmdvm delete 2024-09-19 17:35:33 -07:00
Pankaj Bhojwani
a54d71a096 update settings, change action 2024-09-18 17:55:46 -07:00
Pankaj Bhojwani
d9101991d4 modify keybinding works 2024-09-18 16:34:39 -07:00
Pankaj Bhojwani
5a188c220e start of modify keybinding 2024-09-17 18:35:40 -07:00
Pankaj Bhojwani
44bc11206b keychord view model 2024-09-16 17:39:58 -07:00
Pankaj Bhojwani
be91a1dac6 check user action 2024-09-13 15:11:46 -07:00
Pankaj Bhojwani
db9d5be258 edit page 2024-09-12 16:11:55 -07:00
Pankaj Bhojwani
2407ef345d barebones cmdvm 2024-09-11 16:09:09 -07:00
Pankaj Bhojwani
c72203ddd1 own page 2024-09-09 11:36:02 -07:00
Pankaj Bhojwani
13eaf5af94 add test page 2024-09-05 15:09:56 -07:00
Pankaj Bhojwani
14fbb9980b boilerplate 2024-09-04 13:07:04 -07:00
80 changed files with 6487 additions and 480 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -21,6 +21,11 @@
<DeploymentContent>true</DeploymentContent>
<Link>ProfileIcons\%(RecursiveDir)%(FileName)%(Extension)</Link>
</Content>
<!-- Profile Generator Icons -->
<Content Include="$(OpenConsoleDir)src\cascadia\CascadiaPackage\ProfileGeneratorIcons\**\*">
<DeploymentContent>true</DeploymentContent>
<Link>ProfileGeneratorIcons\%(RecursiveDir)%(FileName)%(Extension)</Link>
</Content>
<!-- Default Settings -->
<Content Include="$(OpenConsoleDir)src\cascadia\TerminalSettingsModel\defaults.json">
<DeploymentContent>true</DeploymentContent>

View File

@@ -77,6 +77,16 @@ namespace winrt::TerminalApp::implementation
}
const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) };
if (profile.Source() == L"Windows.Terminal.InstallPowerShell")
{
TraceLoggingWrite(
g_hTerminalAppProvider,
"InstallPowerShellStubInvoked",
TraceLoggingDescription("Event emitted when the 'Install Latest PowerShell' stub was invoked"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
}
// Try to handle auto-elevation
if (_maybeElevate(newTerminalArgs, settings, profile))
{

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,12 @@
#include "ActionsViewModel.g.h"
#include "KeyBindingViewModel.g.h"
#include "CommandViewModel.g.h"
#include "ArgWrapper.g.h"
#include "ActionArgsViewModel.g.h"
#include "KeyChordViewModel.g.h"
#include "ModifyKeyBindingEventArgs.g.h"
#include "ModifyKeyChordEventArgs.g.h"
#include "Utils.h"
#include "ViewModelHelpers.h"
@@ -19,6 +24,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
};
struct CommandViewModelComparator
{
bool operator()(const Editor::CommandViewModel& lhs, const Editor::CommandViewModel& rhs) const
{
return lhs.DisplayName() < rhs.DisplayName();
}
};
struct ModifyKeyBindingEventArgs : ModifyKeyBindingEventArgsT<ModifyKeyBindingEventArgs>
{
public:
@@ -34,6 +47,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_PROPERTY(hstring, NewActionName);
};
struct ModifyKeyChordEventArgs : ModifyKeyChordEventArgsT<ModifyKeyChordEventArgs>
{
public:
ModifyKeyChordEventArgs(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys) :
_OldKeys{ oldKeys },
_NewKeys{ newKeys } {}
WINRT_PROPERTY(Control::KeyChord, OldKeys, nullptr);
WINRT_PROPERTY(Control::KeyChord, NewKeys, nullptr);
};
struct KeyBindingViewModel : KeyBindingViewModelT<KeyBindingViewModel>, ViewModelHelper<KeyBindingViewModel>
{
public:
@@ -99,32 +123,200 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
hstring _KeyChordText{};
};
struct CommandViewModel : CommandViewModelT<CommandViewModel>, ViewModelHelper<CommandViewModel>
{
public:
CommandViewModel(winrt::Microsoft::Terminal::Settings::Model::Command cmd,
std::vector<Control::KeyChord> keyChordList,
const Editor::ActionsViewModel actionsPageVM,
const Windows::Foundation::Collections::IMap<Model::ShortcutAction, winrt::hstring>& availableShortcutActionsAndNames);
void Initialize();
winrt::hstring DisplayName();
winrt::hstring Name();
void Name(const winrt::hstring& newName);
winrt::hstring ID();
void ID(const winrt::hstring& newID);
bool IsUserAction();
void Edit_Click();
til::typed_event<Editor::CommandViewModel, IInspectable> EditRequested;
void Delete_Click();
til::typed_event<Editor::CommandViewModel, IInspectable> DeleteRequested;
void AddKeybinding_Click();
til::typed_event<IInspectable, Editor::ArgWrapper> PropagateColorSchemeRequested;
VIEW_MODEL_OBSERVABLE_PROPERTY(IInspectable, ProposedShortcutAction);
VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::ActionArgsViewModel, ActionArgsVM, nullptr);
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<hstring>, AvailableShortcutActions, nullptr);
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::KeyChordViewModel>, KeyChordViewModelList, nullptr);
WINRT_PROPERTY(bool, IsNewCommand, false);
private:
winrt::Microsoft::Terminal::Settings::Model::Command _command;
std::vector<Control::KeyChord> _keyChordList;
weak_ref<Editor::ActionsViewModel> _actionsPageVM{ nullptr };
void _RegisterKeyChordVMEvents(Editor::KeyChordViewModel kcVM);
void _RegisterActionArgsVMEvents(Editor::ActionArgsViewModel actionArgsVM);
void _ReplaceCommandWithUserCopy();
void _CreateAndInitializeActionArgsVMHelper();
Windows::Foundation::Collections::IMap<Model::ShortcutAction, winrt::hstring> _AvailableActionsAndNamesMap;
std::unordered_map<winrt::hstring, Model::ShortcutAction> _NameToActionMap;
};
struct ArgWrapper : ArgWrapperT<ArgWrapper>, ViewModelHelper<ArgWrapper>
{
public:
ArgWrapper(const winrt::hstring& name, const winrt::hstring& type, const bool required, const Windows::Foundation::IInspectable& value);
void Initialize();
winrt::hstring Name() const noexcept { return _name; };
winrt::hstring Type() const noexcept { return _type; };
bool Required() const noexcept { return _required; };
// We cannot use the macro here because we need to implement additional logic for the setter
Windows::Foundation::IInspectable EnumValue() const noexcept { return _EnumValue; };
void EnumValue(const Windows::Foundation::IInspectable& value);
Windows::Foundation::Collections::IObservableVector<Microsoft::Terminal::Settings::Editor::EnumEntry> EnumList() const noexcept { return _EnumList; };
Windows::Foundation::Collections::IObservableVector<Microsoft::Terminal::Settings::Editor::FlagEntry> FlagList() const noexcept { return _FlagList; };
// unboxing functions
winrt::hstring UnboxString(const Windows::Foundation::IInspectable& value);
winrt::hstring UnboxGuid(const Windows::Foundation::IInspectable& value);
int32_t UnboxInt32(const Windows::Foundation::IInspectable& value);
float UnboxInt32Optional(const Windows::Foundation::IInspectable& value);
uint32_t UnboxUInt32(const Windows::Foundation::IInspectable& value);
float UnboxUInt32Optional(const Windows::Foundation::IInspectable& value);
float UnboxUInt64(const Windows::Foundation::IInspectable& value);
float UnboxFloat(const Windows::Foundation::IInspectable& value);
winrt::Windows::Foundation::IReference<bool> UnboxBoolOptional(const Windows::Foundation::IInspectable& value);
winrt::Windows::Foundation::IReference<Microsoft::Terminal::Core::Color> UnboxTerminalCoreColorOptional(const Windows::Foundation::IInspectable& value);
winrt::Windows::Foundation::IReference<Microsoft::Terminal::Core::Color> UnboxWindowsUIColorOptional(const Windows::Foundation::IInspectable& value);
// bind back functions
void StringBindBack(const winrt::hstring& newValue);
void GuidBindBack(const winrt::hstring& newValue);
void Int32BindBack(const double newValue);
void Int32OptionalBindBack(const double newValue);
void UInt32BindBack(const double newValue);
void UInt32OptionalBindBack(const double newValue);
void UInt64BindBack(const double newValue);
void FloatBindBack(const double newValue);
void BoolOptionalBindBack(const Windows::Foundation::IReference<bool> newValue);
void TerminalCoreColorBindBack(const winrt::Windows::Foundation::IReference<Microsoft::Terminal::Core::Color> newValue);
void WindowsUIColorBindBack(const winrt::Windows::Foundation::IReference<Microsoft::Terminal::Core::Color> newValue);
til::typed_event<IInspectable, Editor::ArgWrapper> ColorSchemeRequested;
VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::ColorSchemeViewModel, DefaultColorScheme, nullptr);
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::Foundation::IInspectable, Value, nullptr);
private:
winrt::hstring _name;
winrt::hstring _type;
bool _required;
Windows::Foundation::IInspectable _EnumValue{ nullptr };
Windows::Foundation::Collections::IObservableVector<Microsoft::Terminal::Settings::Editor::EnumEntry> _EnumList;
Windows::Foundation::Collections::IObservableVector<Microsoft::Terminal::Settings::Editor::FlagEntry> _FlagList;
};
struct ActionArgsViewModel : ActionArgsViewModelT<ActionArgsViewModel>, ViewModelHelper<ActionArgsViewModel>
{
public:
ActionArgsViewModel(const Microsoft::Terminal::Settings::Model::ActionAndArgs actionAndArgs);
void Initialize();
bool HasArgs() const noexcept;
til::typed_event<IInspectable, IInspectable> WrapperValueChanged;
til::typed_event<IInspectable, Editor::ArgWrapper> PropagateColorSchemeRequested;
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::ArgWrapper>, ArgValues, nullptr);
private:
Model::ActionAndArgs _actionAndArgs{ nullptr };
};
struct KeyChordViewModel : KeyChordViewModelT<KeyChordViewModel>, ViewModelHelper<KeyChordViewModel>
{
public:
KeyChordViewModel(Control::KeyChord CurrentKeys);
void CurrentKeys(const Control::KeyChord& newKeys);
Control::KeyChord CurrentKeys() const noexcept;
void ToggleEditMode();
void AttemptAcceptChanges();
void CancelChanges();
void DeleteKeyChord();
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsInEditMode, false);
VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, ProposedKeys);
VIEW_MODEL_OBSERVABLE_PROPERTY(winrt::hstring, KeyChordText);
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Controls::Flyout, AcceptChangesFlyout, nullptr);
public:
til::typed_event<Editor::KeyChordViewModel, Terminal::Control::KeyChord> AddKeyChordRequested;
til::typed_event<Editor::KeyChordViewModel, Editor::ModifyKeyChordEventArgs> ModifyKeyChordRequested;
til::typed_event<Editor::KeyChordViewModel, Terminal::Control::KeyChord> DeleteKeyChordRequested;
private:
Control::KeyChord _currentKeys;
};
struct ActionsViewModel : ActionsViewModelT<ActionsViewModel>, ViewModelHelper<ActionsViewModel>
{
public:
ActionsViewModel(Model::CascadiaSettings settings);
void UpdateSettings(const Model::CascadiaSettings& settings);
void OnAutomationPeerAttached();
void AddNewKeybinding();
void AddNewCommand();
void CurrentCommand(const Editor::CommandViewModel& newCommand);
Editor::CommandViewModel CurrentCommand();
void CmdListItemClicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Controls::ItemClickEventArgs& e);
void AttemptDeleteKeyChord(const Control::KeyChord& keys);
void AttemptAddOrModifyKeyChord(const Editor::KeyChordViewModel& senderVM, winrt::hstring commandID, const Control::KeyChord& newKeys, const Control::KeyChord& oldKeys);
void AttemptAddCopiedCommand(const Model::Command& newCommand);
til::typed_event<IInspectable, IInspectable> FocusContainer;
til::typed_event<IInspectable, IInspectable> UpdateBackground;
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::KeyBindingViewModel>, KeyBindingList);
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::CommandViewModel>, CommandList);
WINRT_OBSERVABLE_PROPERTY(ActionsSubPage, CurrentPage, _propertyChangedHandlers, ActionsSubPage::Base);
private:
Editor::CommandViewModel _CurrentCommand{ nullptr };
bool _AutomationPeerAttached{ false };
Model::CascadiaSettings _Settings;
Windows::Foundation::Collections::IObservableVector<hstring> _AvailableActionAndArgs;
Windows::Foundation::Collections::IMap<hstring, Model::ActionAndArgs> _AvailableActionMap;
Windows::Foundation::Collections::IMap<Model::ShortcutAction, winrt::hstring> _AvailableActionsAndNamesMap;
void _MakeCommandVMsHelper();
std::optional<uint32_t> _GetContainerIndexByKeyChord(const Control::KeyChord& keys);
void _RegisterEvents(com_ptr<implementation::KeyBindingViewModel>& kbdVM);
void _RegisterCmdVMEvents(com_ptr<implementation::CommandViewModel>& cmdVM);
void _KeyBindingViewModelPropertyChangedHandler(const Windows::Foundation::IInspectable& senderVM, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
void _KeyBindingViewModelDeleteKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Control::KeyChord& args);
void _KeyBindingViewModelModifyKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Editor::ModifyKeyBindingEventArgs& args);
void _KeyBindingViewModelDeleteNewlyAddedKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const IInspectable& args);
void _CmdVMPropertyChangedHandler(const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
void _CmdVMEditRequestedHandler(const Editor::CommandViewModel& senderVM, const IInspectable& args);
void _CmdVMDeleteRequestedHandler(const Editor::CommandViewModel& senderVM, const IInspectable& args);
void _CmdVMPropagateColorSchemeRequestedHandler(const IInspectable& sender, const Editor::ArgWrapper& wrapper);
};
}

View File

@@ -1,6 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "EnumEntry.idl";
import "ColorSchemeViewModel.idl";
namespace Microsoft.Terminal.Settings.Editor
{
runtimeclass ModifyKeyBindingEventArgs
@@ -11,6 +14,12 @@ namespace Microsoft.Terminal.Settings.Editor
String NewActionName { get; };
}
runtimeclass ModifyKeyChordEventArgs
{
Microsoft.Terminal.Control.KeyChord OldKeys { get; };
Microsoft.Terminal.Control.KeyChord NewKeys { get; };
}
runtimeclass KeyBindingViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
// Settings Model side
@@ -46,15 +55,125 @@ namespace Microsoft.Terminal.Settings.Editor
event Windows.Foundation.TypedEventHandler<KeyBindingViewModel, Microsoft.Terminal.Control.KeyChord> DeleteKeyBindingRequested;
}
runtimeclass CommandViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
// Settings Model side
String Name;
String ID;
Boolean IsUserAction { get; };
// keybindings
IObservableVector<KeyChordViewModel> KeyChordViewModelList { get; };
// action args
ActionArgsViewModel ActionArgsVM { get; };
// View-model specific
String DisplayName { get; };
Boolean IsNewCommand;
// UI side (command list page)
void Edit_Click();
// UI side (edit command page)
IObservableVector<String> AvailableShortcutActions { get; };
Object ProposedShortcutAction;
void Delete_Click();
void AddKeybinding_Click();
event Windows.Foundation.TypedEventHandler<Object, ArgWrapper> PropagateColorSchemeRequested;
}
runtimeclass ArgWrapper : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
String Name { get; };
String Type { get; };
Boolean Required { get; };
IInspectable Value;
IInspectable EnumValue;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> EnumList { get; };
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.FlagEntry> FlagList { get; };
ColorSchemeViewModel DefaultColorScheme;
// unboxing functions
String UnboxString(Object value);
String UnboxGuid(Object value);
UInt32 UnboxInt32(Object value);
Single UnboxInt32Optional(Object value);
UInt32 UnboxUInt32(Object value);
Single UnboxUInt32Optional(Object value);
Single UnboxUInt64(Object value);
Single UnboxFloat(Object value);
Windows.Foundation.IReference<Boolean> UnboxBoolOptional(Object value);
Windows.Foundation.IReference<Microsoft.Terminal.Core.Color> UnboxTerminalCoreColorOptional(Object value);
Windows.Foundation.IReference<Microsoft.Terminal.Core.Color> UnboxWindowsUIColorOptional(Object value);
// bind back functions
void StringBindBack(String newValue);
void GuidBindBack(String newValue);
void Int32BindBack(Double newValue);
void Int32OptionalBindBack(Double newValue);
void UInt32BindBack(Double newValue);
void UInt32OptionalBindBack(Double newValue);
void UInt64BindBack(Double newValue);
void FloatBindBack(Double newValue);
void BoolOptionalBindBack(Windows.Foundation.IReference<Boolean> newValue);
void TerminalCoreColorBindBack(Windows.Foundation.IReference<Microsoft.Terminal.Core.Color> newValue);
void WindowsUIColorBindBack(Windows.Foundation.IReference<Microsoft.Terminal.Core.Color> newValue);
event Windows.Foundation.TypedEventHandler<Object, ArgWrapper> ColorSchemeRequested;
}
runtimeclass ActionArgsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
Boolean HasArgs { get; };
IObservableVector<ArgWrapper> ArgValues;
event Windows.Foundation.TypedEventHandler<Object, Object> WrapperValueChanged;
event Windows.Foundation.TypedEventHandler<Object, ArgWrapper> PropagateColorSchemeRequested;
}
runtimeclass KeyChordViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
String KeyChordText { get; };
// UI side
Microsoft.Terminal.Control.KeyChord ProposedKeys;
Windows.UI.Xaml.Controls.Flyout AcceptChangesFlyout;
Boolean IsInEditMode { get; };
void ToggleEditMode();
void AttemptAcceptChanges();
void CancelChanges();
void DeleteKeyChord();
event Windows.Foundation.TypedEventHandler<KeyChordViewModel, Microsoft.Terminal.Control.KeyChord> AddKeyChordRequested;
event Windows.Foundation.TypedEventHandler<KeyChordViewModel, ModifyKeyChordEventArgs> ModifyKeyChordRequested;
event Windows.Foundation.TypedEventHandler<KeyChordViewModel, Microsoft.Terminal.Control.KeyChord> DeleteKeyChordRequested;
}
enum ActionsSubPage
{
Base = 0,
Edit = 1
};
runtimeclass ActionsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
ActionsViewModel(Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
void UpdateSettings(Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
void OnAutomationPeerAttached();
void AddNewKeybinding();
void AddNewCommand();
IObservableVector<KeyBindingViewModel> KeyBindingList { get; };
event Windows.Foundation.TypedEventHandler<Object, Object> FocusContainer;
event Windows.Foundation.TypedEventHandler<Object, Object> UpdateBackground;
ActionsSubPage CurrentPage;
CommandViewModel CurrentCommand;
void AttemptAddOrModifyKeyChord(KeyChordViewModel senderVM, String commandID, Microsoft.Terminal.Control.KeyChord newKeys, Microsoft.Terminal.Control.KeyChord oldKeys);
void AttemptDeleteKeyChord(Microsoft.Terminal.Control.KeyChord keys);
void AttemptAddCopiedCommand(Microsoft.Terminal.Settings.Model.Command newCommand);
IObservableVector<CommandViewModel> CommandList { get; };
void CmdListItemClicked(IInspectable sender, Windows.UI.Xaml.Controls.ItemClickEventArgs args);
}
}

View File

@@ -59,7 +59,7 @@
<IconSourceElement Grid.Column="0"
Width="16"
Height="16"
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Icon), Mode=OneTime}" />
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(EvaluatedIcon), Mode=OneTime}" />
<TextBlock Grid.Column="1"
Text="{x:Bind Name}" />

View File

@@ -0,0 +1,102 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ArgsTemplateSelectors.h"
#include "ArgsTemplateSelectors.g.cpp"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
Windows::UI::Xaml::DataTemplate ArgsTemplateSelectors::SelectTemplateCore(const winrt::Windows::Foundation::IInspectable& item, const winrt::Windows::UI::Xaml::DependencyObject& /*container*/)
{
return SelectTemplateCore(item);
}
// Method Description:
// - This method is called once command palette decides how to render a filtered command.
// Currently we support two ways to render command, that depend on its palette item type:
// - For TabPalette item we render an icon, a title, and some tab-related indicators like progress bar (as defined by TabItemTemplate)
// - All other items are currently rendered with icon, title and optional key-chord (as defined by GeneralItemTemplate)
// Arguments:
// - item - an instance of filtered command to render
// Return Value:
// - data template to use for rendering
Windows::UI::Xaml::DataTemplate ArgsTemplateSelectors::SelectTemplateCore(const winrt::Windows::Foundation::IInspectable& item)
{
if (const auto argWrapper{ item.try_as<Microsoft::Terminal::Settings::Editor::ArgWrapper>() })
{
const auto argType = argWrapper.Type();
if (argType == L"winrt::hstring")
{
return StringTemplate();
}
else if (argType == L"winrt::guid")
{
return GuidTemplate();
}
else if (argType == L"int32_t")
{
return Int32Template();
}
else if (argType == L"uint32_t")
{
return UInt32Template();
}
else if (argType == L"uint64_t")
{
return UInt64Template();
}
else if (argType == L"float")
{
return FloatTemplate();
}
else if (argType == L"bool" ||
argType == L"Windows::Foundation::IReference<bool>")
{
return BoolOptionalTemplate();
}
else if (argType == L"Windows::Foundation::IReference<int32_t>")
{
return Int32OptionalTemplate();
}
else if (argType == L"Windows::Foundation::IReference<uint32_t>")
{
return UInt32OptionalTemplate();
}
else if (argType == L"Model::ResizeDirection" ||
argType == L"Model::FocusDirection" ||
argType == L"SettingsTarget" ||
argType == L"MoveTabDirection" ||
argType == L"Microsoft::Terminal::Control::ScrollToMarkDirection" ||
argType == L"CommandPaletteLaunchMode" ||
argType == L"FindMatchDirection" ||
argType == L"Model::DesktopBehavior" ||
argType == L"Model::MonitorBehavior" ||
argType == L"winrt::Microsoft::Terminal::Control::ClearBufferType" ||
argType == L"SelectOutputDirection" ||
argType == L"Windows::Foundation::IReference<TabSwitcherMode>" ||
argType == L"Model::SplitDirection" ||
argType == L"SplitType")
{
return EnumTemplate();
}
else if (argType == L"SuggestionsSource")
{
return FlagTemplate();
}
else if (argType == L"Windows::Foundation::IReference<Microsoft::Terminal::Core::Color>")
{
return TerminalCoreColorOptionalTemplate();
}
else if (argType == L"Windows::Foundation::IReference<Windows::UI::Color>")
{
return WindowsUIColorOptionalTemplate();
}
else if (argType == L"Windows::Foundation::IReference<Control::CopyFormat>")
{
return FlagTemplate();
}
}
return NoArgTemplate();
}
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "ArgsTemplateSelectors.g.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct ArgsTemplateSelectors : ArgsTemplateSelectorsT<ArgsTemplateSelectors>
{
ArgsTemplateSelectors() = default;
Windows::UI::Xaml::DataTemplate SelectTemplateCore(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::UI::Xaml::DependencyObject&);
Windows::UI::Xaml::DataTemplate SelectTemplateCore(const winrt::Windows::Foundation::IInspectable&);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, NoArgTemplate);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, GuidTemplate);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, Int32Template);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, Int32OptionalTemplate);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, UInt32Template);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, UInt32OptionalTemplate);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, UInt64Template);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, FloatTemplate);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, StringTemplate);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, BoolOptionalTemplate);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, EnumTemplate);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, FlagTemplate);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, TerminalCoreColorOptionalTemplate);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, WindowsUIColorOptionalTemplate);
};
}
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(ArgsTemplateSelectors);
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.Settings.Editor
{
[default_interface] runtimeclass ArgsTemplateSelectors : Windows.UI.Xaml.Controls.DataTemplateSelector
{
ArgsTemplateSelectors();
Windows.UI.Xaml.DataTemplate NoArgTemplate;
Windows.UI.Xaml.DataTemplate GuidTemplate;
Windows.UI.Xaml.DataTemplate Int32Template;
Windows.UI.Xaml.DataTemplate Int32OptionalTemplate;
Windows.UI.Xaml.DataTemplate UInt32Template;
Windows.UI.Xaml.DataTemplate UInt32OptionalTemplate;
Windows.UI.Xaml.DataTemplate UInt64Template;
Windows.UI.Xaml.DataTemplate FloatTemplate;
Windows.UI.Xaml.DataTemplate StringTemplate;
Windows.UI.Xaml.DataTemplate BoolOptionalTemplate;
Windows.UI.Xaml.DataTemplate EnumTemplate;
Windows.UI.Xaml.DataTemplate FlagTemplate;
Windows.UI.Xaml.DataTemplate TerminalCoreColorOptionalTemplate;
Windows.UI.Xaml.DataTemplate WindowsUIColorOptionalTemplate;
}
}

View File

@@ -1199,7 +1199,7 @@
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
@@ -1222,7 +1222,8 @@
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}" />
<FontIcon Margin="20,0,8,0"
<FontIcon Grid.Column="1"
Margin="20,0,8,0"
HorizontalAlignment="Right"
FontSize="10"
FontWeight="Black"
@@ -1266,4 +1267,24 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="NewInfoBadge"
TargetType="muxc:InfoBadge">
<Setter Property="Padding" Value="5,1,5,2" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="muxc:InfoBadge">
<Border x:Name="RootGrid"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
CornerRadius="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.InfoBadgeCornerRadius}">
<TextBlock x:Uid="NewInfoBadgeTextBlock"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="10" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "EditAction.h"
#include "EditAction.g.cpp"
#include "LibraryResources.h"
#include "../TerminalSettingsModel/AllShortcutActions.h"
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Navigation;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
EditAction::EditAction()
{
InitializeComponent();
_itemTemplateSelector = Resources().Lookup(winrt::box_value(L"ArgsTemplateSelector")).try_as<ArgsTemplateSelectors>();
_listItemTemplate = Resources().Lookup(winrt::box_value(L"ListItemTemplate")).try_as<DataTemplate>();
}
void EditAction::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::CommandViewModel>();
}
void EditAction::_choosingItemContainer(
const Windows::UI::Xaml::Controls::ListViewBase& /*sender*/,
const Windows::UI::Xaml::Controls::ChoosingItemContainerEventArgs& args)
{
const auto dataTemplate = _itemTemplateSelector.SelectTemplate(args.Item());
const auto itemContainer = args.ItemContainer();
if (!itemContainer || itemContainer.ContentTemplate() != dataTemplate)
{
ElementFactoryGetArgs factoryArgs{};
const auto listViewItem = _listItemTemplate.GetElement(factoryArgs).try_as<Controls::ListViewItem>();
listViewItem.ContentTemplate(dataTemplate);
args.ItemContainer(listViewItem);
}
args.IsContainerPrepared(true);
}
}

View File

@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "EditAction.g.h"
#include "ActionsViewModel.h"
#include "Utils.h"
#include "ViewModelHelpers.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct EditAction : public HasScrollViewer<EditAction>, EditActionT<EditAction>
{
public:
EditAction();
void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
til::property_changed_event PropertyChanged;
WINRT_OBSERVABLE_PROPERTY(Editor::CommandViewModel, ViewModel, PropertyChanged.raise, nullptr);
private:
friend struct EditActionT<EditAction>; // for Xaml to bind events
Windows::UI::Xaml::DataTemplate _listItemTemplate;
winrt::Microsoft::Terminal::Settings::Editor::ArgsTemplateSelectors _itemTemplateSelector{ nullptr };
void _choosingItemContainer(const Windows::UI::Xaml::Controls::ListViewBase& sender, const Windows::UI::Xaml::Controls::ChoosingItemContainerEventArgs& args);
};
}
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(EditAction);
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "ActionsViewModel.idl";
namespace Microsoft.Terminal.Settings.Editor
{
[default_interface] runtimeclass EditAction : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged
{
EditAction();
CommandViewModel ViewModel { get; };
}
}

View File

@@ -0,0 +1,636 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information.
-->
<Page x:Class="Microsoft.Terminal.Settings.Editor.EditAction"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.Terminal.Settings.Editor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mtu="using:Microsoft.Terminal.UI"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d">
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="CommonResources.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Theme Dictionary -->
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<!-- KeyChordText styles -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Button">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="1" />
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
<!-- Override visual states -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="border">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAccentRevealBackgroundBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed" />
<VisualState x:Name="Disabled" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<!-- Define the appearance of the button -->
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<!-- KeyChordText styles -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Button">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="1" />
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
<!-- Override visual states -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="border">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAccentRevealBackgroundBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed" />
<VisualState x:Name="Disabled" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<!-- Define the appearance of the button -->
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<!-- KeyChordText styles (use XAML defaults for High Contrast theme) -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Button" />
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<GridLength x:Key="ArgumentNameWidth">148</GridLength>
<!-- Styles -->
<Style x:Key="KeyBindingContainerStyle"
BasedOn="{StaticResource DefaultListViewItemStyle}"
TargetType="ListViewItem">
<Setter Property="Padding" Value="4" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="XYFocusKeyboardNavigation" Value="Enabled" />
</Style>
<Style x:Key="KeyChordEditorStyle"
TargetType="local:KeyChordListener">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<x:Int32 x:Key="EditButtonSize">32</x:Int32>
<x:Double x:Key="EditButtonIconSize">15</x:Double>
<Style x:Key="EditButtonStyle"
BasedOn="{StaticResource DefaultButtonStyle}"
TargetType="Button">
<Setter Property="Padding" Value="0" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Height" Value="{StaticResource EditButtonSize}" />
<Setter Property="Width" Value="{StaticResource EditButtonSize}" />
</Style>
<Style x:Key="AccentEditButtonStyle"
BasedOn="{StaticResource AccentButtonStyle}"
TargetType="Button">
<Setter Property="Padding" Value="3" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Height" Value="{StaticResource EditButtonSize}" />
<Setter Property="Width" Value="{StaticResource EditButtonSize}" />
</Style>
<!-- Templates -->
<DataTemplate x:Key="KeyChordTemplate"
x:DataType="local:KeyChordViewModel">
<ListViewItem Style="{StaticResource KeyBindingContainerStyle}">
<Grid Padding="2,0,2,0"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Style="{ThemeResource KeyChordBorderStyle}"
Click="{x:Bind ToggleEditMode}"
Grid.Column="0"
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(IsInEditMode), Mode=OneWay}">
<TextBlock FontSize="14"
Style="{ThemeResource KeyChordTextBlockStyle}"
Text="{x:Bind KeyChordText, Mode=OneWay}"
TextWrapping="WrapWholeWords"/>
</Button>
<Grid Grid.Column="0"
ColumnSpacing="8"
Visibility="{x:Bind IsInEditMode, Mode=OneWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Edit Mode: Key Chord Listener -->
<local:KeyChordListener Keys="{x:Bind ProposedKeys, Mode=TwoWay}"
Style="{StaticResource KeyChordEditorStyle}"
Grid.Column="0"/>
<!-- Cancel editing the action -->
<Button x:Uid="Actions_CancelButton"
Style="{StaticResource EditButtonStyle}"
Click="{x:Bind CancelChanges}"
Grid.Column="1">
<FontIcon FontSize="{StaticResource EditButtonIconSize}"
Glyph="&#xE711;" />
</Button>
<!-- Accept changes -->
<Button x:Uid="Actions_AcceptButton"
Style="{StaticResource AccentEditButtonStyle}"
Click="{x:Bind AttemptAcceptChanges}"
Flyout="{x:Bind AcceptChangesFlyout, Mode=OneWay}"
Grid.Column="2">
<FontIcon FontSize="{StaticResource EditButtonIconSize}"
Glyph="&#xE8FB;" />
</Button>
</Grid>
<Button Grid.Column="1"
Margin="8,0,0,0"
Style="{StaticResource DeleteSmallButtonStyle}">
<Button.Content>
<FontIcon FontSize="{StaticResource EditButtonIconSize}"
Glyph="&#xE74D;" />
</Button.Content>
<Button.Flyout>
<Flyout>
<StackPanel>
<TextBlock x:Uid="Actions_DeleteConfirmationMessage"
Style="{StaticResource CustomFlyoutTextStyle}" />
<Button x:Uid="Actions_DeleteConfirmationButton"
Click="{x:Bind DeleteKeyChord}" />
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</Grid>
</ListViewItem>
</DataTemplate>
<DataTemplate x:Key="ListItemTemplate"
x:DataType="local:ArgWrapper">
<ListViewItem HorizontalContentAlignment="Stretch" />
</DataTemplate>
<DataTemplate x:Key="NoArgTemplate"
x:DataType="local:ArgWrapper">
<ListViewItem />
</DataTemplate>
<DataTemplate x:Key="Int32Template"
x:DataType="local:ArgWrapper">
<ListViewItem>
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}"
VerticalAlignment="Center"
Grid.Column="0"/>
<muxc:NumberBox Value="{x:Bind UnboxInt32(Value), Mode=TwoWay, BindBack=Int32BindBack}"
Style="{StaticResource NumberBoxSettingStyle}"
Minimum="0"
Maximum="999"
LargeChange="1"
SmallChange="1"
Grid.Column="1"/>
</Grid>
</ListViewItem>
</DataTemplate>
<DataTemplate x:Key="UInt32Template"
x:DataType="local:ArgWrapper">
<ListViewItem>
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}"
VerticalAlignment="Center"
Grid.Column="0"/>
<muxc:NumberBox Value="{x:Bind UnboxUInt32(Value), Mode=TwoWay, BindBack=UInt32BindBack}"
Style="{StaticResource NumberBoxSettingStyle}"
Minimum="0"
Maximum="999"
LargeChange="1"
SmallChange="1"
Grid.Column="1"/>
</Grid>
</ListViewItem>
</DataTemplate>
<DataTemplate x:Key="UInt32OptionalTemplate"
x:DataType="local:ArgWrapper">
<ListViewItem>
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}"
VerticalAlignment="Center"
Grid.Column="0"/>
<muxc:NumberBox Value="{x:Bind UnboxUInt32Optional(Value), Mode=TwoWay, BindBack=UInt32OptionalBindBack}"
Style="{StaticResource NumberBoxSettingStyle}"
Minimum="0"
Maximum="999"
LargeChange="1"
SmallChange="1"
Grid.Column="1"/>
</Grid>
</ListViewItem>
</DataTemplate>
<DataTemplate x:Key="UInt64Template"
x:DataType="local:ArgWrapper">
<ListViewItem>
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}"
VerticalAlignment="Center"
Grid.Column="0"/>
<muxc:NumberBox Value="{x:Bind UnboxUInt64(Value), Mode=TwoWay, BindBack=UInt64BindBack}"
Style="{StaticResource NumberBoxSettingStyle}"
Minimum="0"
Maximum="999"
LargeChange="1"
SmallChange="1"
Grid.Column="1"/>
</Grid>
</ListViewItem>
</DataTemplate>
<DataTemplate x:Key="Int32OptionalTemplate"
x:DataType="local:ArgWrapper">
<ListViewItem>
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}"
VerticalAlignment="Center"
Grid.Column="0"/>
<muxc:NumberBox Value="{x:Bind UnboxInt32Optional(Value), Mode=TwoWay, BindBack=Int32OptionalBindBack}"
Style="{StaticResource NumberBoxSettingStyle}"
Minimum="0"
Maximum="999"
LargeChange="1"
SmallChange="1"
Grid.Column="1"/>
</Grid>
</ListViewItem>
</DataTemplate>
<DataTemplate x:Key="FloatTemplate"
x:DataType="local:ArgWrapper">
<ListViewItem>
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}"
VerticalAlignment="Center"
Grid.Column="0"/>
<muxc:NumberBox Value="{x:Bind UnboxFloat(Value), Mode=TwoWay, BindBack=FloatBindBack}"
Style="{StaticResource NumberBoxSettingStyle}"
Minimum="0"
Maximum="999"
LargeChange="1"
SmallChange="1"
Grid.Column="1"/>
</Grid>
</ListViewItem>
</DataTemplate>
<DataTemplate x:Key="StringTemplate"
x:DataType="local:ArgWrapper">
<ListViewItem>
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}"
VerticalAlignment="Center"
Grid.Column="0"/>
<TextBox Text="{x:Bind UnboxString(Value), Mode=TwoWay, BindBack=StringBindBack}"
Grid.Column="1"/>
</Grid>
</ListViewItem>
</DataTemplate>
<DataTemplate x:Key="GuidTemplate"
x:DataType="local:ArgWrapper">
<ListViewItem>
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}"
VerticalAlignment="Center"
Grid.Column="0"/>
<TextBox Text="{x:Bind UnboxGuid(Value), Mode=TwoWay, BindBack=GuidBindBack}"
Grid.Column="1"/>
</Grid>
</ListViewItem>
</DataTemplate>
<DataTemplate x:Key="BoolOptionalTemplate"
x:DataType="local:ArgWrapper">
<ListViewItem>
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}"
VerticalAlignment="Center"
Grid.Column="0"/>
<CheckBox IsChecked="{x:Bind UnboxBoolOptional(Value), Mode=TwoWay, BindBack=BoolOptionalBindBack}"
IsThreeState="True"
Grid.Column="1"/>
</Grid>
</ListViewItem>
</DataTemplate>
<DataTemplate x:Key="EnumComboBoxTemplate"
x:DataType="local:EnumEntry">
<TextBlock Text="{x:Bind EnumName, Mode=OneWay}" />
</DataTemplate>
<DataTemplate x:Key="EnumTemplate"
x:DataType="local:ArgWrapper">
<ListViewItem>
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}"
VerticalAlignment="Center"
Grid.Column="0"/>
<ComboBox ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind EnumList, Mode=OneWay}"
SelectedItem="{x:Bind EnumValue, Mode=TwoWay}"
Style="{StaticResource ComboBoxSettingStyle}"
Grid.Column="1"/>
</Grid>
</ListViewItem>
</DataTemplate>
<DataTemplate x:Key="FlagItemTemplate"
x:DataType="local:FlagEntry">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{x:Bind IsSet, Mode=TwoWay}"
Grid.Column="0" />
<TextBlock Text="{x:Bind FlagName, Mode=OneWay}"
Grid.Column="1"
VerticalAlignment="Center"
Padding="0,0,0,4"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="FlagTemplate"
x:DataType="local:ArgWrapper">
<ListViewItem>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}"
VerticalAlignment="Center"
Grid.Column="0"/>
<ListView ItemTemplate="{StaticResource FlagItemTemplate}"
SelectionMode="None"
ItemsSource="{x:Bind FlagList, Mode=OneWay}"
Grid.Column="1"
HorizontalAlignment="Left"
Padding="-8,0,0,0"
Margin="0"/>
</Grid>
</ListViewItem>
</DataTemplate>
<DataTemplate x:Key="TerminalCoreColorOptionalTemplate"
x:DataType="local:ArgWrapper">
<ListViewItem>
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}"
VerticalAlignment="Center"
Grid.Column="0"/>
<local:NullableColorPicker x:Uid="Actions_NullableColorPicker"
CurrentColor="{x:Bind UnboxTerminalCoreColorOptional(Value), Mode=TwoWay, BindBack=TerminalCoreColorBindBack}"
ColorSchemeVM="{x:Bind DefaultColorScheme, Mode=OneWay}"
NullColorPreview="{x:Bind DefaultColorScheme.ForegroundColor.Color, Mode=OneWay}"
Grid.Column="1"/>
</Grid>
</ListViewItem>
</DataTemplate>
<DataTemplate x:Key="WindowsUIColorOptionalTemplate"
x:DataType="local:ArgWrapper">
<ListViewItem>
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ArgumentNameWidth}" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}"
VerticalAlignment="Center"
Grid.Column="0"/>
<local:NullableColorPicker x:Uid="Actions_NullableColorPicker"
CurrentColor="{x:Bind UnboxWindowsUIColorOptional(Value), Mode=TwoWay, BindBack=WindowsUIColorBindBack}"
ColorSchemeVM="{x:Bind DefaultColorScheme, Mode=OneWay}"
NullColorPreview="{x:Bind DefaultColorScheme.ForegroundColor.Color, Mode=OneWay}"
Grid.Column="1"/>
</Grid>
</ListViewItem>
</DataTemplate>
<local:ArgsTemplateSelectors x:Key="ArgsTemplateSelector"
NoArgTemplate="{StaticResource NoArgTemplate}"
Int32Template="{StaticResource Int32Template}"
Int32OptionalTemplate="{StaticResource Int32OptionalTemplate}"
UInt32Template="{StaticResource UInt32Template}"
UInt32OptionalTemplate="{StaticResource UInt32OptionalTemplate}"
UInt64Template="{StaticResource UInt64Template}"
FloatTemplate="{StaticResource FloatTemplate}"
StringTemplate="{StaticResource StringTemplate}"
GuidTemplate="{StaticResource GuidTemplate}"
BoolOptionalTemplate="{StaticResource BoolOptionalTemplate}"
EnumTemplate="{StaticResource EnumTemplate}"
FlagTemplate="{StaticResource FlagTemplate}"
TerminalCoreColorOptionalTemplate="{StaticResource TerminalCoreColorOptionalTemplate}"
WindowsUIColorOptionalTemplate="{StaticResource WindowsUIColorOptionalTemplate}" />
</ResourceDictionary>
</Page.Resources>
<Border MaxWidth="{StaticResource StandardControlMaxWidth}">
<Grid MaxWidth="600"
HorizontalAlignment="Left"
RowSpacing="8"
ColumnSpacing="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Uid="Actions_Name"
VerticalAlignment="Center"
Grid.Column="0"
Grid.Row="0"/>
<TextBox Text="{x:Bind ViewModel.Name, Mode=TwoWay}"
PlaceholderText="{x:Bind ViewModel.DisplayName, Mode=OneWay}"
Grid.Row="0"
Grid.Column="1"/>
<TextBlock x:Uid="Actions_ShortcutAction"
VerticalAlignment="Center"
Grid.Column="0"
Grid.Row="1"/>
<ComboBox VerticalAlignment="Center"
ItemsSource="{x:Bind ViewModel.AvailableShortcutActions, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.ProposedShortcutAction, Mode=TwoWay}"
Grid.Row="1"
Grid.Column="1"/>
<TextBlock x:Uid="Actions_Arguments"
VerticalAlignment="Center"
Grid.Column="0"
Grid.Row="2"
Visibility="{x:Bind ViewModel.ActionArgsVM.HasArgs, Mode=OneWay}"/>
<ListView ItemsSource="{x:Bind ViewModel.ActionArgsVM.ArgValues, Mode=OneWay}"
ChoosingItemContainer="_choosingItemContainer"
SelectionMode="None"
AllowDrop="False"
CanDragItems="False"
CanReorderItems="False"
Grid.Row="2"
Grid.Column="1"
Margin="-28,0,0,0">
</ListView>
<TextBlock x:Uid="Actions_Keybindings"
VerticalAlignment="Center"
Grid.Column="0"
Grid.Row="3"/>
<ListView ItemTemplate="{StaticResource KeyChordTemplate}"
ItemsSource="{x:Bind ViewModel.KeyChordViewModelList, Mode=OneWay}"
SelectionMode="None"
Grid.Row="3"
Grid.Column="1">
<ListView.Header>
<Button Click="{x:Bind ViewModel.AddKeybinding_Click}">
<TextBlock x:Uid="Actions_AddKeyChord" />
</Button>
</ListView.Header>
</ListView>
<Button Style="{StaticResource DeleteButtonStyle}"
IsEnabled="{x:Bind ViewModel.IsUserAction}"
Grid.Row="4"
Grid.Column="0">
<Button.Content>
<StackPanel Orientation="Horizontal">
<FontIcon FontSize="{StaticResource StandardIconSize}"
Glyph="&#xE74D;" />
<TextBlock x:Uid="Actions_DeleteButton2"
Style="{StaticResource IconButtonTextBlockStyle}" />
</StackPanel>
</Button.Content>
<Button.Flyout>
<Flyout>
<StackPanel>
<TextBlock x:Uid="Actions_CommandDeleteConfirmationMessage"
Style="{StaticResource CustomFlyoutTextStyle}" />
<Button x:Uid="Actions_CommandDeleteConfirmationButton"
Click="{x:Bind ViewModel.Delete_Click}" />
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</Grid>
</Border>
</Page>

View File

@@ -17,6 +17,7 @@ Author(s):
#pragma once
#include "EnumEntry.g.h"
#include "FlagEntry.g.h"
#include "Utils.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
@@ -55,4 +56,41 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, EnumName, PropertyChanged.raise);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::Foundation::IInspectable, EnumValue, PropertyChanged.raise);
};
template<typename T>
struct FlagEntryComparator
{
bool operator()(const Editor::FlagEntry& lhs, const Editor::FlagEntry& rhs) const
{
return lhs.FlagValue().as<T>() < rhs.FlagValue().as<T>();
}
};
template<typename T>
struct FlagEntryReverseComparator
{
bool operator()(const Editor::FlagEntry& lhs, const Editor::FlagEntry& rhs) const
{
return lhs.FlagValue().as<T>() > rhs.FlagValue().as<T>();
}
};
struct FlagEntry : FlagEntryT<FlagEntry>
{
public:
FlagEntry(const winrt::hstring flagName, const winrt::Windows::Foundation::IInspectable& flagValue, const bool isSet) :
_FlagName{ flagName },
_FlagValue{ flagValue },
_IsSet{ isSet } {}
hstring ToString()
{
return FlagName();
}
til::property_changed_event PropertyChanged;
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, FlagName, PropertyChanged.raise);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::Foundation::IInspectable, FlagValue, PropertyChanged.raise);
WINRT_OBSERVABLE_PROPERTY(bool, IsSet, PropertyChanged.raise);
};
}

View File

@@ -8,4 +8,11 @@ namespace Microsoft.Terminal.Settings.Editor
String EnumName { get; };
IInspectable EnumValue { get; };
}
[default_interface] runtimeclass FlagEntry : Windows.UI.Xaml.Data.INotifyPropertyChanged, Windows.Foundation.IStringable
{
String FlagName { get; };
IInspectable FlagValue { get; };
Boolean IsSet;
}
}

View File

@@ -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 <LibraryResources.h>
#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<Editor::ExtensionPackageTemplateSelector>();
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<Editor::ExtensionsViewModel>();
auto vmImpl = get_self<ExtensionsViewModel>(_ViewModel);
vmImpl->ExtensionPackageIdentifierTemplateSelector(_extensionPackageIdentifierTemplateSelector);
vmImpl->LazyLoadExtensions();
vmImpl->MarkAsVisited();
}
void Extensions::ExtensionNavigator_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/)
{
const auto extPkgVM = sender.as<Controls::Button>().Tag().as<Editor::ExtensionPackageViewModel>();
_ViewModel.CurrentExtensionPackage(extPkgVM);
}
void Extensions::NavigateToProfile_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/)
{
const auto& profileGuid = sender.as<Controls::Button>().Tag().as<guid>();
get_self<ExtensionsViewModel>(_ViewModel)->NavigateToProfile(profileGuid);
}
void Extensions::NavigateToColorScheme_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/)
{
const auto& schemeVM = sender.as<Controls::Button>().Tag().as<Editor::ColorSchemeViewModel>();
get_self<ExtensionsViewModel>(_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 colorSchmesAddedChanged = viewModelProperty == L"ColorSchemesAdded";
if (extensionPackageChanged || (!IsExtensionView() && (profilesModifiedChanged || profilesAddedChanged || colorSchmesAddedChanged)))
{
// 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 || colorSchmesAddedChanged;
_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 (colorSchmesAddedChanged)
{
_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<Editor::FragmentProfileViewModel> profilesModifiedTotal;
std::vector<Editor::FragmentProfileViewModel> profilesAddedTotal;
std::vector<Editor::FragmentColorSchemeViewModel> 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<ExtensionPackageViewModel>(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<ExtensionPackageViewModel>(extPkg)->UpdateSettings(_settings);
}
}
}
void ExtensionsViewModel::LazyLoadExtensions()
{
if (_extensionsLoaded)
{
return;
}
std::vector<Model::ExtensionPackage> extensions = wil::to_vector(_settings.Extensions());
// these vectors track components all extensions successfully added
std::vector<Editor::ExtensionPackageViewModel> extensionPackages;
std::vector<Editor::FragmentProfileViewModel> profilesModifiedTotal;
std::vector<Editor::FragmentProfileViewModel> profilesAddedTotal;
std::vector<Editor::FragmentColorSchemeViewModel> colorSchemesAddedTotal;
for (const auto& extPkg : extensions)
{
auto extPkgVM = winrt::make_self<ExtensionPackageViewModel>(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<Editor::FragmentProfileViewModel> currentProfilesModified;
std::vector<Editor::FragmentProfileViewModel> currentProfilesAdded;
std::vector<Editor::FragmentColorSchemeViewModel> 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<FragmentProfileViewModel>(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<FragmentProfileViewModel>(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<FragmentColorSchemeViewModel>(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<FragmentExtensionViewModel>(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<ExtensionPackageViewModel>()->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<Editor::ExtensionPackageViewModel>(std::move(extensionPackages));
_profilesModifiedView = single_threaded_observable_vector<Editor::FragmentProfileViewModel>(std::move(profilesModifiedTotal));
_profilesAddedView = single_threaded_observable_vector<Editor::FragmentProfileViewModel>(std::move(profilesAddedTotal));
_colorSchemesAddedView = single_threaded_observable_vector<Editor::FragmentColorSchemeViewModel>(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<hstring> disabledProfileSources{ extensionSource };
settings.GlobalSettings().DisabledProfileSources(single_threaded_vector<hstring>(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:
// "<DisplayName?>, <Source>"
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<Editor::ExtensionPackageViewModel>())
{
if (!extPkgVM.Package().DisplayName().empty())
{
return ComplexTemplate();
}
return DefaultTemplate();
}
return nullptr;
}
}

View File

@@ -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<Extensions>, ExtensionsT<Extensions>
{
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<ExtensionsViewModel>, ViewModelHelper<ExtensionsViewModel>
{
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<Editor::ExtensionPackageViewModel> ExtensionPackages() const noexcept { return _extensionPackages; }
Windows::Foundation::Collections::IObservableVector<Editor::FragmentProfileViewModel> ProfilesModified() const noexcept { return _profilesModifiedView; }
Windows::Foundation::Collections::IObservableVector<Editor::FragmentProfileViewModel> ProfilesAdded() const noexcept { return _profilesAddedView; }
Windows::Foundation::Collections::IObservableVector<Editor::FragmentColorSchemeViewModel> 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<IInspectable, guid> NavigateToProfileRequested;
til::typed_event<IInspectable, Editor::ColorSchemeViewModel> 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<Editor::ExtensionPackageViewModel> _extensionPackages;
Windows::Foundation::Collections::IObservableVector<Editor::FragmentProfileViewModel> _profilesModifiedView;
Windows::Foundation::Collections::IObservableVector<Editor::FragmentProfileViewModel> _profilesAddedView;
Windows::Foundation::Collections::IObservableVector<Editor::FragmentColorSchemeViewModel> _colorSchemesAddedView;
bool _extensionsLoaded;
void _UpdateListViews(bool updateProfilesModified, bool updateProfilesAdded, bool updateColorSchemesAdded);
};
struct ExtensionPackageViewModel : ExtensionPackageViewModelT<ExtensionPackageViewModel>, ViewModelHelper<ExtensionPackageViewModel>
{
public:
ExtensionPackageViewModel(const Model::ExtensionPackage& pkg, const Model::CascadiaSettings& settings) :
_package{ pkg },
_settings{ settings },
_fragmentExtensions{ single_threaded_observable_vector<Editor::FragmentExtensionViewModel>() } {}
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<Editor::FragmentExtensionViewModel> FragmentExtensions() { return _fragmentExtensions; }
private:
Model::ExtensionPackage _package;
Model::CascadiaSettings _settings;
Windows::Foundation::Collections::IObservableVector<Editor::FragmentExtensionViewModel> _fragmentExtensions;
};
struct FragmentExtensionViewModel : FragmentExtensionViewModelT<FragmentExtensionViewModel>, ViewModelHelper<FragmentExtensionViewModel>
{
public:
FragmentExtensionViewModel(const Model::FragmentSettings& fragment,
std::vector<FragmentProfileViewModel>& profilesModified,
std::vector<FragmentProfileViewModel>& profilesAdded,
std::vector<FragmentColorSchemeViewModel>& 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<FragmentProfileViewModel> ProfilesModified() const noexcept { return _profilesModified.GetView(); }
Windows::Foundation::Collections::IVectorView<FragmentProfileViewModel> ProfilesAdded() const noexcept { return _profilesAdded.GetView(); }
Windows::Foundation::Collections::IVectorView<FragmentColorSchemeViewModel> ColorSchemesAdded() const noexcept { return _colorSchemesAdded.GetView(); }
private:
Model::FragmentSettings _fragment;
Windows::Foundation::Collections::IVector<FragmentProfileViewModel> _profilesModified;
Windows::Foundation::Collections::IVector<FragmentProfileViewModel> _profilesAdded;
Windows::Foundation::Collections::IVector<FragmentColorSchemeViewModel> _colorSchemesAdded;
};
struct FragmentProfileViewModel : FragmentProfileViewModelT<FragmentProfileViewModel>, ViewModelHelper<FragmentProfileViewModel>
{
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<FragmentColorSchemeViewModel>, ViewModelHelper<FragmentColorSchemeViewModel>
{
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<ExtensionPackageTemplateSelector>
{
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);
}

View File

@@ -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<ExtensionPackageViewModel> ExtensionPackages { get; };
IObservableVector<FragmentProfileViewModel> ProfilesModified { get; };
IObservableVector<FragmentProfileViewModel> ProfilesAdded { get; };
IObservableVector<FragmentColorSchemeViewModel> ColorSchemesAdded { get; };
// Methods
void UpdateSettings(Microsoft.Terminal.Settings.Model.CascadiaSettings settings, ColorSchemesPageViewModel colorSchemesPageVM);
event Windows.Foundation.TypedEventHandler<Object, Guid> NavigateToProfileRequested;
event Windows.Foundation.TypedEventHandler<Object, ColorSchemeViewModel> 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; };
IVector<FragmentExtensionViewModel> FragmentExtensions { get; };
}
[default_interface] runtimeclass FragmentExtensionViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
Microsoft.Terminal.Settings.Model.FragmentSettings Fragment { get; };
IVectorView<FragmentProfileViewModel> ProfilesModified { get; };
IVectorView<FragmentProfileViewModel> ProfilesAdded { get; };
IVectorView<FragmentColorSchemeViewModel> 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;
}
}

View File

@@ -0,0 +1,497 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information.
-->
<Page x:Class="Microsoft.Terminal.Settings.Editor.Extensions"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.Terminal.Settings.Editor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:Microsoft.Terminal.Settings.Model"
xmlns:mtu="using:Microsoft.Terminal.UI"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d">
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="CommonResources.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style x:Key="ItalicDisclaimerStyle"
BasedOn="{StaticResource DisclaimerStyle}"
TargetType="TextBlock">
<Setter Property="FontStyle" Value="Italic" />
</Style>
<Style x:Key="CodeBlockStyle"
TargetType="TextBlock">
<Setter Property="FontFamily" Value="Cascadia Mono, Consolas" />
<Setter Property="IsTextSelectionEnabled" Value="True" />
</Style>
<Style x:Key="CodeBlockScrollViewerStyle"
TargetType="ScrollViewer">
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw" />
<Setter Property="HorizontalScrollMode" Value="Auto" />
<Setter Property="HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="VerticalScrollMode" Value="Disabled" />
<Setter Property="VerticalScrollBarVisibility" Value="Disabled" />
</Style>
<local:ExtensionPackageTemplateSelector x:Key="ExtensionPackageIdentifierTemplateSelector"
ComplexTemplate="{StaticResource ComplexExtensionIdentifierTemplate}"
DefaultTemplate="{StaticResource DefaultExtensionIdentifierTemplate}" />
<DataTemplate x:Key="DefaultExtensionIdentifierTemplate"
x:DataType="local:ExtensionPackageViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<FontIcon Grid.Column="0"
Margin="0,0,8,0"
FontSize="32"
Glyph="&#xE74C;" />
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
Text="{x:Bind Package.Source}" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="ComplexExtensionIdentifierTemplate"
x:DataType="local:ExtensionPackageViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<IconSourceElement Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Width="32"
Height="32"
Margin="0,0,8,0"
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Package.Icon)}" />
<TextBlock Grid.Row="0"
Grid.Column="1"
Text="{x:Bind Package.DisplayName}" />
<TextBlock Grid.Row="1"
Grid.Column="1"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
Text="{x:Bind Package.Source}" />
</Grid>
</DataTemplate>
<local:ExtensionPackageTemplateSelector x:Key="ExtensionPackageNavigatorTemplateSelector"
ComplexTemplate="{StaticResource ComplexExtensionNavigatorTemplate}"
DefaultTemplate="{StaticResource DefaultExtensionNavigatorTemplate}" />
<DataTemplate x:Key="DefaultExtensionNavigatorTemplate"
x:DataType="local:ExtensionPackageViewModel">
<Button AutomationProperties.Name="{x:Bind AccessibleName}"
Click="ExtensionNavigator_Click"
Style="{StaticResource NavigatorButtonStyle}"
Tag="{x:Bind}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{x:Bind}"
ContentTemplate="{StaticResource DefaultExtensionIdentifierTemplate}" />
<ToggleSwitch Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind AccessibleName}"
IsOn="{x:Bind Enabled, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</Grid>
</Button>
</DataTemplate>
<DataTemplate x:Key="ComplexExtensionNavigatorTemplate"
x:DataType="local:ExtensionPackageViewModel">
<Button AutomationProperties.Name="{x:Bind AccessibleName}"
Click="ExtensionNavigator_Click"
Style="{StaticResource NavigatorButtonStyle}"
Tag="{x:Bind}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{x:Bind}"
ContentTemplate="{StaticResource ComplexExtensionIdentifierTemplate}" />
<ToggleSwitch Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind AccessibleName}"
IsOn="{x:Bind Enabled, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</Grid>
</Button>
</DataTemplate>
<DataTemplate x:Key="FragmentProfileViewModelTemplate"
x:DataType="local:FragmentProfileViewModel">
<muxc:Expander AutomationProperties.Name="{x:Bind AccessibleName}"
Style="{StaticResource ExpanderStyle}">
<muxc:Expander.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Orientation="Horizontal">
<IconSourceElement Width="16"
Height="16"
Margin="0,0,8,0"
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Profile.EvaluatedIcon), Mode=OneWay}" />
<TextBlock Text="{x:Bind Profile.Name, Mode=OneWay}" />
<Button x:Name="NavigateToProfileButton"
x:Uid="Extensions_NavigateToProfileButton"
Click="NavigateToProfile_Click"
Style="{StaticResource SettingContainerResetButtonStyle}"
Tag="{x:Bind Profile.Guid}"
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(Profile.Deleted)}">
<FontIcon Glyph="&#xE8A7;"
Style="{StaticResource SettingContainerFontIconStyle}" />
</Button>
</StackPanel>
<TextBlock Grid.Column="1"
Style="{StaticResource SettingContainerCurrentValueTextBlockStyle}"
Text="{x:Bind SourceName}" />
</Grid>
</muxc:Expander.Header>
<muxc:Expander.Content>
<ScrollViewer Style="{StaticResource CodeBlockScrollViewerStyle}">
<TextBlock Style="{StaticResource CodeBlockStyle}"
Text="{x:Bind Json, Mode=OneWay}" />
</ScrollViewer>
</muxc:Expander.Content>
</muxc:Expander>
</DataTemplate>
<!-- This styling matches that of ExpanderSettingContainerStyle for consistency -->
<Style x:Key="ExpanderStyle"
TargetType="muxc:Expander">
<Setter Property="MaxWidth" Value="1000" />
<Setter Property="MinHeight" Value="64" />
<Setter Property="Margin" Value="0,4,0,0" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
<DataTemplate x:Key="JsonTemplate"
x:DataType="local:FragmentExtensionViewModel">
<muxc:Expander Header="{x:Bind Fragment.Filename}"
Style="{StaticResource ExpanderStyle}">
<ScrollViewer Style="{StaticResource CodeBlockScrollViewerStyle}">
<TextBlock Style="{StaticResource CodeBlockStyle}"
Text="{x:Bind Fragment.Json}" />
</ScrollViewer>
</muxc:Expander>
</DataTemplate>
<!--
Copied over from Appearances.xaml. We're unable to add the DataTemplate to CommonResources.xaml
because it needs a code-behind class to use {x:Bind}
-->
<DataTemplate x:Key="ColorChipTemplate"
x:DataType="local:ColorTableEntry">
<Border Width="8"
Height="8"
Background="{x:Bind mtu:Converters.ColorToBrush(Color)}"
CornerRadius="1" />
</DataTemplate>
<!--
Copied over from Appearances.xaml. We're unable to add the DataTemplate to CommonResources.xaml
because it needs a code-behind class to use {x:Bind}
-->
<DataTemplate x:Key="ColorSchemeVMTemplate"
x:DataType="local:ColorSchemeViewModel">
<StackPanel Orientation="Horizontal">
<Grid Grid.Column="0"
Padding="8"
VerticalAlignment="Center"
Background="{x:Bind mtu:Converters.ColorToBrush(BackgroundColor.Color), Mode=OneWay}"
ColumnSpacing="1"
CornerRadius="2"
RowSpacing="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl Grid.Row="0"
Grid.Column="0"
Content="{x:Bind ColorEntryAt(0), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="0"
Grid.Column="1"
Content="{x:Bind ColorEntryAt(1), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="0"
Grid.Column="2"
Content="{x:Bind ColorEntryAt(2), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="0"
Grid.Column="3"
Content="{x:Bind ColorEntryAt(3), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="0"
Grid.Column="4"
Content="{x:Bind ColorEntryAt(4), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="0"
Grid.Column="5"
Content="{x:Bind ColorEntryAt(5), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="0"
Grid.Column="6"
Content="{x:Bind ColorEntryAt(6), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="0"
Grid.Column="7"
Content="{x:Bind ColorEntryAt(7), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="1"
Grid.Column="0"
Content="{x:Bind ColorEntryAt(8), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="1"
Grid.Column="1"
Content="{x:Bind ColorEntryAt(9), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="1"
Grid.Column="2"
Content="{x:Bind ColorEntryAt(10), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="1"
Grid.Column="3"
Content="{x:Bind ColorEntryAt(11), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="1"
Grid.Column="4"
Content="{x:Bind ColorEntryAt(12), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="1"
Grid.Column="5"
Content="{x:Bind ColorEntryAt(13), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="1"
Grid.Column="6"
Content="{x:Bind ColorEntryAt(14), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<ContentControl Grid.Row="1"
Grid.Column="7"
Content="{x:Bind ColorEntryAt(15), Mode=OneWay}"
ContentTemplate="{StaticResource ColorChipTemplate}"
IsTabStop="False" />
<TextBlock Grid.RowSpan="2"
Grid.Column="8"
MaxWidth="192"
Margin="4,0,4,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
FontFamily="Cascadia Code"
Foreground="{x:Bind mtu:Converters.ColorToBrush(ForegroundColor.Color), Mode=OneWay}"
Text="{x:Bind Name, Mode=OneWay}"
TextTrimming="WordEllipsis" />
</Grid>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="FragmentColorSchemeViewModelTemplate"
x:DataType="local:FragmentColorSchemeViewModel">
<muxc:Expander AutomationProperties.Name="{x:Bind AccessibleName}"
Style="{StaticResource ExpanderStyle}">
<muxc:Expander.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Orientation="Horizontal">
<ContentPresenter Content="{x:Bind ColorSchemeVM, Mode=OneWay}"
ContentTemplate="{StaticResource ColorSchemeVMTemplate}" />
<Button x:Name="NavigateToColorSchemeButton"
x:Uid="Extensions_NavigateToColorSchemeButton"
Click="NavigateToColorScheme_Click"
Style="{StaticResource SettingContainerResetButtonStyle}"
Tag="{x:Bind ColorSchemeVM}">
<FontIcon Glyph="&#xE8A7;"
Style="{StaticResource SettingContainerFontIconStyle}" />
</Button>
</StackPanel>
<TextBlock Grid.Column="1"
Style="{StaticResource SettingContainerCurrentValueTextBlockStyle}"
Text="{x:Bind SourceName}" />
</Grid>
</muxc:Expander.Header>
<muxc:Expander.Content>
<ScrollViewer Style="{StaticResource CodeBlockScrollViewerStyle}">
<TextBlock Style="{StaticResource CodeBlockStyle}"
Text="{x:Bind Json, Mode=OneWay}" />
</ScrollViewer>
</muxc:Expander.Content>
</muxc:Expander>
</DataTemplate>
</ResourceDictionary>
</Page.Resources>
<StackPanel Style="{StaticResource SettingsStackStyle}">
<!-- [Root View Only] -->
<StackPanel MaxWidth="{StaticResource StandardControlMaxWidth}"
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(ViewModel.IsExtensionView), Mode=OneWay}">
<!-- Learn more about fragment extensions -->
<HyperlinkButton x:Uid="Extensions_DisclaimerHyperlink"
NavigateUri="https://learn.microsoft.com/en-us/windows/terminal/json-fragment-extensions" />
<!-- Grouping: Active Extensions -->
<TextBlock x:Uid="Extensions_ActiveExtensionsHeader"
Style="{StaticResource TextBlockSubHeaderStyle}" />
<ItemsControl x:Name="ActiveExtensionsList"
IsTabStop="False"
ItemTemplateSelector="{StaticResource ExtensionPackageNavigatorTemplateSelector}"
ItemsSource="{x:Bind ViewModel.ExtensionPackages}"
XYFocusKeyboardNavigation="Enabled" />
</StackPanel>
<!-- [Extension View Only] -->
<StackPanel MaxWidth="{StaticResource StandardControlMaxWidth}"
Visibility="{x:Bind ViewModel.IsExtensionView, Mode=OneWay}">
<!-- Extension Status -->
<muxc:Expander AutomationProperties.Name="{x:Bind ViewModel.CurrentExtensionPackage.AccessibleName, Mode=OneWay}"
IsExpanded="True"
Style="{StaticResource ExpanderStyle}">
<muxc:Expander.Header>
<Grid MinHeight="64">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!--
BODGY
Theoretically, you could use a ContentTemplateSelector directly. However, that doesn't work.
For some reason, we just get the object type's ToString called and the selector gets nullptr as a parameter.
Adding the template as a view model property is a workaround.
-->
<ContentPresenter Grid.Column="0"
VerticalAlignment="Center"
Content="{x:Bind ViewModel.CurrentExtensionPackage, Mode=OneWay}"
ContentTemplate="{x:Bind ViewModel.CurrentExtensionPackageIdentifierTemplate, Mode=OneWay}" />
<ToggleSwitch Grid.Column="1"
Margin="0"
AutomationProperties.Name="{x:Bind ViewModel.CurrentExtensionPackage.AccessibleName, Mode=OneWay}"
IsOn="{x:Bind ViewModel.CurrentExtensionPackage.Enabled, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}"
Tag="{x:Bind ViewModel.CurrentExtensionPackage.Package.Source, Mode=OneWay}" />
</Grid>
</muxc:Expander.Header>
<muxc:Expander.Content>
<StackPanel>
<!-- Scope -->
<local:SettingContainer x:Uid="Extensions_Scope"
Content="{x:Bind ViewModel.CurrentExtensionPackage.Scope, Mode=OneWay}"
IsTabStop="False"
Style="{StaticResource SettingContainerWithTextContent}" />
<!-- JSON -->
<ItemsControl IsTabStop="False"
ItemTemplate="{StaticResource JsonTemplate}"
ItemsSource="{x:Bind ViewModel.CurrentExtensionPackage.FragmentExtensions, Mode=OneWay}" />
</StackPanel>
</muxc:Expander.Content>
</muxc:Expander>
</StackPanel>
<!-- Grouping: Modified Profiles -->
<StackPanel Margin="{x:Bind CalculateMargin(ViewModel.NoProfilesModified), Mode=OneWay}"
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(ViewModel.NoProfilesModified), Mode=OneWay}">
<TextBlock x:Uid="Extensions_ModifiedProfilesHeader"
Style="{StaticResource TextBlockSubHeaderStyle}" />
<ItemsControl x:Name="ModifiedProfilesList"
IsTabStop="False"
ItemTemplate="{StaticResource FragmentProfileViewModelTemplate}"
ItemsSource="{x:Bind ViewModel.ProfilesModified, Mode=OneWay}"
XYFocusKeyboardNavigation="Enabled" />
</StackPanel>
<!-- Grouping: Added Profiles -->
<StackPanel Margin="{x:Bind CalculateMargin(ViewModel.NoProfilesAdded), Mode=OneWay}"
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(ViewModel.NoProfilesAdded), Mode=OneWay}">
<TextBlock x:Uid="Extensions_AddedProfilesHeader"
Style="{StaticResource TextBlockSubHeaderStyle}" />
<ItemsControl x:Name="AddedProfilesList"
IsTabStop="False"
ItemTemplate="{StaticResource FragmentProfileViewModelTemplate}"
ItemsSource="{x:Bind ViewModel.ProfilesAdded, Mode=OneWay}"
XYFocusKeyboardNavigation="Enabled" />
</StackPanel>
<!-- Grouping: Added Color Schemes -->
<StackPanel Margin="{x:Bind CalculateMargin(ViewModel.NoProfilesAdded), Mode=OneWay}"
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(ViewModel.NoSchemesAdded), Mode=OneWay}">
<TextBlock x:Uid="Extensions_AddedColorSchemesHeader"
Style="{StaticResource TextBlockSubHeaderStyle}" />
<ItemsControl x:Name="AddedColorSchemesList"
IsTabStop="False"
ItemTemplate="{StaticResource FragmentColorSchemeViewModelTemplate}"
ItemsSource="{x:Bind ViewModel.ColorSchemesAdded, Mode=OneWay}"
XYFocusKeyboardNavigation="Enabled" />
</StackPanel>
</StackPanel>
</Page>

View File

@@ -9,7 +9,9 @@
#include "Compatibility.h"
#include "Rendering.h"
#include "RenderingViewModel.h"
#include "Extensions.h"
#include "Actions.h"
#include "NewActions.h"
#include "ProfileViewModel.h"
#include "GlobalAppearance.h"
#include "GlobalAppearanceViewModel.h"
@@ -44,7 +46,9 @@ static const std::wstring_view interactionTag{ L"Interaction_Nav" };
static const std::wstring_view renderingTag{ L"Rendering_Nav" };
static const std::wstring_view compatibilityTag{ L"Compatibility_Nav" };
static const std::wstring_view actionsTag{ L"Actions_Nav" };
static const std::wstring_view 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" };
@@ -112,6 +116,50 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
});
_actionsVM = winrt::make<ActionsViewModel>(_settingsClone);
_actionsViewModelChangedRevoker = _actionsVM.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) {
const auto settingName{ args.PropertyName() };
if (settingName == L"CurrentPage")
{
if (_actionsVM.CurrentPage() == ActionsSubPage::Edit)
{
contentFrame().Navigate(xaml_typename<Editor::EditAction>(), _actionsVM.CurrentCommand());
const auto crumb = winrt::make<Breadcrumb>(box_value(newActionsTag), L"Edit Action...", BreadcrumbSubPage::Actions_Edit);
_breadcrumbs.Append(crumb);
}
else if (_actionsVM.CurrentPage() == ActionsSubPage::Base)
{
_Navigate(winrt::hstring{ newActionsTag }, BreadcrumbSubPage::None);
}
}
});
auto extensionsVMImpl = winrt::make_self<ExtensionsViewModel>(_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<Breadcrumb>(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<Breadcrumb>(box_value(extensionsTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
contentFrame().Navigate(xaml_typename<Editor::Extensions>(), _extensionsVM);
}
});
// Make sure to initialize the profiles _after_ we have initialized the color schemes page VM, because we pass
// that VM into the appearance VMs within the profiles
_InitializeProfilesList();
@@ -161,7 +209,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_InitializeProfilesList();
// Update the Nav State with the new version of the settings
_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
@@ -183,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;
}
}
@@ -198,6 +248,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return;
}
}
else if (const auto& breadcrumbExtensionPackage{ crumb->Tag().try_as<Editor::ExtensionPackageViewModel>() })
{
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<ProfileViewModel>() })
{
@@ -441,6 +502,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Actions/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
else if (clickedItemTag == newActionsTag)
{
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Actions/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
contentFrame().Navigate(xaml_typename<Editor::NewActions>(), _actionsVM);
if (subPage == BreadcrumbSubPage::Actions_Edit && _actionsVM.CurrentCommand() != nullptr)
{
_actionsVM.CurrentPage(ActionsSubPage::Edit);
}
}
else if (clickedItemTag == newTabMenuTag)
{
if (_newTabMenuPageVM.CurrentFolder())
@@ -457,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<Editor::Extensions>(), _extensionsVM);
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
}
}
else if (clickedItemTag == globalProfileTag)
{
auto profileVM{ _viewModelForProfile(_settingsClone.ProfileDefaults(), _settingsClone, Dispatcher()) };
@@ -587,6 +674,40 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
void MainPage::_Navigate(const Editor::ExtensionPackageViewModel& extPkgVM, BreadcrumbSubPage subPage)
{
_PreNavigateHelper();
contentFrame().Navigate(xaml_typename<Editor::Extensions>(), _extensionsVM);
const auto crumb = winrt::make<Breadcrumb>(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);
@@ -612,6 +733,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
_Navigate(*ntmEntryViewModel, subPage);
}
else if (const auto extPkgViewModel = tag.try_as<ExtensionPackageViewModel>())
{
_Navigate(*extPkgViewModel, subPage);
}
else
{
_Navigate(tag.as<hstring>(), subPage);
@@ -809,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<MUX::Controls::NavigationViewItem>() })
{
if (const auto& tag{ navViewItem.Tag() })
{
if (const auto& profileTag{ tag.try_as<ProfileViewModel>() })
{
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();

View File

@@ -43,6 +43,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
winrt::Windows::UI::Xaml::Media::Brush BackgroundBrush();
Windows::Foundation::Collections::IObservableVector<IInspectable> Breadcrumbs() noexcept;
Editor::ExtensionsViewModel ExtensionsVM() const noexcept { return _extensionsVM; }
til::typed_event<Windows::Foundation::IInspectable, Model::SettingsTarget> OpenJson;
@@ -68,16 +69,23 @@ 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();
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;
};
}

View File

@@ -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.
@@ -20,7 +22,9 @@ namespace Microsoft.Terminal.Settings.Editor
Profile_Terminal,
Profile_Advanced,
ColorSchemes_Edit,
NewTabMenu_Folder
Actions_Edit,
NewTabMenu_Folder,
Extensions_Extension
};
runtimeclass Breadcrumb : Windows.Foundation.IStringable
@@ -42,6 +46,7 @@ namespace Microsoft.Terminal.Settings.Editor
void SetHostingWindow(UInt64 window);
Windows.Foundation.Collections.IObservableVector<IInspectable> Breadcrumbs { get; };
ExtensionsViewModel ExtensionsVM { get; };
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
}

View File

@@ -120,7 +120,8 @@
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
<muxc:NavigationViewItem x:Uid="Nav_ColorSchemes"
<muxc:NavigationViewItem x:Name="ColorSchemesNavItem"
x:Uid="Nav_ColorSchemes"
Tag="ColorSchemes_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE790;" />
@@ -148,11 +149,29 @@
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
<muxc:NavigationViewItem x:Uid="Nav_NewActions"
Tag="NewActions_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE765;" />
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
<muxc:NavigationViewItem x:Uid="Nav_NewTabMenu"
Tag="NewTabMenu_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE71d;" />
<FontIcon Glyph="&#xE71d;" />
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
<muxc:NavigationViewItem x:Uid="Nav_Extensions"
Tag="Extensions_Nav">
<muxc:NavigationViewItem.Icon>
<FontIcon Glyph="&#xEA86;" />
</muxc:NavigationViewItem.Icon>
<muxc:NavigationViewItem.InfoBadge>
<muxc:InfoBadge Style="{StaticResource NewInfoBadge}"
Visibility="{x:Bind ExtensionsVM.DisplayBadge, Mode=OneWay}" />
</muxc:NavigationViewItem.InfoBadge>
</muxc:NavigationViewItem>
<muxc:NavigationViewItemHeader x:Uid="Nav_Profiles" />

View File

@@ -44,6 +44,16 @@
<ClInclude Include="Actions.h">
<DependentUpon>Actions.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="NewActions.h">
<DependentUpon>NewActions.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="ArgsTemplateSelectors.h">
<DependentUpon>ArgsTemplateSelectors.idl</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="EditAction.h">
<DependentUpon>EditAction.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="AddProfile.h">
<DependentUpon>AddProfile.xaml</DependentUpon>
</ClInclude>
@@ -125,6 +135,10 @@
<DependentUpon>NewTabMenuViewModel.idl</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="Extensions.h">
<DependentUpon>Extensions.xaml</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="Profiles_Base.h">
<DependentUpon>Profiles_Base.xaml</DependentUpon>
<SubType>Code</SubType>
@@ -163,6 +177,12 @@
<Page Include="Actions.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="NewActions.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="EditAction.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="AddProfile.xaml">
<SubType>Designer</SubType>
</Page>
@@ -199,6 +219,9 @@
<Page Include="MainPage.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="Extensions.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="Profiles_Base.xaml">
<SubType>Designer</SubType>
</Page>
@@ -229,6 +252,16 @@
<ClCompile Include="Actions.cpp">
<DependentUpon>Actions.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="NewActions.cpp">
<DependentUpon>NewActions.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="ArgsTemplateSelectors.cpp">
<DependentUpon>ArgsTemplateSelectors.idl</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="EditAction.cpp">
<DependentUpon>EditAction.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="AddProfile.cpp">
<DependentUpon>AddProfile.xaml</DependentUpon>
</ClCompile>
@@ -309,6 +342,10 @@
<DependentUpon>NewTabMenuViewModel.idl</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="Extensions.cpp">
<DependentUpon>Extensions.xaml</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="Profiles_Base.cpp">
<DependentUpon>Profiles_Base.xaml</DependentUpon>
<SubType>Code</SubType>
@@ -350,6 +387,17 @@
<DependentUpon>Actions.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="NewActions.idl">
<DependentUpon>NewActions.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="ArgsTemplateSelectors.idl">
<SubType>Designer</SubType>
</Midl>
<Midl Include="EditAction.idl">
<DependentUpon>EditAction.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="AddProfile.idl">
<DependentUpon>AddProfile.xaml</DependentUpon>
<SubType>Code</SubType>
@@ -408,6 +456,10 @@
<Midl Include="GlobalAppearanceViewModel.idl" />
<Midl Include="LaunchViewModel.idl" />
<Midl Include="NewTabMenuViewModel.idl" />
<Midl Include="Extensions.idl">
<DependentUpon>Extensions.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="Profiles_Base.idl">
<DependentUpon>Profiles_Base.xaml</DependentUpon>
<SubType>Code</SubType>

View File

@@ -29,6 +29,7 @@
<Midl Include="LaunchViewModel.idl" />
<Midl Include="EnumEntry.idl" />
<Midl Include="SettingContainer.idl" />
<Midl Include="ArgsTemplateSelectors.idl" />
<Midl Include="TerminalColorConverters.idl" />
<Midl Include="NewTabMenuViewModel.idl" />
</ItemGroup>
@@ -49,6 +50,8 @@
<Page Include="Appearances.xaml" />
<Page Include="Rendering.xaml" />
<Page Include="Actions.xaml" />
<Page Include="NewActions.xaml" />
<Page Include="EditAction.xaml" />
<Page Include="SettingContainerStyle.xaml" />
<Page Include="AddProfile.xaml" />
<Page Include="KeyChordListener.xaml" />

View File

@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "NewActions.h"
#include "NewActions.g.cpp"
#include "LibraryResources.h"
#include "../TerminalSettingsModel/AllShortcutActions.h"
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Navigation;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
NewActions::NewActions()
{
InitializeComponent();
Automation::AutomationProperties::SetName(AddNewButton(), RS_(L"Actions_AddNewTextBlock/Text"));
}
Automation::Peers::AutomationPeer NewActions::OnCreateAutomationPeer()
{
_ViewModel.OnAutomationPeerAttached();
return nullptr;
}
void NewActions::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::ActionsViewModel>();
_ViewModel.CurrentPage(ActionsSubPage::Base);
}
void NewActions::AddNew_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*eventArgs*/)
{
_ViewModel.AddNewCommand();
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "NewActions.g.h"
#include "ActionsViewModel.h"
#include "Utils.h"
#include "ViewModelHelpers.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct NewActions : public HasScrollViewer<NewActions>, NewActionsT<NewActions>
{
public:
NewActions();
void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
void AddNew_Click(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
til::property_changed_event PropertyChanged;
WINRT_OBSERVABLE_PROPERTY(Editor::ActionsViewModel, ViewModel, PropertyChanged.raise, nullptr);
};
}
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(NewActions);
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "ActionsViewModel.idl";
namespace Microsoft.Terminal.Settings.Editor
{
[default_interface] runtimeclass NewActions : Windows.UI.Xaml.Controls.Page
{
NewActions();
ActionsViewModel ViewModel { get; };
}
}

View File

@@ -0,0 +1,204 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information.
-->
<Page x:Class="Microsoft.Terminal.Settings.Editor.NewActions"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.Terminal.Settings.Editor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mtu="using:Microsoft.Terminal.UI"
mc:Ignorable="d">
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="CommonResources.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Theme Dictionary -->
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<!-- TextBox colors ! -->
<SolidColorBrush x:Key="TextControlBackground"
Color="#333333" />
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush"
Color="#B5B5B5" />
<SolidColorBrush x:Key="TextControlForeground"
Color="#B5B5B5" />
<SolidColorBrush x:Key="TextControlBorderBrush"
Color="#404040" />
<SolidColorBrush x:Key="TextControlButtonForeground"
Color="#B5B5B5" />
<SolidColorBrush x:Key="TextControlBackgroundPointerOver"
Color="#404040" />
<SolidColorBrush x:Key="TextControlForegroundPointerOver"
Color="#FFFFFF" />
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver"
Color="#404040" />
<SolidColorBrush x:Key="TextControlButtonForegroundPointerOver"
Color="#FF4343" />
<SolidColorBrush x:Key="TextControlBackgroundFocused"
Color="#333333" />
<SolidColorBrush x:Key="TextControlForegroundFocused"
Color="#FFFFFF" />
<SolidColorBrush x:Key="TextControlBorderBrushFocused"
Color="#404040" />
<SolidColorBrush x:Key="TextControlButtonForegroundPressed"
Color="#FFFFFF" />
<SolidColorBrush x:Key="TextControlButtonBackgroundPressed"
Color="#FF4343" />
<!-- KeyChordText styles -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="1" />
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<!-- TextBox colors ! -->
<SolidColorBrush x:Key="TextControlBackground"
Color="#CCCCCC" />
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush"
Color="#636363" />
<SolidColorBrush x:Key="TextControlBorderBrush"
Color="#636363" />
<SolidColorBrush x:Key="TextControlButtonForeground"
Color="#636363" />
<SolidColorBrush x:Key="TextControlBackgroundPointerOver"
Color="#DADADA" />
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver"
Color="#636363" />
<SolidColorBrush x:Key="TextControlButtonForegroundPointerOver"
Color="#FF4343" />
<SolidColorBrush x:Key="TextControlBackgroundFocused"
Color="#CCCCCC" />
<SolidColorBrush x:Key="TextControlBorderBrushFocused"
Color="#636363" />
<SolidColorBrush x:Key="TextControlButtonForegroundPressed"
Color="#FFFFFF" />
<SolidColorBrush x:Key="TextControlButtonBackgroundPressed"
Color="#FF4343" />
<!-- KeyChordText styles -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="1" />
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<!-- KeyChordText styles (use XAML defaults for High Contrast theme) -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Border" />
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<!-- Styles -->
<Style x:Key="KeyBindingContainerStyle"
BasedOn="{StaticResource DefaultListViewItemStyle}"
TargetType="ListViewItem">
<Setter Property="Padding" Value="4" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="XYFocusKeyboardNavigation" Value="Enabled" />
</Style>
<Style x:Key="KeyBindingNameTextBlockStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="TextWrapping" Value="WrapWholeWords" />
</Style>
<Style x:Key="KeyChordEditorStyle"
TargetType="local:KeyChordListener">
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<!-- Converters & Misc. -->
<SolidColorBrush x:Key="ActionContainerBackgroundEditing"
Color="{ThemeResource SystemListMediumColor}" />
<SolidColorBrush x:Key="ActionContainerBackground"
Color="Transparent" />
<!-- Templates -->
<DataTemplate x:Key="CommandTemplate"
x:DataType="local:CommandViewModel">
<ListViewItem AutomationProperties.Name="{x:Bind DisplayName, Mode=OneWay}"
Style="{StaticResource KeyBindingContainerStyle}">
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<!-- command name -->
<ColumnDefinition Width="*" />
<!-- key chord -->
<ColumnDefinition Width="150" />
<!-- edit buttons -->
<!--
This needs to be 112 because that is the width of the row of buttons in edit mode + padding.
3 buttons: 32+32+32
Padding: 8+ 8
This allows the "edit" button to align with the "cancel" button seamlessly
-->
<ColumnDefinition Width="112" />
</Grid.ColumnDefinitions>
<!-- Command Name -->
<TextBlock Grid.Column="0"
Style="{StaticResource KeyBindingNameTextBlockStyle}"
Text="{x:Bind DisplayName, Mode=OneWay}" />
</Grid>
</ListViewItem>
</DataTemplate>
</ResourceDictionary>
</Page.Resources>
<Border MaxWidth="{StaticResource StandardControlMaxWidth}">
<StackPanel MaxWidth="600"
HorizontalAlignment="Left"
Spacing="8"
Style="{StaticResource SettingsStackStyle}">
<!-- Add New Button -->
<Button x:Name="AddNewButton"
Click="AddNew_Click">
<Button.Content>
<StackPanel Orientation="Horizontal">
<FontIcon FontSize="{StaticResource StandardIconSize}"
Glyph="&#xE710;" />
<TextBlock x:Uid="Actions_AddNewTextBlock"
Style="{StaticResource IconButtonTextBlockStyle}" />
</StackPanel>
</Button.Content>
</Button>
<!-- Keybindings -->
<ListView x:Name="CommandsListView"
IsItemClickEnabled="True"
ItemTemplate="{StaticResource CommandTemplate}"
ItemsSource="{x:Bind ViewModel.CommandList, Mode=OneWay}"
ItemClick="{x:Bind ViewModel.CmdListItemClicked}"/>
</StackPanel>
</Border>
</Page>

View File

@@ -22,8 +22,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
InitializeComponent();
_entryTemplateSelector = Resources().Lookup(box_value(L"NewTabMenuEntryTemplateSelector")).as<Editor::NewTabMenuEntryTemplateSelector>();
// 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).

View File

@@ -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);

View File

@@ -321,7 +321,7 @@
<TextBlock x:Uid="NewTabMenu_CurrentFolderTextBlock"
Style="{StaticResource TextBlockSubHeaderStyle}" />
<!-- TODO CARLOS: Icon -->
<!-- TODO GH #18281: Icon -->
<!-- Once PR #17965 merges, we can add that kind of control to set an icon -->
<!-- Name -->

View File

@@ -684,6 +684,14 @@
<value>Actions</value>
<comment>Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app.</comment>
</data>
<data name="Nav_NewActions.Content" xml:space="preserve">
<value>New Actions</value>
<comment>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.</comment>
</data>
<data name="Nav_Extensions.Content" xml:space="preserve">
<value>Extensions</value>
<comment>Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app.</comment>
</data>
<data name="Profile_OpacitySlider.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Background opacity</value>
<comment>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.</comment>
@@ -1784,6 +1792,34 @@
<value>Delete the unfocused appearance for this profile.</value>
<comment>A description for what the delete unfocused appearance button does.</comment>
</data>
<data name="Actions_DeleteButton2.Text" xml:space="preserve">
<value>Delete action</value>
<comment>Button label that deletes the selected action.</comment>
</data>
<data name="Actions_Name.Text" xml:space="preserve">
<value>Action name</value>
<comment>Label for the text box that edits the action name.</comment>
</data>
<data name="Actions_NameEntryBox.PlaceholderText" xml:space="preserve">
<value>Action name</value>
<comment>Placeholder text for the text box where the user can edit the action name.</comment>
</data>
<data name="Actions_ShortcutAction.Text" xml:space="preserve">
<value>Action type</value>
<comment>Label for the combo box that edits the action type.</comment>
</data>
<data name="Actions_Arguments.Text" xml:space="preserve">
<value>Additional arguments</value>
<comment>Label for the list of editable arguments for the currently selected action.</comment>
</data>
<data name="Actions_Keybindings.Text" xml:space="preserve">
<value>Keybindings</value>
<comment>Label for the list of editable keybindings for the current command.</comment>
</data>
<data name="Actions_AddKeyChord.Text" xml:space="preserve">
<value>Add keybinding</value>
<comment>Button label that adds a keybinding to the current action.</comment>
</data>
<data name="Actions_DeleteConfirmationButton.Content" xml:space="preserve">
<value>Yes, delete key binding</value>
<comment>Button label that confirms deletion of a key binding entry.</comment>
@@ -1792,6 +1828,14 @@
<value>Are you sure you want to delete this key binding?</value>
<comment>Confirmation message displayed when the user attempts to delete a key binding entry.</comment>
</data>
<data name="Actions_CommandDeleteConfirmationButton.Content" xml:space="preserve">
<value>Yes, delete action</value>
<comment>Button label that confirms deletion of an action.</comment>
</data>
<data name="Actions_CommandDeleteConfirmationMessage.Text" xml:space="preserve">
<value>Are you sure you want to delete this action?</value>
<comment>Confirmation message displayed when the user attempts to delete an action.</comment>
</data>
<data name="Actions_InvalidKeyChordMessage" xml:space="preserve">
<value>Invalid key chord. Please enter a valid key chord.</value>
<comment>Error message displayed when an invalid key chord is input by the user.</comment>
@@ -1836,6 +1880,274 @@
<value>Action</value>
<comment>Label for a control that sets the action of a key binding.</comment>
</data>
<data name="Actions_NullEnumValue" xml:space="preserve">
<value>Null (use global setting)</value>
<comment>An option to choose from for nullable enums. Clears the enum value.</comment>
</data>
<data name="Actions_CopyFormatHtml.Content" xml:space="preserve">
<value>HTML</value>
<comment>An option to choose from for the "copy format". Copies content in HTML format.</comment>
</data>
<data name="Actions_CopyFormatRtf.Content" xml:space="preserve">
<value>RTF</value>
<comment>An option to choose from for the "copy format". Copies content in Rich Text Format (RTF).</comment>
</data>
<data name="Actions_SplitDirectionAuto.Content" xml:space="preserve">
<value>Automatic</value>
<comment>An option to choose from for the "split direction". Automatically determines the split direction.</comment>
</data>
<data name="Actions_SplitDirectionUp.Content" xml:space="preserve">
<value>Up</value>
<comment>An option to choose from for the "split direction". Splits upward.</comment>
</data>
<data name="Actions_SplitDirectionRight.Content" xml:space="preserve">
<value>Right</value>
<comment>An option to choose from for the "split direction". Splits to the right.</comment>
</data>
<data name="Actions_SplitDirectionDown.Content" xml:space="preserve">
<value>Down</value>
<comment>An option to choose from for the "split direction". Splits downward.</comment>
</data>
<data name="Actions_SplitDirectionLeft.Content" xml:space="preserve">
<value>Left</value>
<comment>An option to choose from for the "split direction". Splits to the left.</comment>
</data>
<data name="Actions_SplitDirectionVertical.Content" xml:space="preserve">
<value>Vertical</value>
<comment>An option to choose from for the "split direction". Splits to the left.</comment>
</data>
<data name="Actions_SplitDirectionHorizontal.Content" xml:space="preserve">
<value>Horizontal</value>
<comment>An option to choose from for the "split direction". Splits to the left.</comment>
</data>
<data name="Actions_SplitTypeManual.Content" xml:space="preserve">
<value>Manual</value>
<comment>An option to choose from for the "split type". Creates a manual split.</comment>
</data>
<data name="Actions_SplitTypeDuplicate.Content" xml:space="preserve">
<value>Duplicate</value>
<comment>An option to choose from for the "split type". Creates a split by duplicating the current session.</comment>
</data>
<data name="Actions_ResizeDirectionNone.Content" xml:space="preserve">
<value>None</value>
<comment>An option to choose from for the "resize direction". None option.</comment>
</data>
<data name="Actions_ResizeDirectionLeft.Content" xml:space="preserve">
<value>Left</value>
<comment>An option to choose from for the "resize direction". Left option.</comment>
</data>
<data name="Actions_ResizeDirectionRight.Content" xml:space="preserve">
<value>Right</value>
<comment>An option to choose from for the "resize direction". Right option.</comment>
</data>
<data name="Actions_ResizeDirectionUp.Content" xml:space="preserve">
<value>Up</value>
<comment>An option to choose from for the "resize direction". Up option.</comment>
</data>
<data name="Actions_ResizeDirectionDown.Content" xml:space="preserve">
<value>Down</value>
<comment>An option to choose from for the "resize direction". Down option.</comment>
</data>
<data name="Actions_FocusDirectionNone.Content" xml:space="preserve">
<value>None</value>
<comment>An option to choose from for the "focus direction". None option.</comment>
</data>
<data name="Actions_FocusDirectionLeft.Content" xml:space="preserve">
<value>Left</value>
<comment>An option to choose from for the "focus direction". Left option.</comment>
</data>
<data name="Actions_FocusDirectionRight.Content" xml:space="preserve">
<value>Right</value>
<comment>An option to choose from for the "focus direction". Right option.</comment>
</data>
<data name="Actions_FocusDirectionUp.Content" xml:space="preserve">
<value>Up</value>
<comment>An option to choose from for the "focus direction". Up option.</comment>
</data>
<data name="Actions_FocusDirectionDown.Content" xml:space="preserve">
<value>Down</value>
<comment>An option to choose from for the "focus direction". Down option.</comment>
</data>
<data name="Actions_FocusDirectionPrevious.Content" xml:space="preserve">
<value>Previous</value>
<comment>An option to choose from for the "focus direction". Previous option.</comment>
</data>
<data name="Actions_FocusDirectionPreviousInOrder.Content" xml:space="preserve">
<value>Previous In Order</value>
<comment>An option to choose from for the "focus direction". Previous in order option.</comment>
</data>
<data name="Actions_FocusDirectionNextInOrder.Content" xml:space="preserve">
<value>Next In Order</value>
<comment>An option to choose from for the "focus direction". Next in order option.</comment>
</data>
<data name="Actions_FocusDirectionFirst.Content" xml:space="preserve">
<value>First</value>
<comment>An option to choose from for the "focus direction". First option.</comment>
</data>
<data name="Actions_FocusDirectionParent.Content" xml:space="preserve">
<value>Parent</value>
<comment>An option to choose from for the "focus direction". Parent option.</comment>
</data>
<data name="Actions_FocusDirectionChild.Content" xml:space="preserve">
<value>Child</value>
<comment>An option to choose from for the "focus direction". Child option.</comment>
</data>
<data name="Actions_SettingsTargetSettingsFile.Content" xml:space="preserve">
<value>Settings File</value>
<comment>An option to choose from for the "settings target". Targets the settings file.</comment>
</data>
<data name="Actions_SettingsTargetDefaultsFile.Content" xml:space="preserve">
<value>Defaults File</value>
<comment>An option to choose from for the "settings target". Targets the defaults file.</comment>
</data>
<data name="Actions_SettingsTargetAllFiles.Content" xml:space="preserve">
<value>All Files</value>
<comment>An option to choose from for the "settings target". Targets all files.</comment>
</data>
<data name="Actions_SettingsTargetSettingsUI.Content" xml:space="preserve">
<value>Settings UI</value>
<comment>An option to choose from for the "settings target". Targets the settings UI.</comment>
</data>
<data name="Actions_SettingsTargetDirectory.Content" xml:space="preserve">
<value>Directory</value>
<comment>An option to choose from for the "settings target". Targets the directory.</comment>
</data>
<data name="Actions_MoveTabDirectionNone.Content" xml:space="preserve">
<value>None</value>
<comment>An option to choose from for the "move tab direction". No movement.</comment>
</data>
<data name="Actions_MoveTabDirectionForward.Content" xml:space="preserve">
<value>Forward</value>
<comment>An option to choose from for the "move tab direction". Moves the tab forward.</comment>
</data>
<data name="Actions_MoveTabDirectionBackward.Content" xml:space="preserve">
<value>Backward</value>
<comment>An option to choose from for the "move tab direction". Moves the tab backward.</comment>
</data>
<data name="Actions_ScrollToMarkDirectionPrevious.Content" xml:space="preserve">
<value>Previous</value>
<comment>An option to choose from for the "scroll to mark direction". Scrolls to the previous mark.</comment>
</data>
<data name="Actions_ScrollToMarkDirectionNext.Content" xml:space="preserve">
<value>Next</value>
<comment>An option to choose from for the "scroll to mark direction". Scrolls to the next mark.</comment>
</data>
<data name="Actions_ScrollToMarkDirectionFirst.Content" xml:space="preserve">
<value>First</value>
<comment>An option to choose from for the "scroll to mark direction". Scrolls to the first mark.</comment>
</data>
<data name="Actions_ScrollToMarkDirectionLast.Content" xml:space="preserve">
<value>Last</value>
<comment>An option to choose from for the "scroll to mark direction". Scrolls to the last mark.</comment>
</data>
<data name="Actions_CommandPaletteLaunchModeAction.Content" xml:space="preserve">
<value>Action</value>
<comment>An option to choose from for the "command palette launch mode". Launches in action mode.</comment>
</data>
<data name="Actions_CommandPaletteLaunchModeCommandLine.Content" xml:space="preserve">
<value>Command Line</value>
<comment>An option to choose from for the "command palette launch mode". Launches in command line mode.</comment>
</data>
<data name="Actions_SuggestionsSourceNone.Content" xml:space="preserve">
<value>None</value>
<comment>An option to choose from for the "suggestions source". No suggestions source.</comment>
</data>
<data name="Actions_SuggestionsSourceTasks.Content" xml:space="preserve">
<value>Tasks</value>
<comment>An option to choose from for the "suggestions source". Suggestions come from tasks.</comment>
</data>
<data name="Actions_SuggestionsSourceSnippets.Content" xml:space="preserve">
<value>Snippets</value>
<comment>An option to choose from for the "suggestions source". Suggestions come from snippets.</comment>
</data>
<data name="Actions_SuggestionsSourceCommandHistory.Content" xml:space="preserve">
<value>Command History</value>
<comment>An option to choose from for the "suggestions source". Suggestions come from command history.</comment>
</data>
<data name="Actions_SuggestionsSourceDirectoryHistory.Content" xml:space="preserve">
<value>Directory History</value>
<comment>An option to choose from for the "suggestions source". Suggestions come from directory history.</comment>
</data>
<data name="Actions_SuggestionsSourceQuickFix.Content" xml:space="preserve">
<value>Quick Fixes</value>
<comment>An option to choose from for the "suggestions source". Suggestions come from quick fixes.</comment>
</data>
<data name="Actions_SuggestionsSourceAll.Content" xml:space="preserve">
<value>All</value>
<comment>An option to choose from for the "suggestions source". Includes all suggestion sources.</comment>
</data>
<data name="Actions_FindMatchDirectionNone.Content" xml:space="preserve">
<value>None</value>
<comment>An option to choose from for the "find match direction". No direction selected.</comment>
</data>
<data name="Actions_FindMatchDirectionNext.Content" xml:space="preserve">
<value>Next</value>
<comment>An option to choose from for the "find match direction". Finds the next match.</comment>
</data>
<data name="Actions_FindMatchDirectionPrev.Content" xml:space="preserve">
<value>Previous</value>
<comment>An option to choose from for the "find match direction". Finds the previous match.</comment>
</data>
<data name="Actions_DesktopBehaviorAny.Content" xml:space="preserve">
<value>Any</value>
<comment>An option to choose from for the "desktop behavior". Applies to any desktop.</comment>
</data>
<data name="Actions_DesktopBehaviorToCurrent.Content" xml:space="preserve">
<value>To Current</value>
<comment>An option to choose from for the "desktop behavior". Moves to the current desktop.</comment>
</data>
<data name="Actions_DesktopBehaviorOnCurrent.Content" xml:space="preserve">
<value>On Current</value>
<comment>An option to choose from for the "desktop behavior". Stays on the current desktop.</comment>
</data>
<data name="Actions_MonitorBehaviorAny.Content" xml:space="preserve">
<value>Any</value>
<comment>An option to choose from for the "monitor behavior". Applies to any monitor.</comment>
</data>
<data name="Actions_MonitorBehaviorToCurrent.Content" xml:space="preserve">
<value>To Current</value>
<comment>An option to choose from for the "monitor behavior". Moves to the current monitor.</comment>
</data>
<data name="Actions_MonitorBehaviorToMouse.Content" xml:space="preserve">
<value>To Mouse</value>
<comment>An option to choose from for the "monitor behavior". Moves to the monitor where the mouse is located.</comment>
</data>
<data name="Actions_ClearBufferTypeScreen.Content" xml:space="preserve">
<value>Screen</value>
<comment>An option to choose from for the "clear buffer type". Clears only the screen.</comment>
</data>
<data name="Actions_ClearBufferTypeScrollback.Content" xml:space="preserve">
<value>Scrollback</value>
<comment>An option to choose from for the "clear buffer type". Clears only the scrollback buffer.</comment>
</data>
<data name="Actions_ClearBufferTypeAll.Content" xml:space="preserve">
<value>All</value>
<comment>An option to choose from for the "clear buffer type". Clears both the screen and the scrollback buffer.</comment>
</data>
<data name="Actions_SelectOutputDirectionPrev.Content" xml:space="preserve">
<value>Previous</value>
<comment>An option to choose from for the "select output direction". Selects the previous output.</comment>
</data>
<data name="Actions_SelectOutputDirectionNext.Content" xml:space="preserve">
<value>Next</value>
<comment>An option to choose from for the "select output direction". Selects the next output.</comment>
</data>
<data name="Actions_TabSwitcherModeMru.Content" xml:space="preserve">
<value>Most Recently Used</value>
<comment>An option to choose from for the "tab switcher mode". Switches tabs based on most recently used order.</comment>
</data>
<data name="Actions_TabSwitcherModeInOrder.Content" xml:space="preserve">
<value>In Order</value>
<comment>An option to choose from for the "tab switcher mode". Switches tabs in sequential order.</comment>
</data>
<data name="Actions_TabSwitcherModeDisabled.Content" xml:space="preserve">
<value>Disabled</value>
<comment>An option to choose from for the "tab switcher mode". Disables tab switching.</comment>
</data>
<data name="Actions_NullableColorPicker.NullColorButtonLabel" xml:space="preserve">
<value>No color</value>
<comment>Label for a button directing the user to opt out of choosing a color.</comment>
</data>
<data name="KeyChordListener.[using:Windows.UI.Xaml.Automation]AutomationProperties.HelpText" xml:space="preserve">
<value>Input your desired keyboard shortcut.</value>
<comment>Help text directing users how to use the "KeyChordListener" control. Pressing a keyboard shortcut will be recorded by this control.</comment>
@@ -2344,4 +2656,47 @@
<value>This option is managed by enterprise policy and cannot be changed here.</value>
<comment>This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting.</comment>
</data>
<data name="Extensions_ActiveExtensionsHeader.Text" xml:space="preserve">
<value>Active Extensions</value>
</data>
<data name="Extensions_ModifiedProfilesHeader.Text" xml:space="preserve">
<value>Modified Profiles</value>
</data>
<data name="Extensions_AddedProfilesHeader.Text" xml:space="preserve">
<value>Added Profiles</value>
</data>
<data name="Extensions_AddedColorSchemesHeader.Text" xml:space="preserve">
<value>Added Color Schemes</value>
</data>
<data name="Extensions_DisclaimerHyperlink.Content" xml:space="preserve">
<value>Learn more about extensions</value>
</data>
<data name="Extensions_NavigateToProfileButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Navigate to profile</value>
</data>
<data name="Extensions_NavigateToProfileButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Navigate to profile</value>
</data>
<data name="Extensions_NavigateToColorSchemeButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Navigate to color scheme</value>
</data>
<data name="Extensions_NavigateToColorSchemeButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Navigate to color scheme</value>
</data>
<data name="Extensions_ScopeUser" xml:space="preserve">
<value>Current User</value>
<comment>Label for the installation scope of an extension.</comment>
</data>
<data name="Extensions_ScopeSystem" xml:space="preserve">
<value>All Users</value>
<comment>Label for the installation scope of an extension</comment>
</data>
<data name="Extensions_Scope.Header" xml:space="preserve">
<value>Scope</value>
<comment>Header for the installation scope of the extension</comment>
</data>
<data name="NewInfoBadgeTextBlock.Text" xml:space="preserve">
<value>NEW</value>
<comment>Text is used on an info badge for new navigation items. Must be all caps.</comment>
</data>
</root>

View File

@@ -134,6 +134,7 @@
</Style>
<Style x:Key="SettingContainerResetButtonStyle"
BasedOn="{StaticResource DefaultButtonStyle}"
TargetType="Button">
<Setter Property="Margin" Value="5,0,0,0" />
<Setter Property="Height" Value="19" />
@@ -179,13 +180,18 @@
<Setter Property="FontFamily" Value="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets" />
</Style>
<Style x:Key="SettingContainerCurrentValueTextBlockStyle"
BasedOn="{StaticResource SettingsPageItemDescriptionStyle}"
TargetType="TextBlock">
<Setter Property="MaxWidth" Value="250" />
<Setter Property="Margin" Value="0,0,-16,0" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets" />
</Style>
<DataTemplate x:Key="ExpanderSettingContainerStringPreviewTemplate">
<TextBlock MaxWidth="250"
Margin="0,0,-16,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
<TextBlock Style="{StaticResource SettingContainerCurrentValueTextBlockStyle}"
Text="{Binding}" />
</DataTemplate>
@@ -228,6 +234,45 @@
</Setter>
</Style>
<!-- A basic setting container displaying immutable text as content -->
<Style x:Key="SettingContainerWithTextContent"
TargetType="local:SettingContainer">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:SettingContainer">
<Grid AutomationProperties.Name="{TemplateBinding Header}"
Style="{StaticResource NonExpanderGrid}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Style="{StaticResource StackPanelInExpanderStyle}">
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingsPageItemHeaderStyle}"
Text="{TemplateBinding Header}" />
<Button x:Name="ResetButton"
Style="{StaticResource SettingContainerResetButtonStyle}">
<FontIcon Glyph="&#xE845;"
Style="{StaticResource SettingContainerFontIconStyle}" />
</Button>
</StackPanel>
<TextBlock x:Name="HelpTextBlock"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
Text="{TemplateBinding HelpText}" />
</StackPanel>
<TextBlock Grid.Column="1"
Margin="0,0,8,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Style="{ThemeResource SecondaryTextBlockStyle}"
Text="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--
A setting container for a setting that has no additional options.
Includes space for an icon on the left side of the header.
@@ -302,8 +347,7 @@
<StackPanel Grid.Column="0"
Style="{StaticResource StackPanelInExpanderStyle}">
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingsPageItemHeaderStyle}"
Text="{TemplateBinding Header}" />
<ContentPresenter Content="{TemplateBinding Header}" />
<Button x:Name="ResetButton"
Style="{StaticResource SettingContainerResetButtonStyle}">
<FontIcon Glyph="&#xE845;"

View File

@@ -286,6 +286,25 @@ protected: \
X(winrt::Microsoft::Terminal::Control::SelectionColor, Background, "background", false, nullptr) \
X(winrt::Microsoft::Terminal::Core::MatchMode, MatchMode, "matchMode", false, winrt::Microsoft::Terminal::Core::MatchMode::None)
////////////////////////////////////////////////////////////////////////////////
#define NEW_TERMINAL_ARGS(X) \
X(winrt::hstring, Commandline, "commandline", false, L"") \
X(winrt::hstring, StartingDirectory, "startingDirectory", false, L"") \
X(winrt::hstring, TabTitle, "tabTitle", false, L"") \
X(Windows::Foundation::IReference<Windows::UI::Color>, TabColor, "tabColor", false, nullptr) \
X(Windows::Foundation::IReference<int32_t>, ProfileIndex, "index", false, nullptr) \
X(winrt::hstring, Profile, "profile", false, L"") \
X(Windows::Foundation::IReference<bool>, SuppressApplicationTitle, "suppressApplicationTitle", false, nullptr) \
X(winrt::hstring, ColorScheme, "colorScheme", args->SchemeName().empty(), L"") \
X(Windows::Foundation::IReference<bool>, Elevate, "elevate", false, nullptr) \
X(Windows::Foundation::IReference<bool>, ReloadEnvironmentVariables, "reloadEnvironmentVariables", false, nullptr)
////////////////////////////////////////////////////////////////////////////////
#define SPLIT_PANE_ARGS(X) \
X(Model::SplitDirection, SplitDirection, "split", false, SplitDirection::Automatic) \
X(SplitType, SplitMode, "splitMode", false, SplitType::Manual) \
X(float, SplitSize, "size", false, 0.5f)
////////////////////////////////////////////////////////////////////////////////
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
@@ -358,41 +377,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// assumptions made in the macro.
struct NewTerminalArgs : public NewTerminalArgsT<NewTerminalArgs>
{
NewTerminalArgs() = default;
NewTerminalArgs(int32_t& profileIndex) :
_ProfileIndex{ profileIndex } {};
PARTIAL_ACTION_ARG_BODY(NewTerminalArgs, NEW_TERMINAL_ARGS);
ACTION_ARG(winrt::hstring, Type, L"");
ACTION_ARG(winrt::hstring, Commandline, L"");
ACTION_ARG(winrt::hstring, StartingDirectory, L"");
ACTION_ARG(winrt::hstring, TabTitle, L"");
ACTION_ARG(Windows::Foundation::IReference<Windows::UI::Color>, TabColor, nullptr);
ACTION_ARG(Windows::Foundation::IReference<int32_t>, ProfileIndex, nullptr);
ACTION_ARG(winrt::hstring, Profile, L"");
ACTION_ARG(winrt::guid, SessionId, winrt::guid{});
ACTION_ARG(bool, AppendCommandLine, false);
ACTION_ARG(Windows::Foundation::IReference<bool>, SuppressApplicationTitle, nullptr);
ACTION_ARG(winrt::hstring, ColorScheme);
ACTION_ARG(Windows::Foundation::IReference<bool>, Elevate, nullptr);
ACTION_ARG(Windows::Foundation::IReference<bool>, ReloadEnvironmentVariables, nullptr);
ACTION_ARG(uint64_t, ContentId);
static constexpr std::string_view CommandlineKey{ "commandline" };
static constexpr std::string_view StartingDirectoryKey{ "startingDirectory" };
static constexpr std::string_view TabTitleKey{ "tabTitle" };
static constexpr std::string_view TabColorKey{ "tabColor" };
static constexpr std::string_view ProfileIndexKey{ "index" };
static constexpr std::string_view ProfileKey{ "profile" };
static constexpr std::string_view SessionIdKey{ "sessionId" };
static constexpr std::string_view AppendCommandLineKey{ "appendCommandLine" };
static constexpr std::string_view SuppressApplicationTitleKey{ "suppressApplicationTitle" };
static constexpr std::string_view ColorSchemeKey{ "colorScheme" };
static constexpr std::string_view ElevateKey{ "elevate" };
static constexpr std::string_view ReloadEnvironmentVariablesKey{ "reloadEnvironmentVariables" };
static constexpr std::string_view ContentKey{ "__content" };
public:
NewTerminalArgs(int32_t& profileIndex) :
_ProfileIndex{ profileIndex } {
NEW_TERMINAL_ARGS(APPEND_ARG_DESCRIPTION);
};
hstring GenerateName() const;
hstring ToCommandline() const;
@@ -471,6 +470,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
copy->_Elevate = _Elevate;
copy->_ReloadEnvironmentVariables = _ReloadEnvironmentVariables;
copy->_ContentId = _ContentId;
copy->_argDescriptions = _argDescriptions;
return *copy;
}
size_t Hash() const
@@ -589,7 +589,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
NewTabArgs() = default;
NewTabArgs(const Model::INewContentArgs& terminalArgs) :
_ContentArgs{ terminalArgs } {};
WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, nullptr);
WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, Model::NewTerminalArgs{});
public:
hstring GenerateName() const;
@@ -632,34 +632,54 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
h.write(ContentArgs());
return h.finalize();
}
uint32_t GetArgCount() const
{
return _ContentArgs.as<NewTerminalArgs>()->GetArgCount();
}
Model::ArgDescription GetArgDescriptionAt(uint32_t index) const
{
return _ContentArgs.as<NewTerminalArgs>()->GetArgDescriptionAt(index);
}
IInspectable GetArgAt(uint32_t index) const
{
return _ContentArgs.as<NewTerminalArgs>()->GetArgAt(index);
}
void SetArgAt(uint32_t index, IInspectable value)
{
_ContentArgs.as<NewTerminalArgs>()->SetArgAt(index, value);
}
};
struct SplitPaneArgs : public SplitPaneArgsT<SplitPaneArgs>
{
SplitPaneArgs() = default;
SplitPaneArgs() {
SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION)
};
SplitPaneArgs(SplitType splitMode, SplitDirection direction, float size, const Model::INewContentArgs& terminalArgs) :
_SplitMode{ splitMode },
_SplitDirection{ direction },
_SplitSize{ size },
_ContentArgs{ terminalArgs } {};
_ContentArgs{ terminalArgs } {
SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION)
};
SplitPaneArgs(SplitDirection direction, float size, const Model::INewContentArgs& terminalArgs) :
_SplitDirection{ direction },
_SplitSize{ size },
_ContentArgs{ terminalArgs } {};
_ContentArgs{ terminalArgs } {
SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION)
};
SplitPaneArgs(SplitDirection direction, const Model::INewContentArgs& terminalArgs) :
_SplitDirection{ direction },
_ContentArgs{ terminalArgs } {};
_ContentArgs{ terminalArgs } {
SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION)
};
SplitPaneArgs(SplitType splitMode) :
_SplitMode{ splitMode } {};
_SplitMode{ splitMode } {
SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION)
};
ACTION_ARG(Model::SplitDirection, SplitDirection, SplitDirection::Automatic);
WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, nullptr);
ACTION_ARG(SplitType, SplitMode, SplitType::Manual);
ACTION_ARG(float, SplitSize, 0.5f);
static constexpr std::string_view SplitKey{ "split" };
static constexpr std::string_view SplitModeKey{ "splitMode" };
static constexpr std::string_view SplitSizeKey{ "size" };
SPLIT_PANE_ARGS(DECLARE_ARGS);
WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, Model::NewTerminalArgs{});
public:
hstring GenerateName() const;
@@ -681,7 +701,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<SplitPaneArgs>();
JsonUtils::GetValueForKey(json, SplitKey, args->_SplitDirection);
JsonUtils::GetValueForKey(json, SplitDirectionKey, args->_SplitDirection);
JsonUtils::GetValueForKey(json, SplitModeKey, args->_SplitMode);
JsonUtils::GetValueForKey(json, SplitSizeKey, args->_SplitSize);
if (args->SplitSize() >= 1 || args->SplitSize() <= 0)
@@ -701,7 +721,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
const auto args{ get_self<SplitPaneArgs>(val) };
auto json{ ContentArgsToJson(args->_ContentArgs) };
JsonUtils::SetValueForKey(json, SplitKey, args->_SplitDirection);
JsonUtils::SetValueForKey(json, SplitDirectionKey, args->_SplitDirection);
JsonUtils::SetValueForKey(json, SplitModeKey, args->_SplitMode);
JsonUtils::SetValueForKey(json, SplitSizeKey, args->_SplitSize);
return json;
@@ -713,6 +733,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
copy->_ContentArgs = _ContentArgs.Copy();
copy->_SplitMode = _SplitMode;
copy->_SplitSize = _SplitSize;
copy->_argDescriptions = _argDescriptions;
return *copy;
}
size_t Hash() const
@@ -724,6 +745,59 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
h.write(SplitSize());
return h.finalize();
}
uint32_t GetArgCount() const
{
if (const auto newTermArgs = _ContentArgs.try_as<NewTerminalArgs>())
{
return newTermArgs->GetArgCount() + gsl::narrow<uint32_t>(_argDescriptions.size());
}
else
{
return gsl::narrow<uint32_t>(_argDescriptions.size());
}
}
Model::ArgDescription GetArgDescriptionAt(uint32_t index) const
{
const auto additionalArgCount = gsl::narrow<uint32_t>(_argDescriptions.size());
if (index < additionalArgCount)
{
return _argDescriptions.at(index);
}
else
{
return _ContentArgs.as<NewTerminalArgs>()->GetArgDescriptionAt(index - additionalArgCount);
}
}
IInspectable GetArgAt(uint32_t index) const
{
const auto additionalArgCount = gsl::narrow<uint32_t>(_argDescriptions.size());
if (index < additionalArgCount)
{
uint32_t curIndex{ 0 };
SPLIT_PANE_ARGS(GET_ARG_BY_INDEX);
}
else
{
return _ContentArgs.as<NewTerminalArgs>()->GetArgAt(index - additionalArgCount);
}
return nullptr;
}
void SetArgAt(uint32_t index, IInspectable value)
{
const auto additionalArgCount = gsl::narrow<uint32_t>(_argDescriptions.size());
if (index < additionalArgCount)
{
uint32_t curIndex{ 0 };
SPLIT_PANE_ARGS(SET_ARG_BY_INDEX);
}
else
{
_ContentArgs.as<NewTerminalArgs>()->SetArgAt(index - additionalArgCount, value);
}
}
private:
std::vector<ArgDescription> _argDescriptions;
};
struct NewWindowArgs : public NewWindowArgsT<NewWindowArgs>
@@ -731,7 +805,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
NewWindowArgs() = default;
NewWindowArgs(const Model::INewContentArgs& terminalArgs) :
_ContentArgs{ terminalArgs } {};
WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, nullptr);
WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, Model::NewTerminalArgs{});
public:
hstring GenerateName() const;
@@ -774,6 +848,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
h.write(ContentArgs());
return h.finalize();
}
uint32_t GetArgCount() const
{
return _ContentArgs.as<NewTerminalArgs>()->GetArgCount();
}
Model::ArgDescription GetArgDescriptionAt(uint32_t index) const
{
return _ContentArgs.as<NewTerminalArgs>()->GetArgDescriptionAt(index);
}
IInspectable GetArgAt(uint32_t index) const
{
return _ContentArgs.as<NewTerminalArgs>()->GetArgAt(index);
}
void SetArgAt(uint32_t index, IInspectable value)
{
_ContentArgs.as<NewTerminalArgs>()->SetArgAt(index, value);
}
};
ACTION_ARGS_STRUCT(CopyTextArgs, COPY_TEXT_ARGS);
@@ -913,6 +1003,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
h.write(winrt::get_abi(_Actions));
return h.finalize();
}
uint32_t GetArgCount() const
{
return _Actions.Size();
}
Model::ArgDescription GetArgDescriptionAt(uint32_t /*index*/) const
{
return {};
}
IInspectable GetArgAt(uint32_t /*index*/) const
{
return nullptr;
}
void SetArgAt(uint32_t /*index*/, IInspectable /*value*/)
{
throw winrt::hresult_not_implemented();
}
};
ACTION_ARGS_STRUCT(AdjustOpacityArgs, ADJUST_OPACITY_ARGS);

View File

@@ -5,12 +5,24 @@ import "Command.idl";
namespace Microsoft.Terminal.Settings.Model
{
struct ArgDescription
{
String Name;
String Type;
Boolean Required;
};
interface IActionArgs
{
Boolean Equals(IActionArgs other);
String GenerateName();
IActionArgs Copy();
UInt64 Hash();
UInt32 GetArgCount();
ArgDescription GetArgDescriptionAt(UInt32 index);
IInspectable GetArgAt(UInt32 index);
void SetArgAt(UInt32 index, Object value);
};
interface IActionEventArgs
@@ -168,6 +180,11 @@ namespace Microsoft.Terminal.Settings.Model
UInt64 ContentId{ get; set; };
String ToCommandline();
UInt32 GetArgCount();
ArgDescription GetArgDescriptionAt(UInt32 index);
IInspectable GetArgAt(UInt32 index);
void SetArgAt(UInt32 index, Object value);
};
[default_interface] runtimeclass ActionEventArgs : IActionEventArgs
@@ -230,7 +247,7 @@ namespace Microsoft.Terminal.Settings.Model
{
SendInputArgs(String input);
String Input { get; };
String Input;
};
[default_interface] runtimeclass SplitPaneArgs : IActionArgs
@@ -309,7 +326,7 @@ namespace Microsoft.Terminal.Settings.Model
[default_interface] runtimeclass CloseTabArgs : IActionArgs
{
CloseTabArgs(UInt32 tabIndex);
Windows.Foundation.IReference<UInt32> Index { get; };
Windows.Foundation.IReference<UInt32> Index;
};
[default_interface] runtimeclass MoveTabArgs : IActionArgs

View File

@@ -66,12 +66,43 @@ struct InitListPlaceholder
#define CTOR_INIT(type, name, jsonKey, required, ...) \
_##name{ name##Param },
// append this argument's description to the internal vector
#define APPEND_ARG_DESCRIPTION(type, name, jsonKey, required, ...) \
_argDescriptions.push_back({ L## #name, L## #type, std::wstring_view(L## #required) != L"false" });
// check each property in the Equals() method. You'll note there's a stray
// `true` in the definition of Equals() below, that's to deal with trailing
// commas
#define EQUALS_ARGS(type, name, jsonKey, required, ...) \
&&(otherAsUs->_##name == _##name)
// getter and setter for each property by index
#define GET_ARG_BY_INDEX(type, name, jsonKey, required, ...) \
if (index == curIndex++) \
{ \
if (_##name.has_value()) \
{ \
return winrt::box_value(_##name.value()); \
} \
else \
{ \
return winrt::box_value(static_cast<type>(__VA_ARGS__)); \
} \
}
#define SET_ARG_BY_INDEX(type, name, jsonKey, required, ...) \
if (index == curIndex++) \
{ \
if (value) \
{ \
_##name = winrt::unbox_value<type>(value); \
} \
else \
{ \
_##name = std::nullopt; \
} \
}
// JSON deserialization. If the parameter is required to pass any validation,
// add that as the `required` parameter here, as the body of a conditional
// EX: For the RESIZE_PANE_ARGS
@@ -79,11 +110,11 @@ struct InitListPlaceholder
// the bit
// args->ResizeDirection() == ResizeDirection::None
// is used as the conditional for the validation here.
#define FROM_JSON_ARGS(type, name, jsonKey, required, ...) \
JsonUtils::GetValueForKey(json, jsonKey, args->_##name); \
if (required) \
{ \
return { nullptr, { SettingsLoadWarnings::MissingRequiredParameter } }; \
#define FROM_JSON_ARGS(type, name, jsonKey, required, ...) \
JsonUtils::GetValueForKey(json, jsonKey, args->_##name); \
if (required) \
{ \
return { nullptr, { SettingsLoadWarnings::MissingRequiredParameter } }; \
}
// JSON serialization
@@ -111,53 +142,110 @@ struct InitListPlaceholder
// * NewTerminalArgs has a ToCommandline method it needs to additionally declare.
// * GlobalSummonArgs has the QuakeModeFromJson helper
#define ACTION_ARG_BODY(className, argsMacro) \
className() = default; \
className( \
argsMacro(CTOR_PARAMS) InitListPlaceholder = {}) : \
argsMacro(CTOR_INIT) _placeholder{} {}; \
argsMacro(DECLARE_ARGS); \
\
private: \
InitListPlaceholder _placeholder; \
\
public: \
hstring GenerateName() const; \
bool Equals(const IActionArgs& other) \
{ \
auto otherAsUs = other.try_as<className>(); \
if (otherAsUs) \
{ \
return true argsMacro(EQUALS_ARGS); \
} \
return false; \
}; \
static FromJsonResult FromJson(const Json::Value& json) \
{ \
auto args = winrt::make_self<className>(); \
argsMacro(FROM_JSON_ARGS); \
return { *args, {} }; \
} \
static Json::Value ToJson(const IActionArgs& val) \
{ \
if (!val) \
{ \
return {}; \
} \
Json::Value json{ Json::ValueType::objectValue }; \
const auto args{ get_self<className>(val) }; \
argsMacro(TO_JSON_ARGS); \
return json; \
} \
IActionArgs Copy() const \
{ \
auto copy{ winrt::make_self<className>() }; \
argsMacro(COPY_ARGS); \
return *copy; \
} \
size_t Hash() const \
{ \
til::hasher h; \
argsMacro(HASH_ARGS); \
return h.finalize(); \
#define ACTION_ARG_BODY(className, argsMacro) \
className() { argsMacro(APPEND_ARG_DESCRIPTION) }; \
className( \
argsMacro(CTOR_PARAMS) InitListPlaceholder = {}) : \
argsMacro(CTOR_INIT) _placeholder{} { \
argsMacro(APPEND_ARG_DESCRIPTION) \
}; \
argsMacro(DECLARE_ARGS); \
\
private: \
InitListPlaceholder _placeholder; \
std::vector<ArgDescription> _argDescriptions; \
\
public: \
hstring GenerateName() const; \
bool Equals(const IActionArgs& other) \
{ \
auto otherAsUs = other.try_as<className>(); \
if (otherAsUs) \
{ \
return true argsMacro(EQUALS_ARGS); \
} \
return false; \
}; \
static FromJsonResult FromJson(const Json::Value& json) \
{ \
auto args = winrt::make_self<className>(); \
argsMacro(FROM_JSON_ARGS); \
return { *args, {} }; \
} \
static Json::Value ToJson(const IActionArgs& val) \
{ \
if (!val) \
{ \
return {}; \
} \
Json::Value json{ Json::ValueType::objectValue }; \
const auto args{ get_self<className>(val) }; \
argsMacro(TO_JSON_ARGS); \
return json; \
} \
IActionArgs Copy() const \
{ \
auto copy{ winrt::make_self<className>() }; \
argsMacro(COPY_ARGS); \
copy->_argDescriptions = _argDescriptions; \
return *copy; \
} \
size_t Hash() const \
{ \
til::hasher h; \
argsMacro(HASH_ARGS); \
return h.finalize(); \
} \
uint32_t GetArgCount() const \
{ \
return gsl::narrow<uint32_t>(_argDescriptions.size()); \
} \
ArgDescription GetArgDescriptionAt(uint32_t index) const \
{ \
return _argDescriptions.at(index); \
} \
IInspectable GetArgAt(uint32_t index) const \
{ \
uint32_t curIndex{ 0 }; \
argsMacro(GET_ARG_BY_INDEX) \
return nullptr; \
} \
void SetArgAt(uint32_t index, IInspectable value) \
{ \
uint32_t curIndex{ 0 }; \
argsMacro(SET_ARG_BY_INDEX) \
}
#define PARTIAL_ACTION_ARG_BODY(className, argsMacro) \
className(){ argsMacro(APPEND_ARG_DESCRIPTION) }; \
className( \
argsMacro(CTOR_PARAMS) InitListPlaceholder = {}) : \
argsMacro(CTOR_INIT) _placeholder{} { \
argsMacro(APPEND_ARG_DESCRIPTION) \
}; \
argsMacro(DECLARE_ARGS); \
\
private: \
InitListPlaceholder _placeholder; \
std::vector<ArgDescription> _argDescriptions; \
\
public: \
uint32_t GetArgCount() const \
{ \
return gsl::narrow<uint32_t>(_argDescriptions.size()); \
} \
ArgDescription GetArgDescriptionAt(uint32_t index) const \
{ \
return _argDescriptions.at(index); \
} \
IInspectable GetArgAt(uint32_t index) const \
{ \
uint32_t curIndex{ 0 }; \
argsMacro(GET_ARG_BY_INDEX) \
return nullptr; \
} \
void SetArgAt(uint32_t index, IInspectable value) \
{ \
uint32_t curIndex{ 0 }; \
argsMacro(SET_ARG_BY_INDEX) \
}

View File

@@ -380,6 +380,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return _ResolvedKeyToActionMapCache.GetView();
}
IVectorView<Model::Command> ActionMap::AllCommands()
{
if (!_ResolvedKeyToActionMapCache)
{
_RefreshKeyBindingCaches();
}
return _AllCommandsCache.GetView();
}
void ActionMap::_RefreshKeyBindingCaches()
{
_CumulativeKeyToActionMapCache.clear();
@@ -387,6 +396,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
_CumulativeActionToKeyMapCache.clear();
std::unordered_map<KeyChord, Model::Command, KeyChordHash, KeyChordEquality> globalHotkeys;
std::unordered_map<KeyChord, Model::Command, KeyChordHash, KeyChordEquality> resolvedKeyToActionMap;
std::vector<Model::Command> allCommandsVector;
_PopulateCumulativeKeyMaps(_CumulativeKeyToActionMapCache, _CumulativeActionToKeyMapCache);
_PopulateCumulativeActionMap(_CumulativeIDToActionMapCache);
@@ -406,8 +416,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
}
for (const auto& [_, cmd] : _CumulativeIDToActionMapCache)
{
allCommandsVector.emplace_back(cmd);
}
_ResolvedKeyToActionMapCache = single_threaded_map(std::move(resolvedKeyToActionMap));
_GlobalHotkeysCache = single_threaded_map(std::move(globalHotkeys));
_AllCommandsCache = single_threaded_vector(std::move(allCommandsVector));
}
com_ptr<ActionMap> ActionMap::Copy() const
@@ -421,7 +437,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
actionMap->_ActionMap.reserve(_ActionMap.size());
for (const auto& [actionID, cmd] : _ActionMap)
{
actionMap->_ActionMap.emplace(actionID, *winrt::get_self<Command>(cmd)->Copy());
const auto copiedCmd = winrt::get_self<Command>(cmd)->Copy();
actionMap->_ActionMap.emplace(actionID, *copiedCmd);
copiedCmd->IDChanged({ actionMap.get(), &ActionMap::_CommandIDChangedHandler });
}
// Name --> Command
@@ -541,6 +559,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
}
}
cmd.IDChanged({ this, &ActionMap::_CommandIDChangedHandler });
_ActionMap.insert_or_assign(cmdID, cmd);
}
}
@@ -573,6 +592,44 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
_changeLog.emplace(KeysKey);
}
void ActionMap::_CommandIDChangedHandler(const Model::Command& senderCmd, const winrt::hstring& oldID)
{
const auto newID = senderCmd.ID();
if (newID != oldID)
{
if (const auto foundCmd{ _GetActionByID(newID) })
{
if (foundCmd.ActionAndArgs() != senderCmd.ActionAndArgs())
{
// we found a command that has the same ID as this one, but that command has different ActionAndArgs
// this means that foundCommand's action and/or args have been changed since its ID was generated,
// generate a new one for it
// Note: this is recursive! Found command's ID being changed lands us back in here to resolve any cascading collisions
foundCmd.GenerateID();
}
}
// update _ActionMap with the ID change
_ActionMap.erase(oldID);
_ActionMap.emplace(newID, senderCmd);
// update _KeyMap so that all keys that pointed to the old ID now point to the new ID
std::unordered_set<KeyChord, KeyChordHash, KeyChordEquality> keysToRemap{};
for (const auto& [keys, cmdID] : _KeyMap)
{
if (cmdID == oldID)
{
keysToRemap.insert(keys);
}
}
for (const auto& keys : keysToRemap)
{
_KeyMap.erase(keys);
_KeyMap.emplace(keys, newID);
}
}
_RefreshKeyBindingCaches();
}
// Method Description:
// - Determines whether the given key chord is explicitly unbound
// Arguments:
@@ -686,6 +743,24 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return nullptr;
}
IVector<Control::KeyChord> ActionMap::AllKeyBindingsForAction(const winrt::hstring& cmdID)
{
if (!_ResolvedKeyToActionMapCache)
{
_RefreshKeyBindingCaches();
}
std::vector<Control::KeyChord> keybindingsList;
for (const auto& [key, ID] : _CumulativeKeyToActionMapCache)
{
if (ID == cmdID)
{
keybindingsList.emplace_back(key);
}
}
return single_threaded_vector(std::move(keybindingsList));
}
// Method Description:
// - Rebinds a key binding to a new key chord
// Arguments:
@@ -741,6 +816,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
}
void ActionMap::AddKeyBinding(Control::KeyChord keys, const winrt::hstring& cmdID)
{
_KeyMap.insert_or_assign(keys, cmdID);
_RefreshKeyBindingCaches();
}
// Method Description:
// - Add a new key binding
// - If the key chord is already in use, the conflicting command is overwritten.
@@ -757,6 +838,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
AddAction(*cmd, keys);
}
void ActionMap::DeleteUserCommand(const winrt::hstring& cmdID)
{
_ActionMap.erase(cmdID);
_RefreshKeyBindingCaches();
}
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
static bool _compareSchemeNames(const ColorScheme& lhs, const ColorScheme& rhs)
{

View File

@@ -56,6 +56,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
Windows::Foundation::Collections::IMapView<hstring, Model::Command> NameMap();
Windows::Foundation::Collections::IMapView<Control::KeyChord, Model::Command> GlobalHotkeys();
Windows::Foundation::Collections::IMapView<Control::KeyChord, Model::Command> KeyBindings();
Windows::Foundation::Collections::IVectorView<Model::Command> AllCommands();
com_ptr<ActionMap> Copy() const;
// queries
@@ -63,6 +64,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
Model::Command GetActionByID(const winrt::hstring& cmdID) const;
bool IsKeyChordExplicitlyUnbound(const Control::KeyChord& keys) const;
Control::KeyChord GetKeyBindingForAction(const winrt::hstring& cmdID);
Windows::Foundation::Collections::IVector<Control::KeyChord> AllKeyBindingsForAction(const winrt::hstring& cmdID);
// population
void AddAction(const Model::Command& cmd, const Control::KeyChord& keys);
@@ -78,7 +80,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// modification
bool RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys);
void DeleteKeyBinding(const Control::KeyChord& keys);
void AddKeyBinding(Control::KeyChord keys, const winrt::hstring& cmdID);
void RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action);
void DeleteUserCommand(const winrt::hstring& cmdID);
void AddSendInputAction(winrt::hstring name, winrt::hstring input, const Control::KeyChord keys);
Windows::Foundation::Collections::IVector<Model::Command> ExpandedCommands();
@@ -105,6 +109,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
std::vector<Model::Command> _updateLocalSnippetCache(winrt::hstring currentWorkingDirectory);
void _CommandIDChangedHandler(const Model::Command& senderCmd, const winrt::hstring& oldID);
Windows::Foundation::Collections::IMap<hstring, Model::ActionAndArgs> _AvailableActionsCache{ nullptr };
Windows::Foundation::Collections::IMap<hstring, Model::Command> _NameMapCache{ nullptr };
Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _GlobalHotkeysCache{ nullptr };
@@ -136,6 +142,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// This is effectively a combination of _CumulativeKeyMapCache and _CumulativeActionMapCache and its purpose is so that
// we can give the SUI a view of the key chords and the commands they map to
Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _ResolvedKeyToActionMapCache{ nullptr };
Windows::Foundation::Collections::IVector<Model::Command> _AllCommandsCache{ nullptr };
til::shared_mutex<std::unordered_map<hstring, std::vector<Model::Command>>> _cwdLocalSnippetsCache{};

View File

@@ -13,12 +13,14 @@ namespace Microsoft.Terminal.Settings.Model
Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys);
Command GetActionByID(String cmdID);
Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(String cmdID);
IVector<Microsoft.Terminal.Control.KeyChord> AllKeyBindingsForAction(String cmdID);
Windows.Foundation.Collections.IMapView<String, ActionAndArgs> AvailableActions { get; };
Windows.Foundation.Collections.IMapView<String, Command> NameMap { get; };
Windows.Foundation.Collections.IMapView<Microsoft.Terminal.Control.KeyChord, Command> KeyBindings { get; };
Windows.Foundation.Collections.IMapView<Microsoft.Terminal.Control.KeyChord, Command> GlobalHotkeys { get; };
Windows.Foundation.Collections.IVectorView<Command> AllCommands { get; };
IVector<Command> ExpandedCommands { get; };
@@ -27,9 +29,11 @@ namespace Microsoft.Terminal.Settings.Model
[default_interface] runtimeclass ActionMap : IActionMapView
{
void AddAction(Command cmd, Microsoft.Terminal.Control.KeyChord keys);
void RebindKeys(Microsoft.Terminal.Control.KeyChord oldKeys, Microsoft.Terminal.Control.KeyChord newKeys);
void DeleteKeyBinding(Microsoft.Terminal.Control.KeyChord keys);
void DeleteUserCommand(String cmdID);
void AddKeyBinding(Microsoft.Terminal.Control.KeyChord keys, String cmdID);
void RegisterKeyBinding(Microsoft.Terminal.Control.KeyChord keys, ActionAndArgs action);
void AddSendInputAction(String name, String input, Microsoft.Terminal.Control.KeyChord keys);
}

View File

@@ -309,6 +309,31 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
_throttler();
}
bool ApplicationState::DismissBadge(const hstring& badgeId)
{
bool inserted{ false };
{
const auto state = _state.lock();
if (!state->DismissedBadges)
{
state->DismissedBadges = std::unordered_set<hstring>{};
}
inserted = state->DismissedBadges->insert(badgeId).second;
}
_throttler();
return inserted;
}
bool ApplicationState::BadgeDismissed(const hstring& badgeId) const
{
const auto state = _state.lock_shared();
if (state->DismissedBadges)
{
return state->DismissedBadges->contains(badgeId);
}
return false;
}
// Generate all getter/setters
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
type ApplicationState::name() const noexcept \

View File

@@ -40,7 +40,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
X(FileSource::Local, Windows::Foundation::Collections::IVector<Model::WindowLayout>, PersistedWindowLayouts, "persistedWindowLayouts") \
X(FileSource::Shared, Windows::Foundation::Collections::IVector<hstring>, RecentCommands, "recentCommands") \
X(FileSource::Shared, Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage>, DismissedMessages, "dismissedMessages") \
X(FileSource::Local, Windows::Foundation::Collections::IVector<hstring>, AllowedCommandlines, "allowedCommandlines")
X(FileSource::Local, Windows::Foundation::Collections::IVector<hstring>, AllowedCommandlines, "allowedCommandlines") \
X(FileSource::Local, std::unordered_set<hstring>, DismissedBadges, "dismissedBadges")
struct WindowLayout : WindowLayoutT<WindowLayout>
{
@@ -70,6 +71,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
Json::Value ToJson(FileSource parseSource) const noexcept;
void AppendPersistedWindowLayout(Model::WindowLayout layout);
bool DismissBadge(const hstring& badgeId);
bool BadgeDismissed(const hstring& badgeId) const;
// State getters/setters
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \

View File

@@ -33,6 +33,8 @@ namespace Microsoft.Terminal.Settings.Model
void Reset();
void AppendPersistedWindowLayout(WindowLayout layout);
Boolean DismissBadge(String badgeId);
Boolean BadgeDismissed(String badgeId);
String SettingsHash;
Windows.Foundation.Collections.IVector<WindowLayout> PersistedWindowLayouts;

View File

@@ -8,16 +8,29 @@
#include "../../inc/DefaultSettings.h"
#include "DynamicProfileUtils.h"
#include <LibraryResources.h>
using namespace ::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::TerminalConnection;
std::wstring_view GENERATOR_ICON_PATH{ L"ms-appx:///ProfileGeneratorIcons/AzureCloudShell.png" };
std::wstring_view AzureCloudShellGenerator::GetNamespace() const noexcept
{
return AzureGeneratorNamespace;
}
std::wstring_view AzureCloudShellGenerator::GetDisplayName() const noexcept
{
return RS_(L"AzureCloudShellGeneratorDisplayName");
}
std::wstring_view AzureCloudShellGenerator::GetIcon() const noexcept
{
return GENERATOR_ICON_PATH;
}
// 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.
@@ -25,7 +38,7 @@ std::wstring_view AzureCloudShellGenerator::GetNamespace() const noexcept
// - <none>
// Return Value:
// - a vector with the Azure Cloud Shell connection profile, if available.
void AzureCloudShellGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
void AzureCloudShellGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
{
if (AzureConnection::IsAzureConnectionAvailable())
{

View File

@@ -25,6 +25,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model
{
public:
std::wstring_view GetNamespace() const noexcept override;
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
std::wstring_view GetDisplayName() const noexcept override;
std::wstring_view GetIcon() const noexcept override;
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
};
};

View File

@@ -8,6 +8,8 @@
#include "DefaultTerminal.h"
#include "FileUtils.h"
#include "AllShortcutActions.h"
#include <LibraryResources.h>
#include <VersionHelpers.h>
#include <WtExeUtils.h>
@@ -113,6 +115,10 @@ Model::CascadiaSettings CascadiaSettings::Copy() const
settings->_globals = _globals->Copy();
settings->_allProfiles = winrt::single_threaded_observable_vector(std::move(allProfiles));
settings->_activeProfiles = winrt::single_threaded_observable_vector(std::move(activeProfiles));
// extension packages don't need a deep clone
// because they're fully immutable. We can just copy the reference over instead.
settings->_extensionPackages = _extensionPackages;
}
// load errors
@@ -173,6 +179,16 @@ IObservableVector<Model::Profile> CascadiaSettings::ActiveProfiles() const noexc
return _activeProfiles;
}
IVectorView<Model::ExtensionPackage> CascadiaSettings::Extensions()
{
if (!_extensionPackages)
{
// Lazy load the ExtensionPackage objects
_extensionPackages = winrt::single_threaded_vector<Model::ExtensionPackage>(std::move(SettingsLoader::LoadExtensionPackages()));
}
return _extensionPackages.GetView();
}
// Method Description:
// - Returns the globally configured keybindings
// Arguments:
@@ -980,6 +996,109 @@ winrt::hstring CascadiaSettings::ApplicationVersion()
return RS_(L"ApplicationVersionUnknown");
}
winrt::Windows::Foundation::Collections::IMap<Model::ShortcutAction, winrt::hstring> CascadiaSettings::AvailableShortcutActionsAndNames()
{
std::map<ShortcutAction, winrt::hstring> availableShortcutActionsAndNames;
#define ON_ALL_ACTIONS(action) availableShortcutActionsAndNames.emplace(ShortcutAction::action, RS_(L## #action));
ALL_SHORTCUT_ACTIONS
// Don't include internal actions here
#undef ON_ALL_ACTIONS
return single_threaded_map(std::move(availableShortcutActionsAndNames));
}
Model::IActionArgs CascadiaSettings::GetEmptyArgsForAction(Model::ShortcutAction shortcutAction)
{
switch (shortcutAction)
{
case Model::ShortcutAction::CopyText:
return winrt::make<CopyTextArgs>();
case Model::ShortcutAction::MovePane:
return winrt::make<MovePaneArgs>();
case Model::ShortcutAction::SwitchToTab:
return winrt::make<SwitchToTabArgs>();
case Model::ShortcutAction::ResizePane:
return winrt::make<ResizePaneArgs>();
case Model::ShortcutAction::MoveFocus:
return winrt::make<MoveFocusArgs>();
case Model::ShortcutAction::SwapPane:
return winrt::make<SwapPaneArgs>();
case Model::ShortcutAction::AdjustFontSize:
return winrt::make<AdjustFontSizeArgs>();
case Model::ShortcutAction::SendInput:
return winrt::make<SendInputArgs>();
case Model::ShortcutAction::OpenSettings:
return winrt::make<OpenSettingsArgs>();
case Model::ShortcutAction::SetFocusMode:
return winrt::make<SetFocusModeArgs>();
case Model::ShortcutAction::SetFullScreen:
return winrt::make<SetFullScreenArgs>();
case Model::ShortcutAction::SetMaximized:
return winrt::make<SetMaximizedArgs>();
case Model::ShortcutAction::SetColorScheme:
return winrt::make<SetColorSchemeArgs>();
case Model::ShortcutAction::RenameTab:
return winrt::make<RenameTabArgs>();
case Model::ShortcutAction::ExecuteCommandline:
return winrt::make<ExecuteCommandlineArgs>();
case Model::ShortcutAction::CloseOtherTabs:
return winrt::make<CloseOtherTabsArgs>();
case Model::ShortcutAction::CloseTabsAfter:
return winrt::make<CloseTabsAfterArgs>();
case Model::ShortcutAction::CloseTab:
return winrt::make<CloseTabArgs>();
case Model::ShortcutAction::MoveTab:
return winrt::make<MoveTabArgs>();
case Model::ShortcutAction::ScrollUp:
return winrt::make<ScrollUpArgs>();
case Model::ShortcutAction::ScrollDown:
return winrt::make<ScrollDownArgs>();
case Model::ShortcutAction::ScrollToMark:
return winrt::make<ScrollToMarkArgs>();
case Model::ShortcutAction::ToggleCommandPalette:
return winrt::make<ToggleCommandPaletteArgs>();
case Model::ShortcutAction::Suggestions:
return winrt::make<SuggestionsArgs>();
case Model::ShortcutAction::FindMatch:
return winrt::make<FindMatchArgs>();
case Model::ShortcutAction::RenameWindow:
return winrt::make<RenameWindowArgs>();
case Model::ShortcutAction::SearchForText:
return winrt::make<SearchForTextArgs>();
case Model::ShortcutAction::GlobalSummon:
return winrt::make<GlobalSummonArgs>();
case Model::ShortcutAction::FocusPane:
return winrt::make<FocusPaneArgs>();
case Model::ShortcutAction::ExportBuffer:
return winrt::make<ExportBufferArgs>();
case Model::ShortcutAction::ClearBuffer:
return winrt::make<ClearBufferArgs>();
case Model::ShortcutAction::AdjustOpacity:
return winrt::make<AdjustOpacityArgs>();
case Model::ShortcutAction::SelectCommand:
return winrt::make<SelectCommandArgs>();
case Model::ShortcutAction::SelectOutput:
return winrt::make<SelectOutputArgs>();
case Model::ShortcutAction::AddMark:
return winrt::make<AddMarkArgs>();
case Model::ShortcutAction::SetTabColor:
return winrt::make<SetTabColorArgs>();
case Model::ShortcutAction::PrevTab:
return winrt::make<PrevTabArgs>();
case Model::ShortcutAction::NextTab:
return winrt::make<NextTabArgs>();
case Model::ShortcutAction::NewTab:
return winrt::make<NewTabArgs>();
case Model::ShortcutAction::NewWindow:
return winrt::make<NewWindowArgs>();
case Model::ShortcutAction::SplitPane:
return winrt::make<SplitPaneArgs>();
default:
return nullptr;
}
}
// Method Description:
// - Determines if we're on an OS platform that supports
// the default terminal handoff functionality.

View File

@@ -18,6 +18,10 @@ Author(s):
#pragma once
#include "CascadiaSettings.g.h"
#include "FragmentSettings.g.h"
#include "FragmentProfileEntry.g.h"
#include "FragmentColorSchemeEntry.g.h"
#include "ExtensionPackage.g.h"
#include "GlobalAppSettings.h"
#include "Profile.h"
@@ -39,6 +43,28 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
std::runtime_error(message) {}
};
struct ExtensionPackage : ExtensionPackageT<ExtensionPackage>
{
public:
ExtensionPackage(hstring source, FragmentScope scope) :
_source{ source },
_scope{ scope },
_fragments{ winrt::single_threaded_vector<Model::FragmentSettings>() } {}
hstring Source() const noexcept { return _source; }
FragmentScope Scope() const noexcept { return _scope; }
Windows::Foundation::Collections::IVectorView<Model::FragmentSettings> FragmentsView() const noexcept { return _fragments.GetView(); }
Windows::Foundation::Collections::IVector<Model::FragmentSettings> Fragments() const noexcept { return _fragments; }
WINRT_PROPERTY(hstring, Icon);
WINRT_PROPERTY(hstring, DisplayName);
private:
hstring _source;
FragmentScope _scope;
Windows::Foundation::Collections::IVector<Model::FragmentSettings> _fragments;
};
struct ParsedSettings
{
winrt::com_ptr<implementation::GlobalAppSettings> globals;
@@ -56,12 +82,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
struct SettingsLoader
{
static SettingsLoader Default(const std::string_view& userJSON, const std::string_view& inboxJSON);
static std::vector<Model::ExtensionPackage> LoadExtensionPackages();
SettingsLoader(const std::string_view& userJSON, const std::string_view& inboxJSON);
void GenerateProfiles();
void GenerateExtensionPackagesFromProfileGenerators();
void ApplyRuntimeInitialSettings();
void MergeInboxIntoUserSettings();
void FindFragmentsAndMergeIntoUserSettings();
void FindFragmentsAndMergeIntoUserSettings(bool generateExtensionPackages);
void MergeFragmentIntoUserSettings(const winrt::hstring& source, const std::string_view& content);
void FinalizeLayering();
bool DisableDeletedProfiles();
@@ -70,6 +98,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
ParsedSettings inboxSettings;
ParsedSettings userSettings;
std::unordered_map<hstring, winrt::com_ptr<implementation::ExtensionPackage>> extensionPackageMap;
bool duplicateProfile = false;
private:
@@ -81,6 +110,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
const Json::Value& profilesList;
const Json::Value& themes;
};
struct ParseFragmentMetadata
{
std::wstring_view jsonFilename;
FragmentScope scope;
};
SettingsLoader() = default;
static std::pair<size_t, size_t> _lineAndColumnFromPosition(const std::string_view& string, const size_t position);
static void _rethrowSerializationExceptionWithLocationInfo(const JsonUtils::DeserializationError& e, const std::string_view& settingsString);
@@ -88,13 +123,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static const Json::Value& _getJSONValue(const Json::Value& json, const std::string_view& key) noexcept;
std::span<const winrt::com_ptr<implementation::Profile>> _getNonUserOriginProfiles() const;
void _parse(const OriginTag origin, const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings);
void _parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings);
void _parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings, const std::optional<ParseFragmentMetadata>& fragmentMeta);
static JsonSettings _parseJson(const std::string_view& content);
static winrt::com_ptr<implementation::Profile> _parseProfile(const OriginTag origin, const winrt::hstring& source, const Json::Value& profileJson);
void _appendProfile(winrt::com_ptr<Profile>&& profile, const winrt::guid& guid, ParsedSettings& settings);
void _addUserProfileParent(const winrt::com_ptr<implementation::Profile>& profile);
void _addOrMergeUserColorScheme(const winrt::com_ptr<implementation::ColorScheme>& colorScheme);
void _executeGenerator(const IDynamicProfileGenerator& generator);
bool _addOrMergeUserColorScheme(const winrt::com_ptr<implementation::ColorScheme>& colorScheme);
static void _executeGenerator(IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList);
void _patchInstallPowerShellProfile();
winrt::com_ptr<implementation::ExtensionPackage> _registerFragment(const winrt::Microsoft::Terminal::Settings::Model::FragmentSettings& fragment, FragmentScope scope);
Json::StreamWriterBuilder _getJsonStyledWriter();
std::unordered_set<winrt::hstring, til::transparent_hstring_hash, til::transparent_hstring_equal_to> _ignoredNamespaces;
std::set<std::string> themesChangeLog;
@@ -115,6 +153,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static winrt::hstring ApplicationVersion();
static bool IsPortableMode();
static Windows::Foundation::Collections::IMap<Model::ShortcutAction, winrt::hstring> AvailableShortcutActionsAndNames();
static Model::IActionArgs GetEmptyArgsForAction(Model::ShortcutAction shortcutAction);
CascadiaSettings() noexcept = default;
CascadiaSettings(const winrt::hstring& userJSON, const winrt::hstring& inboxJSON);
CascadiaSettings(const std::string_view& userJSON, const std::string_view& inboxJSON = {});
@@ -127,6 +168,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
winrt::Windows::Foundation::Collections::IObservableVector<Model::Profile> AllProfiles() const noexcept;
winrt::Windows::Foundation::Collections::IObservableVector<Model::Profile> ActiveProfiles() const noexcept;
Model::ActionMap ActionMap() const noexcept;
winrt::Windows::Foundation::Collections::IVectorView<Model::ExtensionPackage> Extensions();
void WriteSettingsToDisk();
Json::Value ToJson() const;
Model::Profile ProfileDefaults() const;
@@ -184,6 +226,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
winrt::com_ptr<implementation::Profile> _baseLayerProfile = winrt::make_self<implementation::Profile>();
winrt::Windows::Foundation::Collections::IObservableVector<Model::Profile> _allProfiles = winrt::single_threaded_observable_vector<Model::Profile>();
winrt::Windows::Foundation::Collections::IObservableVector<Model::Profile> _activeProfiles = winrt::single_threaded_observable_vector<Model::Profile>();
winrt::Windows::Foundation::Collections::IVector<Model::ExtensionPackage> _extensionPackages = nullptr;
std::set<std::string> _themesChangeLog{};
// load errors
@@ -199,6 +242,67 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
mutable std::once_flag _commandLinesCacheOnce;
mutable std::vector<std::pair<std::wstring, Model::Profile>> _commandLinesCache;
};
struct FragmentProfileEntry : FragmentProfileEntryT<FragmentProfileEntry>
{
public:
FragmentProfileEntry(winrt::guid profileGuid, hstring json) :
_profileGuid{ profileGuid },
Json{ json } {}
winrt::guid ProfileGuid() const noexcept { return _profileGuid; }
til::property<hstring> Json;
private:
winrt::guid _profileGuid;
};
struct FragmentColorSchemeEntry : FragmentColorSchemeEntryT<FragmentColorSchemeEntry>
{
public:
FragmentColorSchemeEntry(hstring schemeName, hstring json) :
_schemeName{ schemeName },
_json{ json } {}
hstring ColorSchemeName() const noexcept { return _schemeName; }
hstring Json() const noexcept { return _json; }
private:
hstring _schemeName;
hstring _json;
};
struct FragmentSettings : FragmentSettingsT<FragmentSettings>
{
public:
FragmentSettings(hstring source, hstring json, hstring filename) :
_source{ source },
_Json{ json },
_filename{ filename } {}
hstring Source() const noexcept { return _source; }
hstring Filename() const noexcept { return _filename; }
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> ModifiedProfiles() const noexcept { return _modifiedProfiles; }
void ModifiedProfiles(const Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry>& modifiedProfiles) noexcept { _modifiedProfiles = modifiedProfiles; }
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> NewProfiles() const noexcept { return _newProfiles; }
void NewProfiles(const Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry>& newProfiles) noexcept { _newProfiles = newProfiles; }
Windows::Foundation::Collections::IVector<Model::FragmentColorSchemeEntry> ColorSchemes() const noexcept { return _colorSchemes; }
void ColorSchemes(const Windows::Foundation::Collections::IVector<Model::FragmentColorSchemeEntry>& colorSchemes) noexcept { _colorSchemes = colorSchemes; }
WINRT_PROPERTY(hstring, Json);
public:
// views
Windows::Foundation::Collections::IVectorView<Model::FragmentProfileEntry> ModifiedProfilesView() const noexcept { return _modifiedProfiles ? _modifiedProfiles.GetView() : nullptr; }
Windows::Foundation::Collections::IVectorView<Model::FragmentProfileEntry> NewProfilesView() const noexcept { return _newProfiles ? _newProfiles.GetView() : nullptr; }
Windows::Foundation::Collections::IVectorView<Model::FragmentColorSchemeEntry> ColorSchemesView() const noexcept { return _colorSchemes ? _colorSchemes.GetView() : nullptr; }
private:
hstring _source;
hstring _filename;
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> _modifiedProfiles;
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> _newProfiles;
Windows::Foundation::Collections::IVector<Model::FragmentColorSchemeEntry> _colorSchemes;
};
}
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation

View File

@@ -5,9 +5,16 @@ import "GlobalAppSettings.idl";
import "Profile.idl";
import "TerminalWarnings.idl";
import "DefaultTerminal.idl";
import "ActionArgs.idl";
namespace Microsoft.Terminal.Settings.Model
{
enum FragmentScope
{
User,
Machine
};
[default_interface] runtimeclass CascadiaSettings {
static CascadiaSettings LoadDefaults();
static CascadiaSettings LoadAll();
@@ -20,6 +27,9 @@ namespace Microsoft.Terminal.Settings.Model
static String ApplicationDisplayName { get; };
static String ApplicationVersion { get; };
static Windows.Foundation.Collections.IMap<Microsoft.Terminal.Settings.Model.ShortcutAction, String> AvailableShortcutActionsAndNames { get; };
static IActionArgs GetEmptyArgsForAction(Microsoft.Terminal.Settings.Model.ShortcutAction shortcutAction);
CascadiaSettings(String userJSON, String inboxJSON);
CascadiaSettings Copy();
@@ -38,6 +48,7 @@ namespace Microsoft.Terminal.Settings.Model
Profile DuplicateProfile(Profile sourceProfile);
ActionMap ActionMap { get; };
Windows.Foundation.Collections.IVectorView<ExtensionPackage> Extensions { get; };
IVectorView<SettingsLoadWarnings> Warnings { get; };
Windows.Foundation.IReference<SettingsLoadErrors> GetLoadingError { get; };
@@ -56,4 +67,35 @@ namespace Microsoft.Terminal.Settings.Model
void ExpandCommands();
}
[default_interface] runtimeclass FragmentProfileEntry
{
Guid ProfileGuid { get; };
String Json { get; };
}
[default_interface] runtimeclass FragmentColorSchemeEntry
{
String ColorSchemeName { get; };
String Json { get; };
}
[default_interface] runtimeclass FragmentSettings
{
String Source { get; };
String Json { get; };
String Filename { get; };
IVectorView<FragmentProfileEntry> ModifiedProfilesView { get; };
IVectorView<FragmentProfileEntry> NewProfilesView { get; };
IVectorView<FragmentColorSchemeEntry> ColorSchemesView { get; };
}
[default_interface] runtimeclass ExtensionPackage
{
String Source { get; };
String DisplayName { get; };
String Icon { get; };
FragmentScope Scope { get; };
IVectorView<FragmentSettings> FragmentsView { get; };
}
}

View File

@@ -19,6 +19,7 @@
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
#include "SshHostGenerator.h"
#endif
#include "PowershellInstallationProfileGenerator.h"
#include "ApplicationState.h"
#include "DefaultTerminal.h"
@@ -124,6 +125,20 @@ SettingsLoader SettingsLoader::Default(const std::string_view& userJSON, const s
return loader;
}
std::vector<Model::ExtensionPackage> SettingsLoader::LoadExtensionPackages()
{
SettingsLoader loader{};
loader.GenerateExtensionPackagesFromProfileGenerators();
loader.FindFragmentsAndMergeIntoUserSettings(true);
std::vector<Model::ExtensionPackage> extensionPackages;
for (auto [_, extPkg] : loader.extensionPackageMap)
{
extensionPackages.emplace_back(std::move(*extPkg));
}
return extensionPackages;
}
// The SettingsLoader class is an internal implementation detail of CascadiaSettings.
// Member methods aren't safe against misuse and you need to ensure to call them in a specific order.
// See CascadiaSettings::LoadAll() for a specific usage example.
@@ -174,19 +189,159 @@ SettingsLoader::SettingsLoader(const std::string_view& userJSON, const std::stri
_userProfileCount = userSettings.profiles.size();
}
// This method is used to generate the JSON writer used for writing json in a styled format.
// We use it a few times throughout the loader, so we lazy load it and cache it here.
Json::StreamWriterBuilder SettingsLoader::_getJsonStyledWriter()
{
static bool jsonWriterInitialized = false;
static Json::StreamWriterBuilder styledWriter;
if (!jsonWriterInitialized)
{
styledWriter["indentation"] = " ";
styledWriter["commentStyle"] = "All";
styledWriter.settings_["enableYAMLCompatibility"] = true; // suppress spaces around colons
styledWriter.settings_["precision"] = 6; // prevent values like 1.1000000000000001
jsonWriterInitialized = true;
}
return styledWriter;
}
// Generate dynamic profiles and add them to the list of "inbox" profiles
// (meaning profiles specified by the application rather by the user).
void SettingsLoader::GenerateProfiles()
{
_executeGenerator(PowershellCoreProfileGenerator{});
_executeGenerator(WslDistroGenerator{});
_executeGenerator(AzureCloudShellGenerator{});
_executeGenerator(VisualStudioGenerator{});
auto generateProfiles = [&](IDynamicProfileGenerator& generator) {
if (!_ignoredNamespaces.contains(generator.GetNamespace()))
{
_executeGenerator(generator, inboxSettings.profiles);
}
};
{
PowershellCoreProfileGenerator powerShellGenerator{};
generateProfiles(powerShellGenerator);
if (Feature_PowerShellInstallerProfileGenerator::IsEnabled())
{
if (!powerShellGenerator.GetPowerShellInstances().empty())
{
// If PowerShell is installed, mark the installer profile for deletion
const winrt::guid profileGuid{ L"{965a10f2-b0f2-55dc-a3c2-2ddbf639bf89}" };
for (const auto& profile : userSettings.profiles)
{
if (profile->Guid() == profileGuid)
{
profile->Deleted(true);
break;
}
}
}
else
{
// Only generate the installer stub profile if PowerShell isn't installed.
PowershellInstallationProfileGenerator pwshInstallationGenerator{};
generateProfiles(pwshInstallationGenerator);
}
}
}
WslDistroGenerator wslGenerator{};
generateProfiles(wslGenerator);
AzureCloudShellGenerator acsGenerator{};
generateProfiles(acsGenerator);
VisualStudioGenerator vsGenerator{};
generateProfiles(vsGenerator);
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
_executeGenerator(SshHostGenerator{});
SshHostGenerator sshGenerator{};
generateProfiles(sshGenerator);
#endif
}
// Generate ExtensionPackage objects from the profile generators.
void SettingsLoader::GenerateExtensionPackagesFromProfileGenerators()
{
auto generateExtensionPackages = [&](IDynamicProfileGenerator& generator) {
std::vector<winrt::com_ptr<implementation::Profile>> profilesList;
_executeGenerator(generator, profilesList);
// These are needed for the FragmentSettings object
std::vector<Model::FragmentProfileEntry> profileEntries;
Json::Value profilesListJson{ Json::ValueType::arrayValue };
for (const auto& profile : profilesList)
{
const auto profileJson = profile->ToJson();
profilesListJson.append(profileJson);
profileEntries.push_back(winrt::make<FragmentProfileEntry>(profile->Guid(), hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), profileJson)) }));
}
// Manually construct the JSON for the FragmentSettings object
Json::Value json{ Json::ValueType::objectValue };
json[JsonKey(ProfilesKey)] = profilesListJson;
auto generatorExtension = winrt::make_self<FragmentSettings>(hstring{ generator.GetNamespace() }, hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), json)) }, hstring{ L"settings.json" });
generatorExtension->NewProfiles(winrt::single_threaded_vector<Model::FragmentProfileEntry>(std::move(profileEntries)));
auto extPkg = _registerFragment(std::move(*generatorExtension), FragmentScope::Machine);
extPkg->DisplayName(hstring{ generator.GetDisplayName() });
extPkg->Icon(hstring{ generator.GetIcon() });
};
PowershellCoreProfileGenerator powerShellGenerator{};
generateExtensionPackages(powerShellGenerator);
if (Feature_PowerShellInstallerProfileGenerator::IsEnabled())
{
PowershellInstallationProfileGenerator pwshInstallationGenerator{};
generateExtensionPackages(pwshInstallationGenerator);
_patchInstallPowerShellProfile();
}
WslDistroGenerator wslGenerator{};
generateExtensionPackages(wslGenerator);
AzureCloudShellGenerator acsGenerator{};
generateExtensionPackages(acsGenerator);
VisualStudioGenerator vsGenerator{};
generateExtensionPackages(vsGenerator);
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
SshHostGenerator sshGenerator{};
generateExtensionPackages(sshGenerator);
#endif
}
// Retrieve the "Install Latest PowerShell" profile and add a comment to the JSON to indicate it's conditionally applied
void SettingsLoader::_patchInstallPowerShellProfile()
{
const hstring pwshInstallerNamespace{ PowershellInstallationProfileGenerator::Namespace };
if (extensionPackageMap.contains(pwshInstallerNamespace))
{
if (const auto& fragExtList = extensionPackageMap[pwshInstallerNamespace]->Fragments(); fragExtList.Size() > 0)
{
auto fragExt = get_self<FragmentSettings>(fragExtList.GetAt(0));
// We want the comment to be the first thing in the object,
// "closeOnExit" is the first property, so target that.
auto fragExtJson = _parseJSON(til::u16u8(fragExt->Json()));
fragExtJson[JsonKey(ProfilesKey)][0]["closeOnExit"].setComment(til::u16u8(fmt::format(FMT_COMPILE(L"// {}"), RS_(L"PowerShellInstallationProfileJsonComment"))), Json::CommentPlacement::commentBefore);
fragExt->Json(hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), fragExtJson)) });
if (const auto& profileEntryList = fragExt->NewProfilesView(); profileEntryList.Size() > 0)
{
auto profileEntry = get_self<FragmentProfileEntry>(profileEntryList.GetAt(0));
// We want the comment to be the first thing in the object,
// "closeOnExit" is the first property, so target that.
auto profileJson = _parseJSON(til::u16u8(profileEntry->Json()));
profileJson["closeOnExit"].setComment(til::u16u8(fmt::format(FMT_COMPILE(L"// {}"), RS_(L"PowerShellInstallationProfileJsonComment"))), Json::CommentPlacement::commentBefore);
profileEntry->Json(hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), profileJson)) });
}
}
}
}
// A new settings.json gets a special treatment:
// 1. The default profile is a PowerShell 7+ one, if one was generated,
// and falls back to the standard PowerShell 5 profile otherwise.
@@ -242,21 +397,27 @@ void SettingsLoader::MergeInboxIntoUserSettings()
// merge them. Unfortunately however the "updates" key in fragment profiles make this impossible:
// The targeted profile might be one that got created as part of SettingsLoader::MergeInboxIntoUserSettings.
// Additionally the GUID in "updates" will conflict with existing GUIDs in .inboxSettings.
void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
void SettingsLoader::FindFragmentsAndMergeIntoUserSettings(bool generateExtensionPackages)
{
ParsedSettings fragmentSettings;
const auto parseAndLayerFragmentFiles = [&](const std::filesystem::path& path, const winrt::hstring& source) {
const auto parseAndLayerFragmentFiles = [&](const std::filesystem::path& path, const winrt::hstring& source, FragmentScope scope) {
for (const auto& fragmentExt : std::filesystem::directory_iterator{ path })
{
if (fragmentExt.path().extension() == jsonExtension)
const auto fragExtPath = fragmentExt.path();
if (fragExtPath.extension() == jsonExtension)
{
try
{
const auto content = til::io::read_file_as_utf8_string_if_exists(fragmentExt.path());
const auto content = til::io::read_file_as_utf8_string_if_exists(fragExtPath);
if (!content.empty())
{
_parseFragment(source, content, fragmentSettings);
_parseFragment(source,
content,
fragmentSettings,
generateExtensionPackages ?
static_cast<std::optional<ParseFragmentMetadata>>(ParseFragmentMetadata{ fragExtPath.filename().wstring(), scope }) :
std::nullopt);
}
}
CATCH_LOG();
@@ -278,9 +439,11 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
const auto filename = fragmentExtFolder.path().filename();
const auto& source = filename.native();
if (!_ignoredNamespaces.contains(std::wstring_view{ source }) && fragmentExtFolder.is_directory())
if (fragmentExtFolder.is_directory())
{
parseAndLayerFragmentFiles(fragmentExtFolder.path(), winrt::hstring{ source });
parseAndLayerFragmentFiles(fragmentExtFolder.path(),
winrt::hstring{ source },
rfid == FOLDERID_LocalAppData ? FragmentScope::User : FragmentScope::Machine); // scope
}
}
}
@@ -312,11 +475,8 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
for (const auto& ext : extensions)
{
const auto packageName = ext.Package().Id().FamilyName();
if (_ignoredNamespaces.contains(std::wstring_view{ packageName }))
{
continue;
}
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());
@@ -334,7 +494,18 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
if (std::filesystem::is_directory(path))
{
parseAndLayerFragmentFiles(path, packageName);
// MSIX does not support machine-wide scope
// See https://github.com/microsoft/winget-cli/discussions/1983
parseAndLayerFragmentFiles(path,
packageName,
FragmentScope::User);
if (generateExtensionPackages)
{
auto extPkg = extensionPackageMap[packageName];
extPkg->Icon(package.Logo().AbsoluteUri());
extPkg->DisplayName(package.DisplayName());
}
}
}
}
@@ -345,7 +516,7 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
void SettingsLoader::MergeFragmentIntoUserSettings(const winrt::hstring& source, const std::string_view& content)
{
ParsedSettings fragmentSettings;
_parseFragment(source, content, fragmentSettings);
_parseFragment(source, content, fragmentSettings, std::nullopt);
}
// Call this method before passing SettingsLoader to the CascadiaSettings constructor.
@@ -724,15 +895,23 @@ void SettingsLoader::_parse(const OriginTag origin, const winrt::hstring& source
// Just like _parse, but is to be used for fragment files, which don't support anything but color
// schemes and profiles. Additionally this function supports profiles which specify an "updates" key.
void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings)
// - fragmentMeta: If set, construct and register FragmentSettings objects. Provides metadata necessary for doing so.
// Otherwise, completely skip over that extra work and apply parsed settings to the user settings, if allowed by disabledProfileSources ("_ignoredNamespaces").
void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings, const std::optional<ParseFragmentMetadata>& fragmentMeta)
{
auto json = _parseJson(content);
const bool buildFragmentSettings = fragmentMeta.has_value();
const bool applyToUserSettings = !buildFragmentSettings && !_ignoredNamespaces.contains(std::wstring_view{ source });
winrt::com_ptr<implementation::FragmentSettings> fragmentSettings = buildFragmentSettings ?
winrt::make_self<FragmentSettings>(source, hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), json.root)) }, hstring{ fragmentMeta->jsonFilename }) :
nullptr;
settings.clear();
// Load GlobalAppSettings and ColorSchemes
{
settings.globals = winrt::make_self<GlobalAppSettings>();
std::vector<Model::FragmentColorSchemeEntry> fragmentColorSchemes;
for (const auto& schemeJson : json.colorSchemes)
{
try
@@ -740,72 +919,111 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str
if (const auto scheme = ColorScheme::FromJson(schemeJson))
{
scheme->Origin(OriginTag::Fragment);
// Don't add the color scheme to the Fragment's GlobalSettings; that will
// cause layering issues later. Add them to a staging area for later processing.
// (search for STAGED COLORS to find the next step)
settings.colorSchemes.emplace(scheme->Name(), std::move(scheme));
if (buildFragmentSettings)
{
fragmentColorSchemes.emplace_back(winrt::make<FragmentColorSchemeEntry>(scheme->Name(), hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), schemeJson)) }));
}
else if (applyToUserSettings)
{
// Don't add the color scheme to the Fragment's GlobalSettings; that will
// cause layering issues later. Add them to a staging area for later processing.
// (search for STAGED COLORS to find the next step)
settings.colorSchemes.emplace(scheme->Name(), std::move(scheme));
}
}
}
CATCH_LOG()
}
// Parse out actions from the fragment. Manually opt-out of keybinding
// parsing - fragments shouldn't be allowed to bind actions to keys
// directly. We may want to revisit circa GH#2205
settings.globals->LayerActionsFrom(json.root, OriginTag::Fragment, false);
if (buildFragmentSettings)
{
fragmentSettings->ColorSchemes(fragmentColorSchemes.empty() ? nullptr : single_threaded_vector<Model::FragmentColorSchemeEntry>(std::move(fragmentColorSchemes)));
}
else if (applyToUserSettings)
{
// Parse out actions from the fragment. Manually opt-out of keybinding
// parsing - fragments shouldn't be allowed to bind actions to keys
// directly. We may want to revisit circa GH#2205
settings.globals = winrt::make_self<GlobalAppSettings>();
settings.globals->LayerActionsFrom(json.root, OriginTag::Fragment, false);
}
}
// Load new and modified profiles
{
const auto size = json.profilesList.size();
settings.profiles.reserve(size);
settings.profilesByGuid.reserve(size);
if (applyToUserSettings)
{
const auto size = json.profilesList.size();
settings.profiles.reserve(size);
settings.profilesByGuid.reserve(size);
}
std::vector<Model::FragmentProfileEntry> newProfiles;
std::vector<Model::FragmentProfileEntry> modifiedProfiles;
for (const auto& profileJson : json.profilesList)
{
try
{
auto profile = _parseProfile(OriginTag::Fragment, source, profileJson);
// GH#9962: Discard Guid-less, Name-less profiles, but...
// allow ones with an Updates field, as those are special for fragments.
// We need to make sure to only call Guid() if HasGuid() is true,
// as Guid() will dynamically generate a return value otherwise.
auto profile = _parseProfile(OriginTag::Fragment, source, profileJson);
const auto guid = profile->HasGuid() ? profile->Guid() : profile->Updates();
auto destinationSet = profile->HasGuid() ? &newProfiles : &modifiedProfiles;
if (guid != winrt::guid{})
{
_appendProfile(std::move(profile), guid, settings);
if (buildFragmentSettings)
{
destinationSet->emplace_back(winrt::make<FragmentProfileEntry>(guid, hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), profileJson)) }));
}
else if (applyToUserSettings)
{
_appendProfile(std::move(profile), guid, settings);
}
}
}
CATCH_LOG()
}
if (buildFragmentSettings)
{
fragmentSettings->NewProfiles(newProfiles.empty() ? nullptr : single_threaded_vector<Model::FragmentProfileEntry>(std::move(newProfiles)));
fragmentSettings->ModifiedProfiles(modifiedProfiles.empty() ? nullptr : single_threaded_vector<Model::FragmentProfileEntry>(std::move(modifiedProfiles)));
_registerFragment(std::move(*fragmentSettings), fragmentMeta->scope);
}
}
for (const auto& fragmentProfile : settings.profiles)
// Merge profiles, color schemes, and globals into the user settings (aka inheritance)
if (applyToUserSettings)
{
if (const auto updates = fragmentProfile->Updates(); updates != winrt::guid{})
for (const auto& fragmentProfile : settings.profiles)
{
if (const auto it = userSettings.profilesByGuid.find(updates); it != userSettings.profilesByGuid.end())
if (const auto updates = fragmentProfile->Updates(); updates != winrt::guid{})
{
it->second->AddMostImportantParent(fragmentProfile);
if (const auto it = userSettings.profilesByGuid.find(updates); it != userSettings.profilesByGuid.end())
{
it->second->AddMostImportantParent(fragmentProfile);
}
}
else
{
_addUserProfileParent(fragmentProfile);
}
}
else
// STAGED COLORS are processed here: we merge them into the partially-loaded
// settings directly so that we can resolve conflicts between user-generated
// color schemes and fragment-originated ones.
for (const auto& [_, fragmentColorScheme] : settings.colorSchemes)
{
_addUserProfileParent(fragmentProfile);
_addOrMergeUserColorScheme(fragmentColorScheme);
}
}
// STAGED COLORS are processed here: we merge them into the partially-loaded
// settings directly so that we can resolve conflicts between user-generated
// color schemes and fragment-originated ones.
for (const auto& fragmentColorScheme : settings.colorSchemes)
{
_addOrMergeUserColorScheme(fragmentColorScheme.second);
// Add the parsed fragment globals as a parent of the user's settings.
// Later, in FinalizeInheritance, this will result in the action map from
// the fragments being applied before the user's own settings.
userSettings.globals->AddLeastImportantParent(settings.globals);
}
// Add the parsed fragment globals as a parent of the user's settings.
// Later, in FinalizeInheritance, this will result in the action map from
// the fragments being applied before the user's own settings.
userSettings.globals->AddLeastImportantParent(settings.globals);
}
SettingsLoader::JsonSettings SettingsLoader::_parseJson(const std::string_view& content)
@@ -905,7 +1123,8 @@ void SettingsLoader::_addUserProfileParent(const winrt::com_ptr<implementation::
}
}
void SettingsLoader::_addOrMergeUserColorScheme(const winrt::com_ptr<implementation::ColorScheme>& newScheme)
// returns true if the scheme was successfully added, otherwise false
bool SettingsLoader::_addOrMergeUserColorScheme(const winrt::com_ptr<implementation::ColorScheme>& newScheme)
{
// On entry, all the user color schemes have been loaded. Therefore, any insertions of inbox or fragment schemes
// will fail; we can leverage this to detect when they are equivalent and delete the user's duplicate copies.
@@ -931,36 +1150,33 @@ void SettingsLoader::_addOrMergeUserColorScheme(const winrt::com_ptr<implementat
userSettings.colorSchemeRemappings.emplace(newScheme->Name(), newName);
// And re-add it to the end.
userSettings.colorSchemes.emplace(newName, std::move(existingScheme));
return true;
}
}
return false;
}
return true;
}
// As the name implies it executes a generator.
// Generated profiles are added to .inboxSettings. Used by GenerateProfiles().
void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator)
void SettingsLoader::_executeGenerator(IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList)
{
const auto generatorNamespace = generator.GetNamespace();
if (_ignoredNamespaces.contains(generatorNamespace))
{
return;
}
const auto previousSize = inboxSettings.profiles.size();
const auto previousSize = profilesList.size();
try
{
generator.GenerateProfiles(inboxSettings.profiles);
generator.GenerateProfiles(profilesList);
}
CATCH_LOG_MSG("Dynamic Profile Namespace: \"%.*s\"", gsl::narrow<int>(generatorNamespace.size()), generatorNamespace.data())
// If the generator produced some profiles we're going to give them default attributes.
// By setting the Origin/Source/etc. here, we deduplicate some code and ensure they aren't missing accidentally.
if (inboxSettings.profiles.size() > previousSize)
if (profilesList.size() > previousSize)
{
const winrt::hstring source{ generatorNamespace };
for (const auto& profile : std::span(inboxSettings.profiles).subspan(previousSize))
for (const auto& profile : std::span(profilesList).subspan(previousSize))
{
profile->Origin(OriginTag::Generated);
profile->Source(source);
@@ -968,6 +1184,23 @@ void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator
}
}
winrt::com_ptr<ExtensionPackage> 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<ExtensionPackage>(src, scope);
newExtPkg->Fragments().Append(fragment);
extensionPackageMap[src] = newExtPkg;
return newExtPkg;
}
}
// Method Description:
// - Creates a CascadiaSettings from whatever's saved on disk, or instantiates
// a new one with the default values. If we're running as a packaged app,
@@ -1040,7 +1273,7 @@ try
loader.MergeInboxIntoUserSettings();
// Fragments might reference user profiles created by a generator.
// --> FindFragmentsAndMergeIntoUserSettings must be called after MergeInboxIntoUserSettings.
loader.FindFragmentsAndMergeIntoUserSettings();
loader.FindFragmentsAndMergeIntoUserSettings(false);
loader.FinalizeLayering();
// DisableDeletedProfiles returns true whenever we encountered any new generated/dynamic profiles.
@@ -1491,7 +1724,11 @@ void CascadiaSettings::_resolveNewTabMenuProfiles() const
auto activeProfileCount = gsl::narrow_cast<int>(_activeProfiles.Size());
for (auto profileIndex = 0; profileIndex < activeProfileCount; profileIndex++)
{
remainingProfilesMap.emplace(profileIndex, _activeProfiles.GetAt(profileIndex));
const auto& profile = _activeProfiles.GetAt(profileIndex);
if (!profile.Deleted())
{
remainingProfilesMap.emplace(profileIndex, _activeProfiles.GetAt(profileIndex));
}
}
// We keep track of the "remaining profiles" - those that have not yet been resolved

View File

@@ -28,6 +28,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
Command::Command() = default;
Model::Command Command::NewUserCommand()
{
auto newCmd{ winrt::make_self<Command>() };
newCmd->_Origin = OriginTag::User;
return *newCmd;
}
Model::Command Command::CopyAsUserCommand(Model::Command originalCmd)
{
auto command{ winrt::get_self<Command>(originalCmd) };
auto copy{ command->Copy() };
copy->_Origin = OriginTag::User;
return *copy;
}
com_ptr<Command> Command::Copy() const
{
auto command{ winrt::make_self<Command>() };
@@ -113,6 +128,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return hstring{ _ID };
}
void Command::ID(const hstring& ID) noexcept
{
const auto oldID = _ID;
_ID = ID;
IDChanged.raise(*this, oldID);
}
void Command::GenerateID()
{
if (_ActionAndArgs)
@@ -120,8 +142,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
auto actionAndArgsImpl{ winrt::get_self<implementation::ActionAndArgs>(_ActionAndArgs) };
if (const auto generatedID = actionAndArgsImpl->GenerateID(); !generatedID.empty())
{
_ID = generatedID;
_IDWasGenerated = true;
ID(generatedID);
}
}
}
@@ -131,6 +153,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return _IDWasGenerated;
}
Model::ActionAndArgs Command::ActionAndArgs() const noexcept
{
return _ActionAndArgs;
}
void Command::ActionAndArgs(const Model::ActionAndArgs& value) noexcept
{
_ActionAndArgs = value;
}
void Command::Name(const hstring& value)
{
if (!_name.has_value() || _name.value() != value)

View File

@@ -45,6 +45,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
struct Command : CommandT<Command>
{
Command();
static Model::Command NewUserCommand();
static Model::Command CopyAsUserCommand(Model::Command originalCmd);
com_ptr<Command> Copy() const;
static winrt::com_ptr<Command> FromJson(const Json::Value& json,
@@ -73,9 +75,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void Name(const hstring& name);
hstring ID() const noexcept;
void ID(const hstring& ID) noexcept;
void GenerateID();
bool IDWasGenerated();
// we cannot use the WINRT_PROPERTY macro for ActionAndArgs because the setter has some additional logic regarding the ID
Model::ActionAndArgs ActionAndArgs() const noexcept;
void ActionAndArgs(const Model::ActionAndArgs& value) noexcept;
hstring IconPath() const noexcept;
void IconPath(const hstring& val);
@@ -86,12 +93,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
hstring iconPath);
WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None);
WINRT_PROPERTY(Model::ActionAndArgs, ActionAndArgs);
WINRT_PROPERTY(OriginTag, Origin);
WINRT_PROPERTY(winrt::hstring, Description, L"");
public:
til::typed_event<Model::Command, winrt::hstring> IDChanged;
private:
Json::Value _originalJson;
Model::ActionAndArgs _ActionAndArgs{};
Windows::Foundation::Collections::IMap<winrt::hstring, Model::Command> _subcommands{ nullptr };
std::optional<std::wstring> _name;
std::wstring _ID;

View File

@@ -35,13 +35,17 @@ namespace Microsoft.Terminal.Settings.Model
[default_interface] runtimeclass Command : ISettingsModelObject
{
Command();
static Command NewUserCommand();
static Command CopyAsUserCommand(Command originalCmd);
String Name { get; };
String ID { get; };
String Name;
Boolean HasName();
String ID;
void GenerateID();
String Description { get; };
ActionAndArgs ActionAndArgs { get; };
ActionAndArgs ActionAndArgs;
String IconPath;
@@ -51,5 +55,6 @@ namespace Microsoft.Terminal.Settings.Model
static IVector<Command> ParsePowerShellMenuComplete(String json, Int32 replaceLength);
static IVector<Command> HistoryToCommands(IVector<String> commandHistory, String commandline, Boolean directories, String iconPath);
event Windows.Foundation.TypedEventHandler<Command, String> IDChanged;
}
}

View File

@@ -53,6 +53,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
DEFINE_ENUM_MAP(Microsoft::Terminal::Core::AdjustTextMode, AdjustIndistinguishableColors);
DEFINE_ENUM_MAP(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle);
// Actions
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::ResizeDirection, ResizeDirection);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::FocusDirection, FocusDirection);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::SplitDirection, SplitDirection);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::SplitType, SplitType);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::SettingsTarget, SettingsTarget);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::MoveTabDirection, MoveTabDirection);
DEFINE_ENUM_MAP(Microsoft::Terminal::Control::ScrollToMarkDirection, ScrollToMarkDirection);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::CommandPaletteLaunchMode, CommandPaletteLaunchMode);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::SuggestionsSource, SuggestionsSource);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::FindMatchDirection, FindMatchDirection);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::DesktopBehavior, DesktopBehavior);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::MonitorBehavior, MonitorBehavior);
DEFINE_ENUM_MAP(Microsoft::Terminal::Control::ClearBufferType, ClearBufferType);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::SelectOutputDirection, SelectOutputDirection);
// FontWeight is special because the JsonUtils::ConversionTrait for it
// creates a FontWeight object, but we need to use the uint16_t value.
winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint16_t> EnumMappings::FontWeight()

View File

@@ -49,6 +49,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::IntenseStyle> IntenseTextStyle();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Core::AdjustTextMode> AdjustIndistinguishableColors();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::PathTranslationStyle> PathTranslationStyle();
// Actions
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::ResizeDirection> ResizeDirection();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::FocusDirection> FocusDirection();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::SplitDirection> SplitDirection();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::SplitType> SplitType();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::SettingsTarget> SettingsTarget();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::MoveTabDirection> MoveTabDirection();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::ScrollToMarkDirection> ScrollToMarkDirection();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::CommandPaletteLaunchMode> CommandPaletteLaunchMode();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::SuggestionsSource> SuggestionsSource();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::FindMatchDirection> FindMatchDirection();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::DesktopBehavior> DesktopBehavior();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::MonitorBehavior> MonitorBehavior();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::ClearBufferType> ClearBufferType();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::SelectOutputDirection> SelectOutputDirection();
};
}

View File

@@ -31,5 +31,21 @@ namespace Microsoft.Terminal.Settings.Model
static Windows.Foundation.Collections.IMap<String, UInt16> FontWeight { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.IntenseStyle> IntenseTextStyle { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.PathTranslationStyle> PathTranslationStyle { get; };
// Actions
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.ResizeDirection> ResizeDirection { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.FocusDirection> FocusDirection { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.SplitDirection> SplitDirection { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.SplitType> SplitType { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.SettingsTarget> SettingsTarget { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.MoveTabDirection> MoveTabDirection { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.ScrollToMarkDirection> ScrollToMarkDirection { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.CommandPaletteLaunchMode> CommandPaletteLaunchMode { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.SuggestionsSource> SuggestionsSource { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.FindMatchDirection> FindMatchDirection { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.DesktopBehavior> DesktopBehavior { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.MonitorBehavior> MonitorBehavior { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.ClearBufferType> ClearBufferType { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.SelectOutputDirection> SelectOutputDirection { get; };
}
}

View File

@@ -92,6 +92,14 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
globals->_NewTabMenu->Append(get_self<NewTabMenuEntry>(entry)->Copy());
}
}
if (_DisabledProfileSources)
{
globals->_DisabledProfileSources = winrt::single_threaded_vector<hstring>();
for (const auto& src : *_DisabledProfileSources)
{
globals->_DisabledProfileSources->Append(src);
}
}
for (const auto& parent : _parents)
{

View File

@@ -30,6 +30,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model
public:
virtual ~IDynamicProfileGenerator() = default;
virtual std::wstring_view GetNamespace() const noexcept = 0;
virtual void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const = 0;
virtual std::wstring_view GetDisplayName() const noexcept = 0;
virtual std::wstring_view GetIcon() const noexcept = 0;
virtual void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) = 0;
};
};

View File

@@ -90,6 +90,7 @@
<DependentUpon>KeyChordSerialization.idl</DependentUpon>
</ClInclude>
<ClInclude Include="PowershellCoreProfileGenerator.h" />
<ClInclude Include="PowershellInstallationProfileGenerator.h" />
<ClInclude Include="Profile.h">
<DependentUpon>Profile.idl</DependentUpon>
</ClInclude>
@@ -167,6 +168,7 @@
<DependentUpon>KeyChordSerialization.idl</DependentUpon>
</ClCompile>
<ClCompile Include="PowershellCoreProfileGenerator.cpp" />
<ClCompile Include="PowershellInstallationProfileGenerator.cpp" />
<ClCompile Include="Profile.cpp">
<DependentUpon>Profile.idl</DependentUpon>
</ClCompile>

View File

@@ -15,6 +15,9 @@
<ClCompile Include="PowershellCoreProfileGenerator.cpp">
<Filter>profileGeneration</Filter>
</ClCompile>
<ClCompile Include="PowershellInstallationProfileGenerator.cpp">
<Filter>profileGeneration</Filter>
</ClCompile>
<ClCompile Include="WslDistroGenerator.cpp">
<Filter>profileGeneration</Filter>
</ClCompile>
@@ -57,6 +60,9 @@
<ClInclude Include="PowershellCoreProfileGenerator.h">
<Filter>profileGeneration</Filter>
</ClInclude>
<ClInclude Include="PowershellInstallationProfileGenerator.h">
<Filter>profileGeneration</Filter>
</ClInclude>
<ClInclude Include="WslDistroGenerator.h">
<Filter>profileGeneration</Filter>
</ClInclude>

View File

@@ -15,334 +15,311 @@
#include <winrt/Windows.Management.Deployment.h>
#include <appmodel.h>
#include <shlobj.h>
#include <LibraryResources.h>
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
{
enum PowerShellFlags
{
None = 0,
// These flags are used as a sort key, so they encode some native ordering.
// They are ordered such that the "most important" flags have the largest
// impact on the sort space. For example, since we want Preview to be very polar
// we give it the highest flag value.
// The "ideal" powershell instance has 0 flags (stable, native, Program Files location)
//
// With this ordering, the sort space ends up being (for PowerShell 6)
// (numerically greater values are on the left; this is flipped in the final sort)
//
// <-- Less Valued .................................... More Valued -->
// | All instances of PS 6 | All PS7 |
// | Preview | Stable | ~~~ |
// | Non-Native | Native | Non-Native | Native | ~~~ |
// | Trd | Pack | Trd | Pack | Trd | Pack | Trd | Pack | ~~~ |
// (where Pack is a stand-in for store, scoop, dotnet, though they have their own orders,
// and Trd is a stand-in for "Traditional" (Program Files))
//
// In short, flags with larger magnitudes are pushed further down (therefore valued less)
// distribution method (choose one)
Store = 1 << 0, // distributed via the store
Scoop = 1 << 1, // installed via Scoop
Dotnet = 1 << 2, // installed as a dotnet global tool
Traditional = 1 << 3, // installed in traditional Program Files locations
// native architecture (choose one)
WOWARM = 1 << 4, // non-native (Windows-on-Windows, ARM variety)
WOWx86 = 1 << 5, // non-native (Windows-on-Windows, x86 variety)
// build type (choose one)
Preview = 1 << 6, // preview version
};
DEFINE_ENUM_FLAG_OPERATORS(PowerShellFlags);
struct PowerShellInstance
{
int majorVersion; // 0 = we don't know, sort last.
PowerShellFlags flags;
std::filesystem::path executablePath;
constexpr bool operator<(const PowerShellInstance& second) const
{
if (majorVersion != second.majorVersion)
{
return majorVersion < second.majorVersion;
}
if (flags != second.flags)
{
return flags > second.flags; // flags are inverted because "0" is ideal; see above
}
return executablePath < second.executablePath; // fall back to path sorting
}
// Method Description:
// - Generates a name, based on flags, for a powershell instance.
// Return value:
// - the name
std::wstring Name() const
{
std::wstringstream namestream;
namestream << L"PowerShell";
if (WI_IsFlagSet(flags, PowerShellFlags::Store))
{
if (WI_IsFlagSet(flags, PowerShellFlags::Preview))
{
namestream << L" Preview";
}
namestream << L" (msix)";
}
else if (WI_IsFlagSet(flags, PowerShellFlags::Dotnet))
{
namestream << L" (dotnet global)";
}
else if (WI_IsFlagSet(flags, PowerShellFlags::Scoop))
{
namestream << L" (scoop)";
}
else
{
if (majorVersion < 7)
{
namestream << L" Core";
}
if (majorVersion != 0)
{
namestream << L" " << majorVersion;
}
if (WI_IsFlagSet(flags, PowerShellFlags::Preview))
{
namestream << L" Preview";
}
if (WI_IsFlagSet(flags, PowerShellFlags::WOWx86))
{
namestream << L" (x86)";
}
if (WI_IsFlagSet(flags, PowerShellFlags::WOWARM))
{
namestream << L" (ARM)";
}
}
return namestream.str();
}
};
}
using namespace ::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::Settings::Model;
// Function Description:
// - Finds all powershell instances with the traditional layout under a directory.
// - The "traditional" directory layout requires that pwsh.exe exist in a versioned directory, as in
// ROOT\6\pwsh.exe
// Arguments:
// - directory: the directory under which to search
// - flags: flags to apply to all found instances
// - out: the list into which to accumulate these instances.
static void _accumulateTraditionalLayoutPowerShellInstancesInDirectory(std::wstring_view directory, PowerShellFlags flags, std::vector<PowerShellInstance>& out)
namespace winrt::Microsoft::Terminal::Settings::Model
{
const std::filesystem::path root{ wil::ExpandEnvironmentStringsW<std::wstring>(directory.data()) };
if (std::filesystem::exists(root))
DEFINE_ENUM_FLAG_OPERATORS(PowershellCoreProfileGenerator::PowerShellFlags);
constexpr bool PowershellCoreProfileGenerator::PowerShellInstance::operator<(const PowerShellInstance& second) const
{
for (const auto& versionedDir : std::filesystem::directory_iterator(root))
if (majorVersion != second.majorVersion)
{
const auto versionedPath = versionedDir.path();
const auto executable = versionedPath / PWSH_EXE;
if (std::filesystem::exists(executable))
return majorVersion < second.majorVersion;
}
if (flags != second.flags)
{
return flags > second.flags; // flags are inverted because "0" is ideal; see above
}
return executablePath < second.executablePath; // fall back to path sorting
}
// Method Description:
// - Generates a name, based on flags, for a powershell instance.
// Return value:
// - the name
std::wstring PowershellCoreProfileGenerator::PowerShellInstance::Name() const
{
std::wstringstream namestream;
namestream << L"PowerShell";
if (WI_IsFlagSet(flags, PowerShellFlags::Store))
{
if (WI_IsFlagSet(flags, PowerShellFlags::Preview))
{
const auto preview = versionedPath.filename().native().find(L"-preview") != std::wstring::npos;
const auto previewFlag = preview ? PowerShellFlags::Preview : PowerShellFlags::None;
out.emplace_back(PowerShellInstance{ std::stoi(versionedPath.filename()),
PowerShellFlags::Traditional | flags | previewFlag,
executable });
namestream << L" Preview";
}
namestream << L" (msix)";
}
else if (WI_IsFlagSet(flags, PowerShellFlags::Dotnet))
{
namestream << L" (dotnet global)";
}
else if (WI_IsFlagSet(flags, PowerShellFlags::Scoop))
{
namestream << L" (scoop)";
}
else
{
if (majorVersion < 7)
{
namestream << L" Core";
}
if (majorVersion != 0)
{
namestream << L" " << majorVersion;
}
if (WI_IsFlagSet(flags, PowerShellFlags::Preview))
{
namestream << L" Preview";
}
if (WI_IsFlagSet(flags, PowerShellFlags::WOWx86))
{
namestream << L" (x86)";
}
if (WI_IsFlagSet(flags, PowerShellFlags::WOWARM))
{
namestream << L" (ARM)";
}
}
return namestream.str();
}
// Function Description:
// - Finds all powershell instances with the traditional layout under a directory.
// - The "traditional" directory layout requires that pwsh.exe exist in a versioned directory, as in
// ROOT\6\pwsh.exe
// Arguments:
// - directory: the directory under which to search
// - flags: flags to apply to all found instances
// - out: the list into which to accumulate these instances.
static void _accumulateTraditionalLayoutPowerShellInstancesInDirectory(std::wstring_view directory, PowershellCoreProfileGenerator::PowerShellFlags flags, std::vector<PowershellCoreProfileGenerator::PowerShellInstance>& out)
{
const std::filesystem::path root{ wil::ExpandEnvironmentStringsW<std::wstring>(directory.data()) };
if (std::filesystem::exists(root))
{
for (const auto& versionedDir : std::filesystem::directory_iterator(root))
{
const auto versionedPath = versionedDir.path();
const auto executable = versionedPath / PWSH_EXE;
if (std::filesystem::exists(executable))
{
const auto preview = versionedPath.filename().native().find(L"-preview") != std::wstring::npos;
const auto previewFlag = preview ? PowershellCoreProfileGenerator::PowerShellFlags::Preview : PowershellCoreProfileGenerator::PowerShellFlags::None;
out.emplace_back(PowershellCoreProfileGenerator::PowerShellInstance{ std::stoi(versionedPath.filename()),
PowershellCoreProfileGenerator::PowerShellFlags::Traditional | flags | previewFlag,
executable });
}
}
}
}
}
// Function Description:
// - Finds the store package, if one exists, for a given package family name
// Arguments:
// - packageFamilyName: the package family name
// Return Value:
// - a package, or nullptr.
static winrt::Windows::ApplicationModel::Package _getStorePackage(const std::wstring_view packageFamilyName) noexcept
try
{
winrt::Windows::Management::Deployment::PackageManager packageManager;
auto foundPackages = packageManager.FindPackagesForUser(L"", packageFamilyName);
auto iterator = foundPackages.First();
if (!iterator.HasCurrent())
// Function Description:
// - Finds the store package, if one exists, for a given package family name
// Arguments:
// - packageFamilyName: the package family name
// Return Value:
// - a package, or nullptr.
static winrt::Windows::ApplicationModel::Package _getStorePackage(const std::wstring_view packageFamilyName) noexcept
try
{
winrt::Windows::Management::Deployment::PackageManager packageManager;
auto foundPackages = packageManager.FindPackagesForUser(L"", packageFamilyName);
auto iterator = foundPackages.First();
if (!iterator.HasCurrent())
{
return nullptr;
}
return iterator.Current();
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return nullptr;
}
return iterator.Current();
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return nullptr;
}
// Function Description:
// - Finds all powershell instances that have App Execution Aliases in the standard location
// Arguments:
// - out: the list into which to accumulate these instances.
static void _accumulateStorePowerShellInstances(std::vector<PowerShellInstance>& out)
{
wil::unique_cotaskmem_string localAppDataFolder;
if (FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localAppDataFolder)))
// Function Description:
// - Finds all powershell instances that have App Execution Aliases in the standard location
// Arguments:
// - out: the list into which to accumulate these instances.
static void _accumulateStorePowerShellInstances(std::vector<PowershellCoreProfileGenerator::PowerShellInstance>& out)
{
return;
}
std::filesystem::path appExecAliasPath{ localAppDataFolder.get() };
appExecAliasPath /= L"Microsoft";
appExecAliasPath /= L"WindowsApps";
if (std::filesystem::exists(appExecAliasPath))
{
// App execution aliases for preview powershell
const auto previewPath = appExecAliasPath / POWERSHELL_PREVIEW_PFN;
if (std::filesystem::exists(previewPath))
wil::unique_cotaskmem_string localAppDataFolder;
if (FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localAppDataFolder)))
{
const auto previewPackage = _getStorePackage(POWERSHELL_PREVIEW_PFN);
if (previewPackage)
{
out.emplace_back(PowerShellInstance{
gsl::narrow_cast<int>(previewPackage.Id().Version().Major),
PowerShellFlags::Store | PowerShellFlags::Preview,
previewPath / PWSH_EXE });
}
return;
}
// App execution aliases for stable powershell
const auto gaPath = appExecAliasPath / POWERSHELL_PFN;
if (std::filesystem::exists(gaPath))
std::filesystem::path appExecAliasPath{ localAppDataFolder.get() };
appExecAliasPath /= L"Microsoft";
appExecAliasPath /= L"WindowsApps";
if (std::filesystem::exists(appExecAliasPath))
{
const auto gaPackage = _getStorePackage(POWERSHELL_PFN);
if (gaPackage)
// App execution aliases for preview powershell
const auto previewPath = appExecAliasPath / POWERSHELL_PREVIEW_PFN;
if (std::filesystem::exists(previewPath))
{
out.emplace_back(PowerShellInstance{
gaPackage.Id().Version().Major,
PowerShellFlags::Store,
gaPath / PWSH_EXE,
});
const auto previewPackage = _getStorePackage(POWERSHELL_PREVIEW_PFN);
if (previewPackage)
{
out.emplace_back(PowershellCoreProfileGenerator::PowerShellInstance{
gsl::narrow_cast<int>(previewPackage.Id().Version().Major),
PowershellCoreProfileGenerator::PowerShellFlags::Store | PowershellCoreProfileGenerator::PowerShellFlags::Preview,
previewPath / PWSH_EXE });
}
}
// App execution aliases for stable powershell
const auto gaPath = appExecAliasPath / POWERSHELL_PFN;
if (std::filesystem::exists(gaPath))
{
const auto gaPackage = _getStorePackage(POWERSHELL_PFN);
if (gaPackage)
{
out.emplace_back(PowershellCoreProfileGenerator::PowerShellInstance{
gaPackage.Id().Version().Major,
PowershellCoreProfileGenerator::PowerShellFlags::Store,
gaPath / PWSH_EXE,
});
}
}
}
}
}
// Function Description:
// - Finds a powershell instance that's just a pwsh.exe in a folder.
// - This function cannot determine the version number of such a powershell instance.
// Arguments:
// - directory: the directory under which to search
// - flags: flags to apply to all found instances
// - out: the list into which to accumulate these instances.
static void _accumulatePwshExeInDirectory(const std::wstring_view directory, const PowerShellFlags flags, std::vector<PowerShellInstance>& out)
{
const std::filesystem::path root{ wil::ExpandEnvironmentStringsW<std::wstring>(directory.data()) };
const auto pwshPath = root / PWSH_EXE;
if (std::filesystem::exists(pwshPath))
// Function Description:
// - Finds a powershell instance that's just a pwsh.exe in a folder.
// - This function cannot determine the version number of such a powershell instance.
// Arguments:
// - directory: the directory under which to search
// - flags: flags to apply to all found instances
// - out: the list into which to accumulate these instances.
static void _accumulatePwshExeInDirectory(const std::wstring_view directory, const PowershellCoreProfileGenerator::PowerShellFlags flags, std::vector<PowershellCoreProfileGenerator::PowerShellInstance>& out)
{
out.emplace_back(PowerShellInstance{ 0 /* we can't tell */, flags, pwshPath });
const std::filesystem::path root{ wil::ExpandEnvironmentStringsW<std::wstring>(directory.data()) };
const auto pwshPath = root / PWSH_EXE;
if (std::filesystem::exists(pwshPath))
{
out.emplace_back(PowershellCoreProfileGenerator::PowerShellInstance{ 0 /* we can't tell */, flags, pwshPath });
}
}
}
// Function Description:
// - Builds a comprehensive priority-ordered list of powershell instances.
// Return value:
// - a comprehensive priority-ordered list of powershell instances.
static std::vector<PowerShellInstance> _collectPowerShellInstances()
{
std::vector<PowerShellInstance> versions;
// Function Description:
// - Builds a comprehensive priority-ordered list of powershell instances.
// Return value:
// - a comprehensive priority-ordered list of powershell instances.
static std::vector<PowershellCoreProfileGenerator::PowerShellInstance> _collectPowerShellInstances()
{
std::vector<PowershellCoreProfileGenerator::PowerShellInstance> versions;
_accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles%\\PowerShell", PowerShellFlags::None, versions);
_accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles%\\PowerShell", PowershellCoreProfileGenerator::PowerShellFlags::None, versions);
#if defined(_M_AMD64) || defined(_M_ARM64) // No point in looking for WOW if we're not somewhere it exists
_accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles(x86)%\\PowerShell", PowerShellFlags::WOWx86, versions);
_accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles(x86)%\\PowerShell", PowershellCoreProfileGenerator::PowerShellFlags::WOWx86, versions);
#endif
#if defined(_M_ARM64) // no point in looking for WOA if we're not on ARM64
_accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles(Arm)%\\PowerShell", PowerShellFlags::WOWARM, versions);
_accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles(Arm)%\\PowerShell", PowershellCoreProfileGenerator::PowerShellFlags::WOWARM, versions);
#endif
_accumulateStorePowerShellInstances(versions);
_accumulateStorePowerShellInstances(versions);
_accumulatePwshExeInDirectory(L"%USERPROFILE%\\.dotnet\\tools", PowerShellFlags::Dotnet, versions);
_accumulatePwshExeInDirectory(L"%USERPROFILE%\\scoop\\shims", PowerShellFlags::Scoop, versions);
_accumulatePwshExeInDirectory(L"%USERPROFILE%\\.dotnet\\tools", PowershellCoreProfileGenerator::PowerShellFlags::Dotnet, versions);
_accumulatePwshExeInDirectory(L"%USERPROFILE%\\scoop\\shims", PowershellCoreProfileGenerator::PowerShellFlags::Scoop, versions);
std::sort(versions.rbegin(), versions.rend()); // sort in reverse (best first)
std::sort(versions.rbegin(), versions.rend()); // sort in reverse (best first)
return versions;
}
return versions;
}
// Legacy GUIDs:
// - PowerShell Core 574e775e-4f2a-5b96-ac1e-a2962a402336
static constexpr winrt::guid PowershellCoreGuid{ 0x574e775e, 0x4f2a, 0x5b96, { 0xac, 0x1e, 0xa2, 0x96, 0x2a, 0x40, 0x23, 0x36 } };
// Legacy GUIDs:
// - PowerShell Core 574e775e-4f2a-5b96-ac1e-a2962a402336
static constexpr winrt::guid PowershellCoreGuid{ 0x574e775e, 0x4f2a, 0x5b96, { 0xac, 0x1e, 0xa2, 0x96, 0x2a, 0x40, 0x23, 0x36 } };
std::wstring_view PowershellCoreProfileGenerator::GetNamespace() const noexcept
{
return PowershellCoreGeneratorNamespace;
}
// Method Description:
// - Checks if pwsh is installed, and if it is, creates a profile to launch it.
// Arguments:
// - <none>
// Return Value:
// - a vector with the PowerShell Core profile, if available.
void PowershellCoreProfileGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
{
const auto psInstances = _collectPowerShellInstances();
auto first = true;
for (const auto& psI : psInstances)
std::wstring_view PowershellCoreProfileGenerator::GetNamespace() const noexcept
{
const auto name = psI.Name();
auto profile{ CreateDynamicProfile(name) };
return PowershellCoreGeneratorNamespace;
}
const auto& unquotedCommandline = psI.executablePath.native();
std::wstring quotedCommandline;
quotedCommandline.reserve(unquotedCommandline.size() + 2);
quotedCommandline.push_back(L'"');
quotedCommandline.append(unquotedCommandline);
quotedCommandline.push_back(L'"');
profile->Commandline(winrt::hstring{ quotedCommandline });
std::wstring_view PowershellCoreProfileGenerator::GetDisplayName() const noexcept
{
return RS_(L"PowershellCoreProfileGeneratorDisplayName");
}
profile->StartingDirectory(winrt::hstring{ DEFAULT_STARTING_DIRECTORY });
profile->DefaultAppearance().DarkColorSchemeName(L"Campbell");
profile->DefaultAppearance().LightColorSchemeName(L"Campbell");
profile->Icon(winrt::hstring{ WI_IsFlagSet(psI.flags, PowerShellFlags::Preview) ? POWERSHELL_PREVIEW_ICON : POWERSHELL_ICON });
std::wstring_view PowershellCoreProfileGenerator::GetIcon() const noexcept
{
return GENERATOR_POWERSHELL_ICON;
}
if (first)
// Method Description:
// - Checks if pwsh is installed, and if it is, creates a profile to launch it.
// Arguments:
// - <none>
// Return Value:
// - a vector with the PowerShell Core profile, if available.
void PowershellCoreProfileGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
{
GetPowerShellInstances();
auto first = true;
for (const auto& psI : _powerShellInstances)
{
// Give the first ("algorithmically best") profile the official, and original, "PowerShell Core" GUID.
// This will turn the anchored default profile into "PowerShell Core Latest for Native Architecture through Store"
// (or the closest approximation thereof). It may choose a preview instance as the "best" if it is a higher version.
profile->Guid(PowershellCoreGuid);
profile->Name(winrt::hstring{ POWERSHELL_PREFERRED_PROFILE_NAME });
const auto name = psI.Name();
auto profile{ CreateDynamicProfile(name) };
first = false;
const auto& unquotedCommandline = psI.executablePath.native();
std::wstring quotedCommandline;
quotedCommandline.reserve(unquotedCommandline.size() + 2);
quotedCommandline.push_back(L'"');
quotedCommandline.append(unquotedCommandline);
quotedCommandline.push_back(L'"');
profile->Commandline(winrt::hstring{ quotedCommandline });
profile->StartingDirectory(winrt::hstring{ DEFAULT_STARTING_DIRECTORY });
profile->DefaultAppearance().DarkColorSchemeName(L"Campbell");
profile->DefaultAppearance().LightColorSchemeName(L"Campbell");
profile->Icon(winrt::hstring{ WI_IsFlagSet(psI.flags, PowerShellFlags::Preview) ? POWERSHELL_PREVIEW_ICON : POWERSHELL_ICON });
if (first)
{
// Give the first ("algorithmically best") profile the official, and original, "PowerShell Core" GUID.
// This will turn the anchored default profile into "PowerShell Core Latest for Native Architecture through Store"
// (or the closest approximation thereof). It may choose a preview instance as the "best" if it is a higher version.
profile->Guid(PowershellCoreGuid);
profile->Name(winrt::hstring{ POWERSHELL_PREFERRED_PROFILE_NAME });
first = false;
}
profiles.emplace_back(std::move(profile));
}
}
profiles.emplace_back(std::move(profile));
std::vector<PowershellCoreProfileGenerator::PowerShellInstance> PowershellCoreProfileGenerator::GetPowerShellInstances() noexcept
{
if (_powerShellInstances.empty())
{
_powerShellInstances = _collectPowerShellInstances();
}
return _powerShellInstances;
}
// Function Description:
// - Returns the thing it's named for.
// Return value:
// - the thing it says in the name
const std::wstring_view PowershellCoreProfileGenerator::GetPreferredPowershellProfileName()
{
return POWERSHELL_PREFERRED_PROFILE_NAME;
}
}
// Function Description:
// - Returns the thing it's named for.
// Return value:
// - the thing it says in the name
const std::wstring_view PowershellCoreProfileGenerator::GetPreferredPowershellProfileName()
{
return POWERSHELL_PREFERRED_PROFILE_NAME;
}

View File

@@ -25,7 +25,60 @@ namespace winrt::Microsoft::Terminal::Settings::Model
public:
static const std::wstring_view GetPreferredPowershellProfileName();
enum PowerShellFlags
{
None = 0,
// These flags are used as a sort key, so they encode some native ordering.
// They are ordered such that the "most important" flags have the largest
// impact on the sort space. For example, since we want Preview to be very polar
// we give it the highest flag value.
// The "ideal" powershell instance has 0 flags (stable, native, Program Files location)
//
// With this ordering, the sort space ends up being (for PowerShell 6)
// (numerically greater values are on the left; this is flipped in the final sort)
//
// <-- Less Valued .................................... More Valued -->
// | All instances of PS 6 | All PS7 |
// | Preview | Stable | ~~~ |
// | Non-Native | Native | Non-Native | Native | ~~~ |
// | Trd | Pack | Trd | Pack | Trd | Pack | Trd | Pack | ~~~ |
// (where Pack is a stand-in for store, scoop, dotnet, though they have their own orders,
// and Trd is a stand-in for "Traditional" (Program Files))
//
// In short, flags with larger magnitudes are pushed further down (therefore valued less)
// distribution method (choose one)
Store = 1 << 0, // distributed via the store
Scoop = 1 << 1, // installed via Scoop
Dotnet = 1 << 2, // installed as a dotnet global tool
Traditional = 1 << 3, // installed in traditional Program Files locations
// native architecture (choose one)
WOWARM = 1 << 4, // non-native (Windows-on-Windows, ARM variety)
WOWx86 = 1 << 5, // non-native (Windows-on-Windows, x86 variety)
// build type (choose one)
Preview = 1 << 6, // preview version
};
struct PowerShellInstance
{
int majorVersion; // 0 = we don't know, sort last.
PowerShellFlags flags;
std::filesystem::path executablePath;
constexpr bool operator<(const PowerShellInstance& second) const;
std::wstring Name() const;
};
std::wstring_view GetNamespace() const noexcept override;
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
std::wstring_view GetDisplayName() const noexcept override;
std::wstring_view GetIcon() const noexcept override;
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
std::vector<PowerShellInstance> GetPowerShellInstances() noexcept;
private:
std::vector<PowerShellInstance> _powerShellInstances;
};
};

View File

@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "PowershellInstallationProfileGenerator.h"
#include "DynamicProfileUtils.h"
#include <LibraryResources.h>
static constexpr std::wstring_view POWERSHELL_ICON{ L"ms-appx:///ProfileIcons/pwsh.png" };
static constexpr std::wstring_view GENERATOR_POWERSHELL_ICON{ L"ms-appx:///ProfileGeneratorIcons/PowerShell.png" };
namespace winrt::Microsoft::Terminal::Settings::Model
{
std::wstring_view PowershellInstallationProfileGenerator::Namespace{ L"Windows.Terminal.InstallPowerShell" };
std::wstring_view PowershellInstallationProfileGenerator::GetNamespace() const noexcept
{
return Namespace;
}
std::wstring_view PowershellInstallationProfileGenerator::GetDisplayName() const noexcept
{
return RS_(L"PowerShellInstallationProfileGeneratorDisplayName");
}
std::wstring_view PowershellInstallationProfileGenerator::GetIcon() const noexcept
{
return GENERATOR_POWERSHELL_ICON;
}
void PowershellInstallationProfileGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
{
auto profile{ CreateDynamicProfile(RS_(L"PowerShellInstallationProfileName")) };
profile->Commandline(winrt::hstring{ fmt::format(FMT_COMPILE(L"cmd /k winget install --interactive --id Microsoft.PowerShell --source winget & echo. & echo {} & exit"), RS_(L"PowerShellInstallationInstallerGuidance")) });
profile->Icon(winrt::hstring{ POWERSHELL_ICON });
profile->CloseOnExit(CloseOnExitMode::Never);
profiles.emplace_back(std::move(profile));
}
}

View File

@@ -0,0 +1,33 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- PowershellInstallationProfileGenerator
Abstract:
- This is the dynamic profile generator for a PowerShell stub. Checks if pwsh is
installed, and if it is NOT installed, creates a profile that installs the
latest PowerShell.
Author(s):
- Carlos Zamora - March 2025
--*/
#pragma once
#include "IDynamicProfileGenerator.h"
namespace winrt::Microsoft::Terminal::Settings::Model
{
class PowershellInstallationProfileGenerator final : public IDynamicProfileGenerator
{
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<winrt::com_ptr<implementation::Profile>>& profiles) override;
};
};

View File

@@ -740,4 +740,309 @@
<data name="OpenCWDCommandKey" xml:space="preserve">
<value>Open current working directory</value>
</data>
<data name="CopyText" xml:space="preserve">
<value>Copy Text</value>
</data>
<data name="PasteText" xml:space="preserve">
<value>Paste Text</value>
</data>
<data name="OpenNewTabDropdown" xml:space="preserve">
<value>Open New Tab Dropdown</value>
</data>
<data name="DuplicateTab" xml:space="preserve">
<value>Duplicate Tab</value>
</data>
<data name="NewTab" xml:space="preserve">
<value>New Tab</value>
</data>
<data name="CloseWindow" xml:space="preserve">
<value>Close Window</value>
</data>
<data name="CloseTab" xml:space="preserve">
<value>Close Tab</value>
</data>
<data name="ClosePane" xml:space="preserve">
<value>Close Pane</value>
</data>
<data name="NextTab" xml:space="preserve">
<value>Next Tab</value>
</data>
<data name="PrevTab" xml:space="preserve">
<value>Previous Tab</value>
</data>
<data name="SendInput" xml:space="preserve">
<value>Send Input</value>
</data>
<data name="SplitPane" xml:space="preserve">
<value>Split Pane</value>
</data>
<data name="ToggleSplitOrientation" xml:space="preserve">
<value>Toggle Split Orientation</value>
</data>
<data name="TogglePaneZoom" xml:space="preserve">
<value>Toggle Pane Zoom</value>
</data>
<data name="SwitchToTab" xml:space="preserve">
<value>Switch To Tab</value>
</data>
<data name="AdjustFontSize" xml:space="preserve">
<value>Adjust Font Size</value>
</data>
<data name="ResetFontSize" xml:space="preserve">
<value>Reset Font Size</value>
</data>
<data name="ScrollUp" xml:space="preserve">
<value>Scroll Up</value>
</data>
<data name="ScrollDown" xml:space="preserve">
<value>Scroll Down</value>
</data>
<data name="ScrollUpPage" xml:space="preserve">
<value>Scroll Up Page</value>
</data>
<data name="ScrollDownPage" xml:space="preserve">
<value>Scroll Down Page</value>
</data>
<data name="ScrollToTop" xml:space="preserve">
<value>Scroll To Top</value>
</data>
<data name="ScrollToBottom" xml:space="preserve">
<value>Scroll To Bottom</value>
</data>
<data name="ScrollToMark" xml:space="preserve">
<value>Scroll To Mark</value>
</data>
<data name="AddMark" xml:space="preserve">
<value>Add Mark</value>
</data>
<data name="ClearMark" xml:space="preserve">
<value>Clear Mark</value>
</data>
<data name="ClearAllMarks" xml:space="preserve">
<value>Clear All Marks</value>
</data>
<data name="ResizePane" xml:space="preserve">
<value>Resize Pane</value>
</data>
<data name="MoveFocus" xml:space="preserve">
<value>Move Focus</value>
</data>
<data name="MovePane" xml:space="preserve">
<value>Move Pane</value>
</data>
<data name="SwapPane" xml:space="preserve">
<value>Swap Pane</value>
</data>
<data name="Find" xml:space="preserve">
<value>Find</value>
</data>
<data name="ToggleShaderEffects" xml:space="preserve">
<value>Toggle Shader Effects</value>
</data>
<data name="ToggleFocusMode" xml:space="preserve">
<value>Toggle Focus Mode</value>
</data>
<data name="ToggleFullscreen" xml:space="preserve">
<value>Toggle Fullscreen</value>
</data>
<data name="ToggleAlwaysOnTop" xml:space="preserve">
<value>Toggle Always On Top</value>
</data>
<data name="OpenSettings" xml:space="preserve">
<value>Open Settings</value>
</data>
<data name="SetFocusMode" xml:space="preserve">
<value>Set Focus Mode</value>
</data>
<data name="SetFullScreen" xml:space="preserve">
<value>Set Full Screen</value>
</data>
<data name="SetMaximized" xml:space="preserve">
<value>Set Maximized</value>
</data>
<data name="SetColorScheme" xml:space="preserve">
<value>Set Color Scheme</value>
</data>
<data name="SetTabColor" xml:space="preserve">
<value>Set Tab Color</value>
</data>
<data name="OpenTabColorPicker" xml:space="preserve">
<value>Open Tab Color Picker</value>
</data>
<data name="RenameTab" xml:space="preserve">
<value>Rename Tab</value>
</data>
<data name="OpenTabRenamer" xml:space="preserve">
<value>Open Tab Renamer</value>
</data>
<data name="ExecuteCommandline" xml:space="preserve">
<value>Execute Command Line</value>
</data>
<data name="ToggleCommandPalette" xml:space="preserve">
<value>Toggle Command Palette</value>
</data>
<data name="CloseOtherTabs" xml:space="preserve">
<value>Close Other Tabs</value>
</data>
<data name="CloseTabsAfter" xml:space="preserve">
<value>Close Tabs After</value>
</data>
<data name="TabSearch" xml:space="preserve">
<value>Tab Search</value>
</data>
<data name="MoveTab" xml:space="preserve">
<value>Move Tab</value>
</data>
<data name="BreakIntoDebugger" xml:space="preserve">
<value>Break Into Debugger</value>
</data>
<data name="TogglePaneReadOnly" xml:space="preserve">
<value>Toggle Pane Read-Only</value>
</data>
<data name="EnablePaneReadOnly" xml:space="preserve">
<value>Enable Pane Read-Only</value>
</data>
<data name="DisablePaneReadOnly" xml:space="preserve">
<value>Disable Pane Read-Only</value>
</data>
<data name="FindMatch" xml:space="preserve">
<value>Find Match</value>
</data>
<data name="NewWindow" xml:space="preserve">
<value>New Window</value>
</data>
<data name="IdentifyWindow" xml:space="preserve">
<value>Identify Window</value>
</data>
<data name="IdentifyWindows" xml:space="preserve">
<value>Identify Windows</value>
</data>
<data name="RenameWindow" xml:space="preserve">
<value>Rename Window</value>
</data>
<data name="OpenWindowRenamer" xml:space="preserve">
<value>Open Window Renamer</value>
</data>
<data name="DisplayWorkingDirectory" xml:space="preserve">
<value>Display Working Directory</value>
</data>
<data name="SearchForText" xml:space="preserve">
<value>Search For Text</value>
</data>
<data name="GlobalSummon" xml:space="preserve">
<value>Global Summon</value>
</data>
<data name="QuakeMode" xml:space="preserve">
<value>Quake Mode</value>
</data>
<data name="FocusPane" xml:space="preserve">
<value>Focus Pane</value>
</data>
<data name="OpenSystemMenu" xml:space="preserve">
<value>Open System Menu</value>
</data>
<data name="ExportBuffer" xml:space="preserve">
<value>Export Buffer</value>
</data>
<data name="ClearBuffer" xml:space="preserve">
<value>Clear Buffer</value>
</data>
<data name="MultipleActions" xml:space="preserve">
<value>Multiple Actions</value>
</data>
<data name="Quit" xml:space="preserve">
<value>Quit</value>
</data>
<data name="AdjustOpacity" xml:space="preserve">
<value>Adjust Opacity</value>
</data>
<data name="RestoreLastClosed" xml:space="preserve">
<value>Restore Last Closed</value>
</data>
<data name="SelectAll" xml:space="preserve">
<value>Select All</value>
</data>
<data name="SelectCommand" xml:space="preserve">
<value>Select Command</value>
</data>
<data name="SelectOutput" xml:space="preserve">
<value>Select Output</value>
</data>
<data name="MarkMode" xml:space="preserve">
<value>Mark Mode</value>
</data>
<data name="ToggleBlockSelection" xml:space="preserve">
<value>Toggle Block Selection</value>
</data>
<data name="SwitchSelectionEndpoint" xml:space="preserve">
<value>Switch Selection Endpoint</value>
</data>
<data name="Suggestions" xml:space="preserve">
<value>Suggestions</value>
</data>
<data name="ColorSelection" xml:space="preserve">
<value>Color Selection</value>
</data>
<data name="ShowContextMenu" xml:space="preserve">
<value>Show Context Menu</value>
</data>
<data name="ExpandSelectionToWord" xml:space="preserve">
<value>Expand Selection To Word</value>
</data>
<data name="CloseOtherPanes" xml:space="preserve">
<value>Close Other Panes</value>
</data>
<data name="RestartConnection" xml:space="preserve">
<value>Restart Connection</value>
</data>
<data name="ToggleBroadcastInput" xml:space="preserve">
<value>Toggle Broadcast Input</value>
</data>
<data name="OpenScratchpad" xml:space="preserve">
<value>Open Scratchpad</value>
</data>
<data name="OpenAbout" xml:space="preserve">
<value>Open About</value>
</data>
<data name="QuickFix" xml:space="preserve">
<value>Quick Fix</value>
</data>
<data name="OpenCWD" xml:space="preserve">
<value>Open Current Working Directory</value>
</data>
<data name="WslDistroGeneratorDisplayName" xml:space="preserve">
<value>WSL Distro Profile Generator</value>
<comment>The display name of a dynamic profile generator for WSL distros</comment>
</data>
<data name="PowershellCoreProfileGeneratorDisplayName" xml:space="preserve">
<value>PowerShell Profile Generator</value>
<comment>The display name of a dynamic profile generator for PowerShell</comment>
</data>
<data name="PowershellInstallationProfileGeneratorDisplayName" xml:space="preserve">
<value>PowerShell Installation Generator</value>
<comment>The display name of a dynamic profile generator that installs the latest PowerShell</comment>
</data>
<data name="PowershellInstallationProfileName" xml:space="preserve">
<value>Install Latest PowerShell</value>
<comment>The display name of a profile generated by the PowerShellInstallationProfileGenerator. This profile installs the latest PowerShell.</comment>
</data>
<data name="AzureCloudShellGeneratorDisplayName" xml:space="preserve">
<value>Azure Cloud Shell Profile Generator</value>
<comment>The display name of a dynamic profile generator for Azure Cloud Shell</comment>
</data>
<data name="VisualStudioGeneratorDisplayName" xml:space="preserve">
<value>Visual Studio Profile Generator</value>
<comment>The display name of a dynamic profile generator for Visual Studio</comment>
</data>
<data name="SshHostGeneratorDisplayName" xml:space="preserve">
<value>SSH Host Profile Generator</value>
<comment>The display name of a dynamic profile generator for SSH hosts</comment>
</data>
<data name="PowerShellInstallationInstallerGuidance" xml:space="preserve">
<value>Restart Windows Terminal to apply the new profile.</value>
<comment>Guidance displayed by the installer directing the user to restart the app.</comment>
</data>
<data name="PowerShellInstallationProfileJsonComment" xml:space="preserve">
<value>This profile only appears if PowerShell is not installed</value>
</data>
</root>

View File

@@ -7,11 +7,13 @@
#include "../../inc/DefaultSettings.h"
#include "DynamicProfileUtils.h"
#include <LibraryResources.h>
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,13 +134,23 @@ 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:
// - <none>
// Return Value:
// - <A list of SSH host profiles.>
void SshHostGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
void SshHostGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
{
std::wstring sshExePath;
if (_tryFindSshExePath(sshExePath))

View File

@@ -24,7 +24,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model
{
public:
std::wstring_view GetNamespace() const noexcept override;
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
std::wstring_view GetDisplayName() const noexcept override;
std::wstring_view GetIcon() const noexcept override;
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
private:
static const std::wregex _configKeyValueRegex;

View File

@@ -6,17 +6,29 @@
#include "VisualStudioGenerator.h"
#include "VsDevCmdGenerator.h"
#include "VsDevShellGenerator.h"
#include <LibraryResources.h>
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;
}
void VisualStudioGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
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<winrt::com_ptr<implementation::Profile>>& profiles)
{
const auto instances = VsSetupConfiguration::QueryInstances();

View File

@@ -28,7 +28,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model
public:
static std::wstring_view Namespace;
std::wstring_view GetNamespace() const noexcept override;
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
std::wstring_view GetDisplayName() const noexcept override;
std::wstring_view GetIcon() const noexcept override;
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
class IVisualStudioProfileGenerator
{

View File

@@ -8,10 +8,13 @@
#include "../../inc/DefaultSettings.h"
#include "DynamicProfileUtils.h"
#include <LibraryResources.h>
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<implementation::Profile> makeProfile(const std::wstring& distName)
{
const auto WSLDistro{ CreateDynamicProfile(distName) };
@@ -65,7 +78,7 @@ static winrt::com_ptr<implementation::Profile> 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;
}
@@ -226,7 +239,7 @@ static bool getWslNames(const wil::unique_hkey& wslRootKey,
// - <none>
// Return Value:
// - A list of WSL profiles.
void WslDistroGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
void WslDistroGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
{
auto wslRootKey{ openWslRegKey() };
if (wslRootKey)

View File

@@ -24,6 +24,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model
{
public:
std::wstring_view GetNamespace() const noexcept override;
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
std::wstring_view GetDisplayName() const noexcept override;
std::wstring_view GetIcon() const noexcept override;
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
};
};

View File

@@ -77,4 +77,36 @@ namespace winrt::Microsoft::Terminal::UI::implementation
{
return fontWeight.Weight;
}
double Converters::MaxValueFromPaddingString(const winrt::hstring& paddingString)
{
std::wstring buffer;
double maxVal = 0;
auto& errnoRef = errno; // Nonzero cost, pay it once
// Get padding values till we run out of delimiter separated values in the stream
// Non-numeral values detected will default to 0
// std::stod will throw invalid_argument exception if the input is an invalid double value
// std::stod will throw out_of_range exception if the input value is more than DBL_MAX
for (const auto& part : til::split_iterator{ std::wstring_view{ paddingString }, L',' })
{
buffer.assign(part);
// wcstod handles whitespace prefix (which is ignored) & stops the
// scan when first char outside the range of radix is encountered.
// We'll be permissive till the extent that stod function allows us to be by default
// Ex. a value like 100.3#535w2 will be read as 100.3, but ;df25 will fail
errnoRef = 0;
wchar_t* end;
const double val = wcstod(buffer.c_str(), &end);
if (end != buffer.c_str() && errnoRef != ERANGE)
{
maxVal = std::max(maxVal, val);
}
}
return maxVal;
}
}

View File

@@ -28,6 +28,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
static winrt::Windows::UI::Text::FontWeight DoubleToFontWeight(double value);
static winrt::Windows::UI::Xaml::Media::SolidColorBrush ColorToBrush(winrt::Windows::UI::Color color);
static double FontWeightToDouble(winrt::Windows::UI::Text::FontWeight fontWeight);
static double MaxValueFromPaddingString(const winrt::hstring& paddingString);
};
}

View File

@@ -26,5 +26,6 @@ namespace Microsoft.Terminal.UI
static Windows.UI.Text.FontWeight DoubleToFontWeight(Double value);
static Windows.UI.Xaml.Media.SolidColorBrush ColorToBrush(Windows.UI.Color color);
static Double FontWeightToDouble(Windows.UI.Text.FontWeight fontWeight);
static Double MaxValueFromPaddingString(String paddingString);
}
}

View File

@@ -100,6 +100,11 @@
<description>Enables the dynamic profile generator for OpenSSH config files</description>
<id>9031</id>
<stage>AlwaysDisabled</stage>
<alwaysEnabledBrandingTokens>
<brandingToken>Dev</brandingToken>
<brandingToken>Canary</brandingToken>
<brandingToken>Preview</brandingToken>
</alwaysEnabledBrandingTokens>
</feature>
<feature>
@@ -197,4 +202,16 @@
<alwaysDisabledReleaseTokens/>
</feature>
<feature>
<name>Feature_PowerShellInstallerProfileGenerator</name>
<description>Enables the PowerShell Installer Dynamic Profile Generator</description>
<id>18639</id>
<stage>AlwaysDisabled</stage>
<alwaysEnabledBrandingTokens>
<brandingToken>Dev</brandingToken>
<brandingToken>Canary</brandingToken>
<brandingToken>Preview</brandingToken>
</alwaysEnabledBrandingTokens>
</feature>
</featureStaging>