From d9738363b711ce44244671d2551b89d8bbdaa76e Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 14 May 2025 13:33:12 -0700 Subject: [PATCH 01/85] settings model reflection --- .../TerminalSettingsModel/ActionArgs.h | 318 ++++++++++++------ .../TerminalSettingsModel/ActionArgs.idl | 29 +- .../TerminalSettingsModel/ActionArgsMagic.h | 210 ++++++++---- .../TerminalSettingsModel/ActionMap.cpp | 89 ++++- .../TerminalSettingsModel/ActionMap.h | 7 + .../TerminalSettingsModel/ActionMap.idl | 6 +- .../CascadiaSettings.cpp | 105 ++++++ .../TerminalSettingsModel/CascadiaSettings.h | 3 + .../CascadiaSettings.idl | 4 + .../TerminalSettingsModel/Command.cpp | 34 +- src/cascadia/TerminalSettingsModel/Command.h | 12 +- .../TerminalSettingsModel/Command.idl | 11 +- .../TerminalSettingsModel/EnumMappings.cpp | 16 + .../TerminalSettingsModel/EnumMappings.h | 16 + .../TerminalSettingsModel/EnumMappings.idl | 16 + .../Resources/en-US/Resources.resw | 270 +++++++++++++++ 16 files changed, 970 insertions(+), 176 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 8b7849f3aef..16bd15e5234 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -102,189 +102,208 @@ protected: \ // false, if we don't really care if the parameter is required or not. //////////////////////////////////////////////////////////////////////////////// -#define COPY_TEXT_ARGS(X) \ - X(bool, DismissSelection, "dismissSelection", false, true) \ - X(bool, SingleLine, "singleLine", false, false) \ - X(bool, WithControlSequences, "withControlSequences", false, false) \ - X(Windows::Foundation::IReference, CopyFormatting, "copyFormatting", false, nullptr) +#define COPY_TEXT_ARGS(X) \ + X(bool, DismissSelection, "dismissSelection", false, ArgTag::None, true) \ + X(bool, SingleLine, "singleLine", false, ArgTag::None, false) \ + X(bool, WithControlSequences, "withControlSequences", false, ArgTag::None, false) \ + X(Windows::Foundation::IReference, CopyFormatting, "copyFormatting", false, ArgTag::None, nullptr) //////////////////////////////////////////////////////////////////////////////// -#define MOVE_PANE_ARGS(X) \ - X(uint32_t, TabIndex, "index", false, 0) \ - X(winrt::hstring, Window, "window", false, L"") +#define MOVE_PANE_ARGS(X) \ + X(uint32_t, TabIndex, "index", false, ArgTag::None, 0) \ + X(winrt::hstring, Window, "window", false, ArgTag::None, L"") //////////////////////////////////////////////////////////////////////////////// #define SWITCH_TO_TAB_ARGS(X) \ - X(uint32_t, TabIndex, "index", false, 0) + X(uint32_t, TabIndex, "index", false, ArgTag::None, 0) //////////////////////////////////////////////////////////////////////////////// #define RESIZE_PANE_ARGS(X) \ - X(Model::ResizeDirection, ResizeDirection, "direction", args->ResizeDirection() == ResizeDirection::None, Model::ResizeDirection::None) + X(Model::ResizeDirection, ResizeDirection, "direction", args->ResizeDirection() == ResizeDirection::None, ArgTag::None, Model::ResizeDirection::None) //////////////////////////////////////////////////////////////////////////////// #define MOVE_FOCUS_ARGS(X) \ - X(Model::FocusDirection, FocusDirection, "direction", args->FocusDirection() == Model::FocusDirection::None, Model::FocusDirection::None) + X(Model::FocusDirection, FocusDirection, "direction", args->FocusDirection() == Model::FocusDirection::None, ArgTag::None, Model::FocusDirection::None) //////////////////////////////////////////////////////////////////////////////// #define SWAP_PANE_ARGS(X) \ - X(Model::FocusDirection, Direction, "direction", args->Direction() == Model::FocusDirection::None, Model::FocusDirection::None) + X(Model::FocusDirection, Direction, "direction", args->Direction() == Model::FocusDirection::None, ArgTag::None, Model::FocusDirection::None) //////////////////////////////////////////////////////////////////////////////// #define ADJUST_FONT_SIZE_ARGS(X) \ - X(float, Delta, "delta", false, 0) + X(float, Delta, "delta", false, ArgTag::None, 0) //////////////////////////////////////////////////////////////////////////////// #define SEND_INPUT_ARGS(X) \ - X(winrt::hstring, Input, "input", args->Input().empty(), L"") + X(winrt::hstring, Input, "input", args->Input().empty(), ArgTag::None, L"") //////////////////////////////////////////////////////////////////////////////// #define OPEN_SETTINGS_ARGS(X) \ - X(SettingsTarget, Target, "target", false, SettingsTarget::SettingsFile) + X(SettingsTarget, Target, "target", false, ArgTag::None, SettingsTarget::SettingsFile) //////////////////////////////////////////////////////////////////////////////// #define SET_FOCUS_MODE_ARGS(X) \ - X(bool, IsFocusMode, "isFocusMode", false, false) + X(bool, IsFocusMode, "isFocusMode", false, ArgTag::None, false) //////////////////////////////////////////////////////////////////////////////// #define SET_MAXIMIZED_ARGS(X) \ - X(bool, IsMaximized, "isMaximized", false, false) + X(bool, IsMaximized, "isMaximized", false, ArgTag::None, false) //////////////////////////////////////////////////////////////////////////////// #define SET_FULL_SCREEN_ARGS(X) \ - X(bool, IsFullScreen, "isFullScreen", false, false) + X(bool, IsFullScreen, "isFullScreen", false, ArgTag::None, false) //////////////////////////////////////////////////////////////////////////////// #define SET_MAXIMIZED_ARGS(X) \ - X(bool, IsMaximized, "isMaximized", false, false) + X(bool, IsMaximized, "isMaximized", false, ArgTag::None, false) //////////////////////////////////////////////////////////////////////////////// #define SET_COLOR_SCHEME_ARGS(X) \ - X(winrt::hstring, SchemeName, "colorScheme", args->SchemeName().empty(), L"") + X(winrt::hstring, SchemeName, "colorScheme", args->SchemeName().empty(), ArgTag::ColorScheme, L"") //////////////////////////////////////////////////////////////////////////////// #define SET_TAB_COLOR_ARGS(X) \ - X(Windows::Foundation::IReference, TabColor, "color", false, nullptr) + X(Windows::Foundation::IReference, TabColor, "color", false, ArgTag::None, nullptr) //////////////////////////////////////////////////////////////////////////////// #define RENAME_TAB_ARGS(X) \ - X(winrt::hstring, Title, "title", false, L"") + X(winrt::hstring, Title, "title", false, ArgTag::None, L"") //////////////////////////////////////////////////////////////////////////////// #define EXECUTE_COMMANDLINE_ARGS(X) \ - X(winrt::hstring, Commandline, "commandline", args->Commandline().empty(), L"") + X(winrt::hstring, Commandline, "commandline", args->Commandline().empty(), ArgTag::None, L"") //////////////////////////////////////////////////////////////////////////////// #define CLOSE_OTHER_TABS_ARGS(X) \ - X(Windows::Foundation::IReference, Index, "index", false, nullptr) + X(Windows::Foundation::IReference, Index, "index", false, ArgTag::None, nullptr) //////////////////////////////////////////////////////////////////////////////// #define CLOSE_TABS_AFTER_ARGS(X) \ - X(Windows::Foundation::IReference, Index, "index", false, nullptr) + X(Windows::Foundation::IReference, Index, "index", false, ArgTag::None, nullptr) //////////////////////////////////////////////////////////////////////////////// #define CLOSE_TAB_ARGS(X) \ - X(Windows::Foundation::IReference, Index, "index", false, nullptr) + X(Windows::Foundation::IReference, Index, "index", false, ArgTag::None, nullptr) //////////////////////////////////////////////////////////////////////////////// // Interestingly, the order MATTERS here. Window has to be BEFORE Direction, // because otherwise we won't have parsed the Window yet when we validate the // Direction. -#define MOVE_TAB_ARGS(X) \ - X(winrt::hstring, Window, "window", false, L"") \ - X(MoveTabDirection, Direction, "direction", (args->Direction() == MoveTabDirection::None) && (args->Window().empty()), MoveTabDirection::None) +#define MOVE_TAB_ARGS(X) \ + X(winrt::hstring, Window, "window", false, ArgTag::None, L"") \ + X(MoveTabDirection, Direction, "direction", (args->Direction() == MoveTabDirection::None) && (args->Window().empty()), ArgTag::None, MoveTabDirection::None) // Other ideas: // X(uint32_t, TabIndex, "index", false, 0) \ // target? source? //////////////////////////////////////////////////////////////////////////////// #define SCROLL_UP_ARGS(X) \ - X(Windows::Foundation::IReference, RowsToScroll, "rowsToScroll", false, nullptr) + X(Windows::Foundation::IReference, RowsToScroll, "rowsToScroll", false, ArgTag::None, nullptr) //////////////////////////////////////////////////////////////////////////////// #define SCROLL_DOWN_ARGS(X) \ - X(Windows::Foundation::IReference, RowsToScroll, "rowsToScroll", false, nullptr) + X(Windows::Foundation::IReference, RowsToScroll, "rowsToScroll", false, ArgTag::None, nullptr) //////////////////////////////////////////////////////////////////////////////// #define SCROLL_TO_MARK_ARGS(X) \ - X(Microsoft::Terminal::Control::ScrollToMarkDirection, Direction, "direction", false, Microsoft::Terminal::Control::ScrollToMarkDirection::Previous) + X(Microsoft::Terminal::Control::ScrollToMarkDirection, Direction, "direction", false, ArgTag::None, Microsoft::Terminal::Control::ScrollToMarkDirection::Previous) //////////////////////////////////////////////////////////////////////////////// #define ADD_MARK_ARGS(X) \ - X(Windows::Foundation::IReference, Color, "color", false, nullptr) + X(Windows::Foundation::IReference, Color, "color", false, ArgTag::None, nullptr) //////////////////////////////////////////////////////////////////////////////// #define TOGGLE_COMMAND_PALETTE_ARGS(X) \ - X(CommandPaletteLaunchMode, LaunchMode, "launchMode", false, CommandPaletteLaunchMode::Action) + X(CommandPaletteLaunchMode, LaunchMode, "launchMode", false, ArgTag::None, CommandPaletteLaunchMode::Action) //////////////////////////////////////////////////////////////////////////////// -#define SAVE_TASK_ARGS(X) \ - X(winrt::hstring, Name, "name", false, L"") \ - X(winrt::hstring, Commandline, "commandline", args->Commandline().empty(), L"") \ - X(winrt::hstring, KeyChord, "keyChord", false, L"") +#define SAVE_TASK_ARGS(X) \ + X(winrt::hstring, Name, "name", false, ArgTag::None, L"") \ + X(winrt::hstring, Commandline, "commandline", args->Commandline().empty(), ArgTag::None, L"") \ + X(winrt::hstring, KeyChord, "keyChord", false, ArgTag::None, L"") //////////////////////////////////////////////////////////////////////////////// -#define SUGGESTIONS_ARGS(X) \ - X(SuggestionsSource, Source, "source", false, SuggestionsSource::Tasks) \ - X(bool, UseCommandline, "useCommandline", false, false) +#define SUGGESTIONS_ARGS(X) \ + X(SuggestionsSource, Source, "source", false, ArgTag::None, SuggestionsSource::Tasks) \ + X(bool, UseCommandline, "useCommandline", false, ArgTag::None, false) //////////////////////////////////////////////////////////////////////////////// #define FIND_MATCH_ARGS(X) \ - X(FindMatchDirection, Direction, "direction", args->Direction() == FindMatchDirection::None, FindMatchDirection::None) + X(FindMatchDirection, Direction, "direction", args->Direction() == FindMatchDirection::None, ArgTag::None, FindMatchDirection::None) //////////////////////////////////////////////////////////////////////////////// #define PREV_TAB_ARGS(X) \ - X(Windows::Foundation::IReference, SwitcherMode, "tabSwitcherMode", false, nullptr) + X(Windows::Foundation::IReference, SwitcherMode, "tabSwitcherMode", false, ArgTag::None, nullptr) //////////////////////////////////////////////////////////////////////////////// #define NEXT_TAB_ARGS(X) \ - X(Windows::Foundation::IReference, SwitcherMode, "tabSwitcherMode", false, nullptr) + X(Windows::Foundation::IReference, SwitcherMode, "tabSwitcherMode", false, ArgTag::None, nullptr) //////////////////////////////////////////////////////////////////////////////// #define RENAME_WINDOW_ARGS(X) \ - X(winrt::hstring, Name, "name", false, L"") + X(winrt::hstring, Name, "name", false, ArgTag::None, L"") //////////////////////////////////////////////////////////////////////////////// #define SEARCH_FOR_TEXT_ARGS(X) \ - X(winrt::hstring, QueryUrl, "queryUrl", false, L"") + X(winrt::hstring, QueryUrl, "queryUrl", false, ArgTag::None, L"") //////////////////////////////////////////////////////////////////////////////// -#define GLOBAL_SUMMON_ARGS(X) \ - X(winrt::hstring, Name, "name", false, L"") \ - X(Model::DesktopBehavior, Desktop, "desktop", false, Model::DesktopBehavior::ToCurrent) \ - X(Model::MonitorBehavior, Monitor, "monitor", false, Model::MonitorBehavior::ToMouse) \ - X(bool, ToggleVisibility, "toggleVisibility", false, true) \ - X(uint32_t, DropdownDuration, "dropdownDuration", false, 0) +#define GLOBAL_SUMMON_ARGS(X) \ + X(winrt::hstring, Name, "name", false, ArgTag::None, L"") \ + X(Model::DesktopBehavior, Desktop, "desktop", false, ArgTag::None, Model::DesktopBehavior::ToCurrent) \ + X(Model::MonitorBehavior, Monitor, "monitor", false, ArgTag::None, Model::MonitorBehavior::ToMouse) \ + X(bool, ToggleVisibility, "toggleVisibility", false, ArgTag::None, true) \ + X(uint32_t, DropdownDuration, "dropdownDuration", false, ArgTag::None, 0) //////////////////////////////////////////////////////////////////////////////// #define FOCUS_PANE_ARGS(X) \ - X(uint32_t, Id, "id", false, 0u) + X(uint32_t, Id, "id", false, ArgTag::None, 0u) //////////////////////////////////////////////////////////////////////////////// #define EXPORT_BUFFER_ARGS(X) \ - X(winrt::hstring, Path, "path", false, L"") + X(winrt::hstring, Path, "path", false, ArgTag::FilePath, L"") //////////////////////////////////////////////////////////////////////////////// #define CLEAR_BUFFER_ARGS(X) \ - X(winrt::Microsoft::Terminal::Control::ClearBufferType, Clear, "clear", false, winrt::Microsoft::Terminal::Control::ClearBufferType::All) + X(winrt::Microsoft::Terminal::Control::ClearBufferType, Clear, "clear", false, ArgTag::None, winrt::Microsoft::Terminal::Control::ClearBufferType::All) //////////////////////////////////////////////////////////////////////////////// -#define ADJUST_OPACITY_ARGS(X) \ - X(int32_t, Opacity, "opacity", false, 0) \ - X(bool, Relative, "relative", false, true) +#define ADJUST_OPACITY_ARGS(X) \ + X(int32_t, Opacity, "opacity", false, ArgTag::None, 0) \ + X(bool, Relative, "relative", false, ArgTag::None, true) //////////////////////////////////////////////////////////////////////////////// #define SELECT_COMMAND_ARGS(X) \ - X(SelectOutputDirection, Direction, "direction", false, SelectOutputDirection::Previous) + X(SelectOutputDirection, Direction, "direction", false, ArgTag::None, SelectOutputDirection::Previous) //////////////////////////////////////////////////////////////////////////////// #define SELECT_OUTPUT_ARGS(X) \ - X(SelectOutputDirection, Direction, "direction", false, SelectOutputDirection::Previous) + X(SelectOutputDirection, Direction, "direction", false, ArgTag::None, SelectOutputDirection::Previous) //////////////////////////////////////////////////////////////////////////////// -#define COLOR_SELECTION_ARGS(X) \ - X(winrt::Microsoft::Terminal::Control::SelectionColor, Foreground, "foreground", false, nullptr) \ - 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 COLOR_SELECTION_ARGS(X) \ + X(winrt::Microsoft::Terminal::Control::SelectionColor, Foreground, "foreground", false, ArgTag::None, nullptr) \ + X(winrt::Microsoft::Terminal::Control::SelectionColor, Background, "background", false, ArgTag::None, nullptr) \ + X(winrt::Microsoft::Terminal::Core::MatchMode, MatchMode, "matchMode", false, ArgTag::None, winrt::Microsoft::Terminal::Core::MatchMode::None) + +//////////////////////////////////////////////////////////////////////////////// +#define NEW_TERMINAL_ARGS(X) \ + X(winrt::hstring, Commandline, "commandline", false, ArgTag::None, L"") \ + X(winrt::hstring, StartingDirectory, "startingDirectory", false, ArgTag::None, L"") \ + X(winrt::hstring, TabTitle, "tabTitle", false, ArgTag::None, L"") \ + X(Windows::Foundation::IReference, TabColor, "tabColor", false, ArgTag::None, nullptr) \ + X(Windows::Foundation::IReference, ProfileIndex, "index", false, ArgTag::None, nullptr) \ + X(winrt::hstring, Profile, "profile", false, ArgTag::None, L"") \ + X(Windows::Foundation::IReference, SuppressApplicationTitle, "suppressApplicationTitle", false, ArgTag::None, nullptr) \ + X(winrt::hstring, ColorScheme, "colorScheme", args->SchemeName().empty(), ArgTag::ColorScheme, L"") \ + X(Windows::Foundation::IReference, Elevate, "elevate", false, ArgTag::None, nullptr) \ + X(Windows::Foundation::IReference, ReloadEnvironmentVariables, "reloadEnvironmentVariables", false, ArgTag::None, nullptr) + +//////////////////////////////////////////////////////////////////////////////// +#define SPLIT_PANE_ARGS(X) \ + X(Model::SplitDirection, SplitDirection, "split", false, ArgTag::None, SplitDirection::Automatic) \ + X(SplitType, SplitMode, "splitMode", false, ArgTag::None, SplitType::Manual) \ + X(float, SplitSize, "size", false, ArgTag::None, 0.5f) //////////////////////////////////////////////////////////////////////////////// @@ -358,41 +377,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // assumptions made in the macro. struct NewTerminalArgs : public NewTerminalArgsT { - 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, TabColor, nullptr); - ACTION_ARG(Windows::Foundation::IReference, 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, SuppressApplicationTitle, nullptr); - ACTION_ARG(winrt::hstring, ColorScheme); - ACTION_ARG(Windows::Foundation::IReference, Elevate, nullptr); - ACTION_ARG(Windows::Foundation::IReference, 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()->GetArgCount(); + } + Model::ArgDescription GetArgDescriptionAt(uint32_t index) const + { + return _ContentArgs.as()->GetArgDescriptionAt(index); + } + IInspectable GetArgAt(uint32_t index) const + { + return _ContentArgs.as()->GetArgAt(index); + } + void SetArgAt(uint32_t index, IInspectable value) + { + _ContentArgs.as()->SetArgAt(index, value); + } }; struct SplitPaneArgs : public SplitPaneArgsT { - 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 } {}; - - 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); + _SplitMode{ splitMode } { + SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION) + }; - 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(); - 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(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()) + { + return newTermArgs->GetArgCount() + gsl::narrow(_argDescriptions.size()); + } + else + { + return gsl::narrow(_argDescriptions.size()); + } + } + Model::ArgDescription GetArgDescriptionAt(uint32_t index) const + { + const auto additionalArgCount = gsl::narrow(_argDescriptions.size()); + if (index < additionalArgCount) + { + return _argDescriptions.at(index); + } + else + { + return _ContentArgs.as()->GetArgDescriptionAt(index - additionalArgCount); + } + } + IInspectable GetArgAt(uint32_t index) const + { + const auto additionalArgCount = gsl::narrow(_argDescriptions.size()); + if (index < additionalArgCount) + { + uint32_t curIndex{ 0 }; + SPLIT_PANE_ARGS(GET_ARG_BY_INDEX); + } + else + { + return _ContentArgs.as()->GetArgAt(index - additionalArgCount); + } + return nullptr; + } + void SetArgAt(uint32_t index, IInspectable value) + { + const auto additionalArgCount = gsl::narrow(_argDescriptions.size()); + if (index < additionalArgCount) + { + uint32_t curIndex{ 0 }; + SPLIT_PANE_ARGS(SET_ARG_BY_INDEX); + } + else + { + _ContentArgs.as()->SetArgAt(index - additionalArgCount, value); + } + } + + private: + std::vector _argDescriptions; }; struct NewWindowArgs : public NewWindowArgsT @@ -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()->GetArgCount(); + } + Model::ArgDescription GetArgDescriptionAt(uint32_t index) const + { + return _ContentArgs.as()->GetArgDescriptionAt(index); + } + IInspectable GetArgAt(uint32_t index) const + { + return _ContentArgs.as()->GetArgAt(index); + } + void SetArgAt(uint32_t index, IInspectable value) + { + _ContentArgs.as()->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); diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index efed24a36a8..39cd220652a 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -5,12 +5,32 @@ import "Command.idl"; namespace Microsoft.Terminal.Settings.Model { + enum ArgTag + { + None = 0, + FilePath, + ColorScheme + }; + + struct ArgDescription + { + String Name; + String Type; + Boolean Required; + ArgTag Tag; + }; + 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 +188,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 +255,7 @@ namespace Microsoft.Terminal.Settings.Model { SendInputArgs(String input); - String Input { get; }; + String Input; }; [default_interface] runtimeclass SplitPaneArgs : IActionArgs @@ -309,7 +334,7 @@ namespace Microsoft.Terminal.Settings.Model [default_interface] runtimeclass CloseTabArgs : IActionArgs { CloseTabArgs(UInt32 tabIndex); - Windows.Foundation.IReference Index { get; }; + Windows.Foundation.IReference Index; }; [default_interface] runtimeclass MoveTabArgs : IActionArgs diff --git a/src/cascadia/TerminalSettingsModel/ActionArgsMagic.h b/src/cascadia/TerminalSettingsModel/ActionArgsMagic.h index 5ec59c34f43..ab4c2958b9f 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgsMagic.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgsMagic.h @@ -54,24 +54,55 @@ struct InitListPlaceholder // expanded. Pretty critical for tracking down extraneous commas, etc. // Property definitions, and JSON keys -#define DECLARE_ARGS(type, name, jsonKey, required, ...) \ +#define DECLARE_ARGS(type, name, jsonKey, required, tag, ...) \ static constexpr std::string_view name##Key{ jsonKey }; \ ACTION_ARG(type, name, ##__VA_ARGS__); // Parameters to the non-default ctor -#define CTOR_PARAMS(type, name, jsonKey, required, ...) \ +#define CTOR_PARAMS(type, name, jsonKey, required, tag, ...) \ const type &name##Param, // initializers in the ctor -#define CTOR_INIT(type, name, jsonKey, required, ...) \ +#define CTOR_INIT(type, name, jsonKey, required, tag, ...) \ _##name{ name##Param }, +// append this argument's description to the internal vector +#define APPEND_ARG_DESCRIPTION(type, name, jsonKey, required, tag, ...) \ + _argDescriptions.push_back({ L## #name, L## #type, std::wstring_view(L## #required) != L"false", tag }); + // 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, ...) \ +#define EQUALS_ARGS(type, name, jsonKey, required, tag, ...) \ &&(otherAsUs->_##name == _##name) +// getter and setter for each property by index +#define GET_ARG_BY_INDEX(type, name, jsonKey, required, tag, ...) \ + if (index == curIndex++) \ + { \ + if (_##name.has_value()) \ + { \ + return winrt::box_value(_##name.value()); \ + } \ + else \ + { \ + return winrt::box_value(static_cast(__VA_ARGS__)); \ + } \ + } + +#define SET_ARG_BY_INDEX(type, name, jsonKey, required, tag, ...) \ + if (index == curIndex++) \ + { \ + if (value) \ + { \ + _##name = winrt::unbox_value(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,25 +110,25 @@ 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, tag, ...) \ + JsonUtils::GetValueForKey(json, jsonKey, args->_##name); \ + if (required) \ + { \ + return { nullptr, { SettingsLoadWarnings::MissingRequiredParameter } }; \ } // JSON serialization -#define TO_JSON_ARGS(type, name, jsonKey, required, ...) \ +#define TO_JSON_ARGS(type, name, jsonKey, required, tag, ...) \ JsonUtils::SetValueForKey(json, jsonKey, args->_##name); // Copy each property in the Copy() method -#define COPY_ARGS(type, name, jsonKey, required, ...) \ +#define COPY_ARGS(type, name, jsonKey, required, tag, ...) \ copy->_##name = _##name; // hash each property in Hash(). You'll note there's a stray `0` in the // definition of Hash() below, that's to deal with trailing commas (or in this // case, leading.) -#define HASH_ARGS(type, name, jsonKey, required, ...) \ +#define HASH_ARGS(type, name, jsonKey, required, tag, ...) \ h.write(name()); // Use ACTION_ARGS_STRUCT when you've got no other customizing to do. @@ -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(); \ - if (otherAsUs) \ - { \ - return true argsMacro(EQUALS_ARGS); \ - } \ - return false; \ - }; \ - static FromJsonResult FromJson(const Json::Value& json) \ - { \ - auto args = winrt::make_self(); \ - 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(val) }; \ - argsMacro(TO_JSON_ARGS); \ - return json; \ - } \ - IActionArgs Copy() const \ - { \ - auto copy{ winrt::make_self() }; \ - 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 _argDescriptions; \ + \ +public: \ + hstring GenerateName() const; \ + bool Equals(const IActionArgs& other) \ + { \ + auto otherAsUs = other.try_as(); \ + if (otherAsUs) \ + { \ + return true argsMacro(EQUALS_ARGS); \ + } \ + return false; \ + }; \ + static FromJsonResult FromJson(const Json::Value& json) \ + { \ + auto args = winrt::make_self(); \ + 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(val) }; \ + argsMacro(TO_JSON_ARGS); \ + return json; \ + } \ + IActionArgs Copy() const \ + { \ + auto copy{ winrt::make_self() }; \ + 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(_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 _argDescriptions; \ + \ +public: \ + uint32_t GetArgCount() const \ + { \ + return gsl::narrow(_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) \ } diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 577903edc0c..123f2f3e27e 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -380,6 +380,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return _ResolvedKeyToActionMapCache.GetView(); } + IVectorView 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 globalHotkeys; std::unordered_map resolvedKeyToActionMap; + std::vector 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::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(cmd)->Copy()); + const auto copiedCmd = winrt::get_self(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 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 ActionMap::AllKeyBindingsForAction(const winrt::hstring& cmdID) + { + if (!_ResolvedKeyToActionMapCache) + { + _RefreshKeyBindingCaches(); + } + + std::vector 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) { diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index c0a6e4ac46a..bdab52da394 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -56,6 +56,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Windows::Foundation::Collections::IMapView NameMap(); Windows::Foundation::Collections::IMapView GlobalHotkeys(); Windows::Foundation::Collections::IMapView KeyBindings(); + Windows::Foundation::Collections::IVectorView AllCommands(); com_ptr 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 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 ExpandedCommands(); @@ -105,6 +109,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static std::unordered_map _loadLocalSnippets(const std::filesystem::path& currentWorkingDirectory); + void _CommandIDChangedHandler(const Model::Command& senderCmd, const winrt::hstring& oldID); + Windows::Foundation::Collections::IMap _AvailableActionsCache{ nullptr }; Windows::Foundation::Collections::IMap _NameMapCache{ nullptr }; Windows::Foundation::Collections::IMap _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 _ResolvedKeyToActionMapCache{ nullptr }; + Windows::Foundation::Collections::IVector _AllCommandsCache{ nullptr }; til::shared_mutex>> _cwdLocalSnippetsCache{}; diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.idl b/src/cascadia/TerminalSettingsModel/ActionMap.idl index 3c94ac5d92a..6c2eea513b1 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.idl +++ b/src/cascadia/TerminalSettingsModel/ActionMap.idl @@ -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 AllKeyBindingsForAction(String cmdID); Windows.Foundation.Collections.IMapView AvailableActions { get; }; Windows.Foundation.Collections.IMapView NameMap { get; }; Windows.Foundation.Collections.IMapView KeyBindings { get; }; Windows.Foundation.Collections.IMapView GlobalHotkeys { get; }; + Windows.Foundation.Collections.IVectorView AllCommands { get; }; IVector 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); } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index 9dc24334038..95a1787e654 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -9,6 +9,8 @@ #include "DefaultTerminal.h" #include "FileUtils.h" +#include "AllShortcutActions.h" + #include #include #include @@ -1017,6 +1019,109 @@ winrt::hstring CascadiaSettings::ApplicationVersion() return RS_(L"ApplicationVersionUnknown"); } +winrt::Windows::Foundation::Collections::IMap CascadiaSettings::AvailableShortcutActionsAndNames() +{ + std::map 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(); + case Model::ShortcutAction::MovePane: + return winrt::make(); + case Model::ShortcutAction::SwitchToTab: + return winrt::make(); + case Model::ShortcutAction::ResizePane: + return winrt::make(); + case Model::ShortcutAction::MoveFocus: + return winrt::make(); + case Model::ShortcutAction::SwapPane: + return winrt::make(); + case Model::ShortcutAction::AdjustFontSize: + return winrt::make(); + case Model::ShortcutAction::SendInput: + return winrt::make(); + case Model::ShortcutAction::OpenSettings: + return winrt::make(); + case Model::ShortcutAction::SetFocusMode: + return winrt::make(); + case Model::ShortcutAction::SetFullScreen: + return winrt::make(); + case Model::ShortcutAction::SetMaximized: + return winrt::make(); + case Model::ShortcutAction::SetColorScheme: + return winrt::make(); + case Model::ShortcutAction::RenameTab: + return winrt::make(); + case Model::ShortcutAction::ExecuteCommandline: + return winrt::make(); + case Model::ShortcutAction::CloseOtherTabs: + return winrt::make(); + case Model::ShortcutAction::CloseTabsAfter: + return winrt::make(); + case Model::ShortcutAction::CloseTab: + return winrt::make(); + case Model::ShortcutAction::MoveTab: + return winrt::make(); + case Model::ShortcutAction::ScrollUp: + return winrt::make(); + case Model::ShortcutAction::ScrollDown: + return winrt::make(); + case Model::ShortcutAction::ScrollToMark: + return winrt::make(); + case Model::ShortcutAction::ToggleCommandPalette: + return winrt::make(); + case Model::ShortcutAction::Suggestions: + return winrt::make(); + case Model::ShortcutAction::FindMatch: + return winrt::make(); + case Model::ShortcutAction::RenameWindow: + return winrt::make(); + case Model::ShortcutAction::SearchForText: + return winrt::make(); + case Model::ShortcutAction::GlobalSummon: + return winrt::make(); + case Model::ShortcutAction::FocusPane: + return winrt::make(); + case Model::ShortcutAction::ExportBuffer: + return winrt::make(); + case Model::ShortcutAction::ClearBuffer: + return winrt::make(); + case Model::ShortcutAction::AdjustOpacity: + return winrt::make(); + case Model::ShortcutAction::SelectCommand: + return winrt::make(); + case Model::ShortcutAction::SelectOutput: + return winrt::make(); + case Model::ShortcutAction::AddMark: + return winrt::make(); + case Model::ShortcutAction::SetTabColor: + return winrt::make(); + case Model::ShortcutAction::PrevTab: + return winrt::make(); + case Model::ShortcutAction::NextTab: + return winrt::make(); + case Model::ShortcutAction::NewTab: + return winrt::make(); + case Model::ShortcutAction::NewWindow: + return winrt::make(); + case Model::ShortcutAction::SplitPane: + return winrt::make(); + default: + return nullptr; + } +} + // Method Description: // - Determines if we're on an OS platform that supports // the default terminal handoff functionality. diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index e650d7c5890..11d16af7d56 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -115,6 +115,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static winrt::hstring ApplicationVersion(); static bool IsPortableMode(); + static Windows::Foundation::Collections::IMap 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 = {}); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl index 863d2fa7512..965aea5f0cf 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl @@ -5,6 +5,7 @@ import "GlobalAppSettings.idl"; import "Profile.idl"; import "TerminalWarnings.idl"; import "DefaultTerminal.idl"; +import "ActionArgs.idl"; namespace Microsoft.Terminal.Settings.Model { @@ -20,6 +21,9 @@ namespace Microsoft.Terminal.Settings.Model static String ApplicationDisplayName { get; }; static String ApplicationVersion { get; }; + static Windows.Foundation.Collections.IMap AvailableShortcutActionsAndNames { get; }; + static IActionArgs GetEmptyArgsForAction(Microsoft.Terminal.Settings.Model.ShortcutAction shortcutAction); + CascadiaSettings(String userJSON, String inboxJSON); CascadiaSettings Copy(); diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 7ec4d073747..476e95e484f 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -28,6 +28,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { Command::Command() = default; + Model::Command Command::NewUserCommand() + { + auto newCmd{ winrt::make_self() }; + newCmd->_Origin = OriginTag::User; + return *newCmd; + } + + Model::Command Command::CopyAsUserCommand(Model::Command originalCmd) + { + auto command{ winrt::get_self(originalCmd) }; + auto copy{ command->Copy() }; + copy->_Origin = OriginTag::User; + return *copy; + } + com_ptr Command::Copy() const { auto command{ winrt::make_self() }; @@ -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(_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) diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index 4d372bea9cb..e4f98b8bb73 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -45,6 +45,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation struct Command : CommandT { Command(); + static Model::Command NewUserCommand(); + static Model::Command CopyAsUserCommand(Model::Command originalCmd); com_ptr Copy() const; static winrt::com_ptr 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 IDChanged; + private: Json::Value _originalJson; + Model::ActionAndArgs _ActionAndArgs{}; Windows::Foundation::Collections::IMap _subcommands{ nullptr }; std::optional _name; std::wstring _ID; diff --git a/src/cascadia/TerminalSettingsModel/Command.idl b/src/cascadia/TerminalSettingsModel/Command.idl index a364eabb4dd..310b0402058 100644 --- a/src/cascadia/TerminalSettingsModel/Command.idl +++ b/src/cascadia/TerminalSettingsModel/Command.idl @@ -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 ParsePowerShellMenuComplete(String json, Int32 replaceLength); static IVector HistoryToCommands(IVector commandHistory, String commandline, Boolean directories, String iconPath); + event Windows.Foundation.TypedEventHandler IDChanged; } } diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp index 7895bec7d95..824e9a6cb6d 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp @@ -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 EnumMappings::FontWeight() diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.h b/src/cascadia/TerminalSettingsModel/EnumMappings.h index 15fbf50a403..83d183b344b 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.h +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.h @@ -49,6 +49,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static winrt::Windows::Foundation::Collections::IMap IntenseTextStyle(); static winrt::Windows::Foundation::Collections::IMap AdjustIndistinguishableColors(); static winrt::Windows::Foundation::Collections::IMap PathTranslationStyle(); + + // Actions + static winrt::Windows::Foundation::Collections::IMap ResizeDirection(); + static winrt::Windows::Foundation::Collections::IMap FocusDirection(); + static winrt::Windows::Foundation::Collections::IMap SplitDirection(); + static winrt::Windows::Foundation::Collections::IMap SplitType(); + static winrt::Windows::Foundation::Collections::IMap SettingsTarget(); + static winrt::Windows::Foundation::Collections::IMap MoveTabDirection(); + static winrt::Windows::Foundation::Collections::IMap ScrollToMarkDirection(); + static winrt::Windows::Foundation::Collections::IMap CommandPaletteLaunchMode(); + static winrt::Windows::Foundation::Collections::IMap SuggestionsSource(); + static winrt::Windows::Foundation::Collections::IMap FindMatchDirection(); + static winrt::Windows::Foundation::Collections::IMap DesktopBehavior(); + static winrt::Windows::Foundation::Collections::IMap MonitorBehavior(); + static winrt::Windows::Foundation::Collections::IMap ClearBufferType(); + static winrt::Windows::Foundation::Collections::IMap SelectOutputDirection(); }; } diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.idl b/src/cascadia/TerminalSettingsModel/EnumMappings.idl index 57735eccd5b..f4c8d2a318d 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.idl +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.idl @@ -31,5 +31,21 @@ namespace Microsoft.Terminal.Settings.Model static Windows.Foundation.Collections.IMap FontWeight { get; }; static Windows.Foundation.Collections.IMap IntenseTextStyle { get; }; static Windows.Foundation.Collections.IMap PathTranslationStyle { get; }; + + // Actions + static Windows.Foundation.Collections.IMap ResizeDirection { get; }; + static Windows.Foundation.Collections.IMap FocusDirection { get; }; + static Windows.Foundation.Collections.IMap SplitDirection { get; }; + static Windows.Foundation.Collections.IMap SplitType { get; }; + static Windows.Foundation.Collections.IMap SettingsTarget { get; }; + static Windows.Foundation.Collections.IMap MoveTabDirection { get; }; + static Windows.Foundation.Collections.IMap ScrollToMarkDirection { get; }; + static Windows.Foundation.Collections.IMap CommandPaletteLaunchMode { get; }; + static Windows.Foundation.Collections.IMap SuggestionsSource { get; }; + static Windows.Foundation.Collections.IMap FindMatchDirection { get; }; + static Windows.Foundation.Collections.IMap DesktopBehavior { get; }; + static Windows.Foundation.Collections.IMap MonitorBehavior { get; }; + static Windows.Foundation.Collections.IMap ClearBufferType { get; }; + static Windows.Foundation.Collections.IMap SelectOutputDirection { get; }; } } diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index b1c24a54d0f..330c84a3228 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -740,4 +740,274 @@ Open current working directory + + Copy Text + + + Paste Text + + + Open New Tab Dropdown + + + Duplicate Tab + + + New Tab + + + Close Window + + + Close Tab + + + Close Pane + + + Next Tab + + + Previous Tab + + + Send Input + + + Split Pane + + + Toggle Split Orientation + + + Toggle Pane Zoom + + + Switch To Tab + + + Adjust Font Size + + + Reset Font Size + + + Scroll Up + + + Scroll Down + + + Scroll Up Page + + + Scroll Down Page + + + Scroll To Top + + + Scroll To Bottom + + + Scroll To Mark + + + Add Mark + + + Clear Mark + + + Clear All Marks + + + Resize Pane + + + Move Focus + + + Move Pane + + + Swap Pane + + + Find + + + Toggle Shader Effects + + + Toggle Focus Mode + + + Toggle Fullscreen + + + Toggle Always On Top + + + Open Settings + + + Set Focus Mode + + + Set Full Screen + + + Set Maximized + + + Set Color Scheme + + + Set Tab Color + + + Open Tab Color Picker + + + Rename Tab + + + Open Tab Renamer + + + Execute Command Line + + + Toggle Command Palette + + + Close Other Tabs + + + Close Tabs After + + + Tab Search + + + Move Tab + + + Break Into Debugger + + + Toggle Pane Read-Only + + + Enable Pane Read-Only + + + Disable Pane Read-Only + + + Find Match + + + New Window + + + Identify Window + + + Identify Windows + + + Rename Window + + + Open Window Renamer + + + Display Working Directory + + + Search For Text + + + Global Summon + + + Quake Mode + + + Focus Pane + + + Open System Menu + + + Export Buffer + + + Clear Buffer + + + Multiple Actions + + + Quit + + + Adjust Opacity + + + Restore Last Closed + + + Select All + + + Select Command + + + Select Output + + + Mark Mode + + + Toggle Block Selection + + + Switch Selection Endpoint + + + Suggestions + + + Color Selection + + + Show Context Menu + + + Expand Selection To Word + + + Close Other Panes + + + Restart Connection + + + Toggle Broadcast Input + + + Open Scratchpad + + + Open About + + + Quick Fix + + + Open Current Working Directory + From 2f6108da6a2d0b3fdd708ba8b3e9f66987cbf3c9 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 14 May 2025 13:35:08 -0700 Subject: [PATCH 02/85] settings actions editor --- .../ActionsViewModel.cpp | 1191 +++++++++++++++++ .../TerminalSettingsEditor/ActionsViewModel.h | 228 ++++ .../ActionsViewModel.idl | 137 ++ .../ArgsTemplateSelectors.cpp | 117 ++ .../ArgsTemplateSelectors.h | 40 + .../ArgsTemplateSelectors.idl | 28 + .../TerminalSettingsEditor/EditAction.cpp | 56 + .../TerminalSettingsEditor/EditAction.h | 35 + .../TerminalSettingsEditor/EditAction.idl | 13 + .../TerminalSettingsEditor/EditAction.xaml | 702 ++++++++++ .../TerminalSettingsEditor/EnumEntry.h | 38 + .../TerminalSettingsEditor/EnumEntry.idl | 7 + .../TerminalSettingsEditor/MainPage.cpp | 32 + .../TerminalSettingsEditor/MainPage.h | 2 + .../TerminalSettingsEditor/MainPage.idl | 1 + .../TerminalSettingsEditor/MainPage.xaml | 11 +- ...Microsoft.Terminal.Settings.Editor.vcxproj | 37 + ...t.Terminal.Settings.Editor.vcxproj.filters | 3 + .../TerminalSettingsEditor/NewActions.cpp | 38 + .../TerminalSettingsEditor/NewActions.h | 31 + .../TerminalSettingsEditor/NewActions.idl | 13 + .../TerminalSettingsEditor/NewActions.xaml | 212 +++ .../Resources/en-US/Resources.resw | 316 +++++ src/cascadia/UIHelpers/Converters.cpp | 32 + src/cascadia/UIHelpers/Converters.h | 1 + src/cascadia/UIHelpers/Converters.idl | 1 + 26 files changed, 3320 insertions(+), 2 deletions(-) create mode 100644 src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.cpp create mode 100644 src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.h create mode 100644 src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.idl create mode 100644 src/cascadia/TerminalSettingsEditor/EditAction.cpp create mode 100644 src/cascadia/TerminalSettingsEditor/EditAction.h create mode 100644 src/cascadia/TerminalSettingsEditor/EditAction.idl create mode 100644 src/cascadia/TerminalSettingsEditor/EditAction.xaml create mode 100644 src/cascadia/TerminalSettingsEditor/NewActions.cpp create mode 100644 src/cascadia/TerminalSettingsEditor/NewActions.h create mode 100644 src/cascadia/TerminalSettingsEditor/NewActions.idl create mode 100644 src/cascadia/TerminalSettingsEditor/NewActions.xaml diff --git a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp index 2c1860d1f70..fef3c93ac83 100644 --- a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp @@ -5,8 +5,16 @@ #include "ActionsViewModel.h" #include "ActionsViewModel.g.cpp" #include "KeyBindingViewModel.g.cpp" +#include "CommandViewModel.g.cpp" +#include "ArgWrapper.g.cpp" +#include "ActionArgsViewModel.g.cpp" +#include "KeyChordViewModel.g.cpp" #include "LibraryResources.h" #include "../TerminalSettingsModel/AllShortcutActions.h" +#include "EnumEntry.h" +#include "ColorSchemeViewModel.h" +#include +#include "..\WinRTUtils\inc\Utils.h" using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; @@ -18,6 +26,173 @@ using namespace winrt::Windows::UI::Xaml::Data; using namespace winrt::Windows::UI::Xaml::Navigation; using namespace winrt::Microsoft::Terminal::Settings::Model; +// todo: +// multiple actions +// selection color +// the above arg types aren't implemented yet - they all have multiple values within them +// and require a different approach to binding/displaying. Selection color has color and IsIndex16, +// multiple actions is... multiple actions +// for now, do not support these shortcut actions in the new action editor +inline const std::set UnimplementedShortcutActions = { + winrt::Microsoft::Terminal::Settings::Model::ShortcutAction::MultipleActions, + winrt::Microsoft::Terminal::Settings::Model::ShortcutAction::ColorSelection +}; + +#define INITIALIZE_ENUM_LIST_AND_VALUE(enumMappingsName, enumType, resourceSectionAndType, resourceProperty) \ + std::vector enumList; \ + const auto mappings = winrt::Microsoft::Terminal::Settings::Model::EnumMappings::enumMappingsName(); \ + enumType unboxedValue; \ + if (_Value) \ + { \ + unboxedValue = unbox_value(_Value); \ + } \ + for (const auto [enumKey, enumValue] : mappings) \ + { \ + const auto enumName = LocalizedNameForEnumName(resourceSectionAndType, enumKey, resourceProperty); \ + auto entry = winrt::make(enumName, winrt::box_value(enumValue)); \ + enumList.emplace_back(entry); \ + if (_Value && unboxedValue == enumValue) \ + { \ + _EnumValue = entry; \ + } \ + } \ + std::sort(enumList.begin(), enumList.end(), EnumEntryReverseComparator()); \ + _EnumList = winrt::single_threaded_observable_vector(std::move(enumList)); \ + _NotifyChanges(L"EnumList", L"EnumValue"); + +#define INITIALIZE_NULLABLE_ENUM_LIST_AND_VALUE(enumMappingsName, enumType, resourceSectionAndType, resourceProperty) \ + std::vector enumList; \ + const auto mappings = winrt::Microsoft::Terminal::Settings::Model::EnumMappings::enumMappingsName(); \ + enumType unboxedValue; \ + auto nullEntry = winrt::make(RS_(L"Actions_NullEnumValue"), nullptr); \ + if (_Value) \ + { \ + unboxedValue = unbox_value(_Value); \ + } \ + else \ + { \ + _EnumValue = nullEntry; \ + } \ + for (const auto [enumKey, enumValue] : mappings) \ + { \ + const auto enumName = LocalizedNameForEnumName(resourceSectionAndType, enumKey, resourceProperty); \ + auto entry = winrt::make(enumName, winrt::box_value(enumValue)); \ + enumList.emplace_back(entry); \ + if (_Value && unboxedValue == enumValue) \ + { \ + _EnumValue = entry; \ + } \ + } \ + std::sort(enumList.begin(), enumList.end(), EnumEntryReverseComparator()); \ + enumList.emplace_back(nullEntry); \ + _EnumList = winrt::single_threaded_observable_vector(std::move(enumList)); \ + _NotifyChanges(L"EnumList", L"EnumValue"); + +#define INITIALIZE_FLAG_LIST_AND_VALUE(enumMappingsName, enumType, resourceSectionAndType, resourceProperty) \ + std::vector flagList; \ + const auto mappings = winrt::Microsoft::Terminal::Settings::Model::EnumMappings::enumMappingsName(); \ + enumType unboxedValue{ 0 }; \ + if (_Value) \ + { \ + unboxedValue = unbox_value(_Value); \ + } \ + for (const auto [flagKey, flagValue] : mappings) \ + { \ + if (flagKey != L"all" && flagKey != L"none") \ + { \ + const auto flagName = LocalizedNameForEnumName(resourceSectionAndType, flagKey, resourceProperty); \ + bool isSet = WI_IsAnyFlagSet(unboxedValue, flagValue); \ + auto entry = winrt::make(flagName, winrt::box_value(flagValue), isSet); \ + entry.PropertyChanged([&, flagValue](const IInspectable& sender, const PropertyChangedEventArgs& args) { \ + const auto itemProperty{ args.PropertyName() }; \ + if (itemProperty == L"IsSet") \ + { \ + const auto flagWrapper = sender.as(); \ + auto unboxed = unbox_value(_Value); \ + flagWrapper.IsSet() ? WI_SetAllFlags(unboxed, flagValue) : WI_ClearAllFlags(unboxed, flagValue); \ + Value(box_value(unboxed)); \ + } \ + }); \ + flagList.emplace_back(entry); \ + } \ + } \ + std::sort(flagList.begin(), flagList.end(), FlagEntryReverseComparator()); \ + _FlagList = winrt::single_threaded_observable_vector(std::move(flagList)); \ + _NotifyChanges(L"FlagList"); + +#define INITIALIZE_NULLABLE_FLAG_LIST_AND_VALUE(enumMappingsName, enumType, resourceSectionAndType, resourceProperty) \ + std::vector flagList; \ + const auto mappings = winrt::Microsoft::Terminal::Settings::Model::EnumMappings::enumMappingsName(); \ + enumType unboxedValue{ 0 }; \ + auto nullEntry = winrt::make(RS_(L"Actions_NullEnumValue"), nullptr, true); \ + if (_Value) \ + { \ + if (const auto unboxedRef = unbox_value>(_Value)) \ + { \ + unboxedValue = unboxedRef.Value(); \ + nullEntry.IsSet(false); \ + } \ + } \ + for (const auto [flagKey, flagValue] : mappings) \ + { \ + if (flagKey != L"all" && flagKey != L"none") \ + { \ + const auto flagName = LocalizedNameForEnumName(resourceSectionAndType, flagKey, resourceProperty); \ + bool isSet = WI_IsAnyFlagSet(unboxedValue, flagValue); \ + auto entry = winrt::make(flagName, winrt::box_value(flagValue), isSet); \ + entry.PropertyChanged([&, flagValue, nullEntry](const IInspectable& sender, const PropertyChangedEventArgs& args) { \ + const auto itemProperty{ args.PropertyName() }; \ + if (itemProperty == L"IsSet") \ + { \ + const auto flagWrapper = sender.as(); \ + enumType unboxedValue{ 0 }; \ + auto unboxedRef = unbox_value>(_Value); \ + if (unboxedRef) \ + { \ + unboxedValue = unboxedRef.Value(); \ + } \ + if (flagWrapper.IsSet()) \ + { \ + nullEntry.IsSet(false); \ + WI_SetAllFlags(unboxedValue, flagValue); \ + } \ + else \ + { \ + WI_ClearAllFlags(unboxedValue, flagValue); \ + } \ + Value(box_value(unboxedValue)); \ + } \ + }); \ + flagList.emplace_back(entry); \ + } \ + } \ + std::sort(flagList.begin(), flagList.end(), FlagEntryReverseComparator()); \ + nullEntry.PropertyChanged([&](const IInspectable& sender, const PropertyChangedEventArgs& args) { \ + const auto itemProperty{ args.PropertyName() }; \ + if (itemProperty == L"IsSet") \ + { \ + const auto flagWrapper = sender.as(); \ + if (flagWrapper.IsSet()) \ + { \ + for (const auto flagEntry : _FlagList) \ + { \ + if (flagEntry.FlagName() != RS_(L"Actions_NullEnumValue")) \ + { \ + flagEntry.IsSet(false); \ + } \ + } \ + Value(box_value(Windows::Foundation::IReference(nullptr))); \ + } \ + else \ + { \ + Value(box_value(Windows::Foundation::IReference(enumType{ 0 }))); \ + } \ + } \ + }); \ + flagList.emplace_back(nullEntry); \ + _FlagList = winrt::single_threaded_observable_vector(std::move(flagList)); \ + _NotifyChanges(L"FlagList"); + namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { KeyBindingViewModel::KeyBindingViewModel(const IObservableVector& availableActions) : @@ -105,8 +280,805 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } + CommandViewModel::CommandViewModel(Command cmd, std::vector keyChordList, const Editor::ActionsViewModel actionsPageVM, const Windows::Foundation::Collections::IMap& availableShortcutActionsAndNames) : + _command{ cmd }, + _keyChordList{ keyChordList }, + _actionsPageVM{ actionsPageVM }, + _AvailableActionsAndNamesMap{ availableShortcutActionsAndNames } + { + } + + void CommandViewModel::Initialize() + { + std::vector keyChordVMs; + for (const auto keys : _keyChordList) + { + auto kcVM{ make(keys) }; + _RegisterKeyChordVMEvents(kcVM); + keyChordVMs.push_back(kcVM); + } + _KeyChordViewModelList = single_threaded_observable_vector(std::move(keyChordVMs)); + + std::vector shortcutActions; + for (const auto [action, name] : _AvailableActionsAndNamesMap) + { + shortcutActions.emplace_back(name); + _NameToActionMap.emplace(name, action); + } + std::sort(shortcutActions.begin(), shortcutActions.end()); + _AvailableShortcutActions = single_threaded_observable_vector(std::move(shortcutActions)); + + const auto shortcutActionString = _AvailableActionsAndNamesMap.Lookup(_command.ActionAndArgs().Action()); + ProposedShortcutAction(winrt::box_value(shortcutActionString)); + _CreateAndInitializeActionArgsVMHelper(); + + // Add a property changed handler to our own property changed event. + // This allows us to create a new ActionArgsVM when the shortcut action changes + PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) { + const auto viewModelProperty{ args.PropertyName() }; + if (viewModelProperty == L"ProposedShortcutAction") + { + const auto actionString = unbox_value(ProposedShortcutAction()); + const auto actionEnum = _NameToActionMap.at(actionString); + const auto emptyArgs = CascadiaSettings::GetEmptyArgsForAction(actionEnum); + // todo: probably need some better default values for empty args + // eg. for sendInput, where "input" is a required argument, "input" gets set to an empty string which does not satisfy the requirement + // i.e. if the user hits "save" immediately after switching to sendInput as the action (without adding something to the input field), they'll get an error + // there are some other cases as well + Model::ActionAndArgs newActionAndArgs{ actionEnum, emptyArgs }; + _command.ActionAndArgs(newActionAndArgs); + if (_IsNewCommand) + { + _command.GenerateID(); + } + else if (!IsUserAction()) + { + _ReplaceCommandWithUserCopy(true); + return; + } + _CreateAndInitializeActionArgsVMHelper(); + } + }); + } + + winrt::hstring CommandViewModel::DisplayName() + { + return _command.Name(); + } + + winrt::hstring CommandViewModel::Name() + { + if (_command.HasName()) + { + return _command.Name(); + } + return L""; + } + + void CommandViewModel::Name(const winrt::hstring& newName) + { + if (!newName.empty()) + { + _command.Name(newName); + } + } + + winrt::hstring CommandViewModel::ID() + { + return _command.ID(); + } + + void CommandViewModel::ID(const winrt::hstring& ID) + { + _command.ID(ID); + } + + bool CommandViewModel::IsUserAction() + { + return _command.Origin() == OriginTag::User; + } + + void CommandViewModel::Edit_Click() + { + EditRequested.raise(*this, *this); + } + + void CommandViewModel::Delete_Click() + { + DeleteRequested.raise(*this, *this); + } + + void CommandViewModel::AddKeybinding_Click() + { + auto kbdVM{ make_self(nullptr) }; + kbdVM->IsInEditMode(true); + _RegisterKeyChordVMEvents(*kbdVM); + KeyChordViewModelList().Append(*kbdVM); + } + + void CommandViewModel::_RegisterKeyChordVMEvents(Editor::KeyChordViewModel kcVM) + { + if (const auto actionsPageVM{ _actionsPageVM.get() }) + { + const auto id = ID(); + kcVM.AddKeyChordRequested([actionsPageVM, id](const Editor::KeyChordViewModel& sender, const Control::KeyChord& keys) { + actionsPageVM.AttemptAddOrModifyKeyChord(sender, id, keys, nullptr); + }); + kcVM.ModifyKeyChordRequested([actionsPageVM, id](const Editor::KeyChordViewModel& sender, const Editor::ModifyKeyChordEventArgs& args) { + actionsPageVM.AttemptAddOrModifyKeyChord(sender, id, args.NewKeys(), args.OldKeys()); + }); + kcVM.DeleteKeyChordRequested([&, actionsPageVM](const Editor::KeyChordViewModel& sender, const Control::KeyChord& args) { + _keyChordList.erase( + std::remove_if( + _keyChordList.begin(), + _keyChordList.end(), + [&](const Control::KeyChord& kc) { return kc == args; }), + _keyChordList.end()); + for (uint32_t i = 0; i < _KeyChordViewModelList.Size(); i++) + { + if (_KeyChordViewModelList.GetAt(i) == sender) + { + KeyChordViewModelList().RemoveAt(i); + break; + } + } + actionsPageVM.AttemptDeleteKeyChord(args); + }); + } + } + + void CommandViewModel::_RegisterActionArgsVMEvents(Editor::ActionArgsViewModel actionArgsVM) + { + actionArgsVM.PropagateColorSchemeRequested([weakThis = get_weak()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) { + if (auto weak = weakThis.get()) + { + if (wrapper) + { + weak->PropagateColorSchemeRequested.raise(*weak, wrapper); + } + } + }); + actionArgsVM.PropagateColorSchemeNamesRequested([weakThis = get_weak()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) { + if (auto weak = weakThis.get()) + { + if (wrapper) + { + weak->PropagateColorSchemeNamesRequested.raise(*weak, wrapper); + } + } + }); + actionArgsVM.PropagateWindowRootRequested([weakThis = get_weak()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) { + if (auto weak = weakThis.get()) + { + if (wrapper) + { + weak->PropagateWindowRootRequested.raise(*weak, wrapper); + } + } + }); + if (_IsNewCommand) + { + // for new commands, make sure we generate a new ID everytime any arg value changes + actionArgsVM.WrapperValueChanged([weakThis = get_weak()](const IInspectable& /*sender*/, const IInspectable& /*args*/) { + if (auto weak = weakThis.get()) + { + weak->_command.GenerateID(); + } + }); + } + else if (!IsUserAction()) + { + actionArgsVM.WrapperValueChanged([weakThis = get_weak()](const IInspectable& /*sender*/, const IInspectable& /*args*/) { + if (auto weak = weakThis.get()) + { + weak->_ReplaceCommandWithUserCopy(false); + } + }); + } + } + + void CommandViewModel::_ReplaceCommandWithUserCopy(bool reinitialize) + { + // the user is attempting to edit an in-box action + // to handle this, we create a new command with the new values that has the same ID as the in-box action + // swap out our underlying command with the copy, tell the ActionsVM that the copy needs to be added to the action map + if (const auto actionsPageVM{ _actionsPageVM.get() }) + { + const auto newCmd = Model::Command::CopyAsUserCommand(_command); + _command = newCmd; + actionsPageVM.AttemptAddCopiedCommand(_command); + if (reinitialize) + { + // full reinitialize needed, recreate the action args VM + // (this happens when the shortcut action is being changed on an in-box action) + _CreateAndInitializeActionArgsVMHelper(); + } + else + { + // no need to reinitialize, just swap out the underlying data model + // (this happens when an additional argument is being changed on an in-box action) + auto actionArgsVMImpl{ get_self(_ActionArgsVM) }; + actionArgsVMImpl->ReplaceActionAndArgs(_command.ActionAndArgs()); + } + } + } + + void CommandViewModel::_CreateAndInitializeActionArgsVMHelper() + { + const auto actionArgsVM = make_self(_command.ActionAndArgs()); + _RegisterActionArgsVMEvents(*actionArgsVM); + actionArgsVM->Initialize(); + ActionArgsVM(*actionArgsVM); + _NotifyChanges(L"DisplayName"); + } + + ArgWrapper::ArgWrapper(const winrt::hstring& name, const winrt::hstring& type, const bool required, const Model::ArgTag tag, const Windows::Foundation::IInspectable& value) : + _name{ name }, + _type{ type }, + _tag{ tag }, + _required{ required } + { + Value(value); + } + + void ArgWrapper::Initialize() + { + if (_type == L"Model::ResizeDirection") + { + INITIALIZE_ENUM_LIST_AND_VALUE(ResizeDirection, Model::ResizeDirection, L"Actions_ResizeDirection", L"Content"); + } + else if (_type == L"Model::FocusDirection") + { + INITIALIZE_ENUM_LIST_AND_VALUE(FocusDirection, Model::FocusDirection, L"Actions_FocusDirection", L"Content"); + } + else if (_type == L"SettingsTarget") + { + INITIALIZE_ENUM_LIST_AND_VALUE(SettingsTarget, Model::SettingsTarget, L"Actions_SettingsTarget", L"Content"); + } + else if (_type == L"MoveTabDirection") + { + INITIALIZE_ENUM_LIST_AND_VALUE(MoveTabDirection, Model::MoveTabDirection, L"Actions_MoveTabDirection", L"Content"); + } + else if (_type == L"Microsoft::Terminal::Control::ScrollToMarkDirection") + { + INITIALIZE_ENUM_LIST_AND_VALUE(ScrollToMarkDirection, Control::ScrollToMarkDirection, L"Actions_ScrollToMarkDirection", L"Content"); + } + else if (_type == L"CommandPaletteLaunchMode") + { + INITIALIZE_ENUM_LIST_AND_VALUE(CommandPaletteLaunchMode, Model::CommandPaletteLaunchMode, L"Actions_CommandPaletteLaunchMode", L"Content"); + } + else if (_type == L"SuggestionsSource") + { + INITIALIZE_FLAG_LIST_AND_VALUE(SuggestionsSource, Model::SuggestionsSource, L"Actions_SuggestionsSource", L"Content"); + } + else if (_type == L"FindMatchDirection") + { + INITIALIZE_ENUM_LIST_AND_VALUE(FindMatchDirection, Model::FindMatchDirection, L"Actions_FindMatchDirection", L"Content"); + } + else if (_type == L"Model::DesktopBehavior") + { + INITIALIZE_ENUM_LIST_AND_VALUE(DesktopBehavior, Model::DesktopBehavior, L"Actions_DesktopBehavior", L"Content"); + } + else if (_type == L"Model::MonitorBehavior") + { + INITIALIZE_ENUM_LIST_AND_VALUE(MonitorBehavior, Model::MonitorBehavior, L"Actions_MonitorBehavior", L"Content"); + } + else if (_type == L"winrt::Microsoft::Terminal::Control::ClearBufferType") + { + INITIALIZE_ENUM_LIST_AND_VALUE(ClearBufferType, Control::ClearBufferType, L"Actions_ClearBufferType", L"Content"); + } + else if (_type == L"SelectOutputDirection") + { + INITIALIZE_ENUM_LIST_AND_VALUE(SelectOutputDirection, Model::SelectOutputDirection, L"Actions_SelectOutputDirection", L"Content"); + } + else if (_type == L"Model::SplitDirection") + { + INITIALIZE_ENUM_LIST_AND_VALUE(SplitDirection, Model::SplitDirection, L"Actions_SplitDirection", L"Content"); + } + else if (_type == L"SplitType") + { + INITIALIZE_ENUM_LIST_AND_VALUE(SplitType, Model::SplitType, L"Actions_SplitType", L"Content"); + } + else if (_type == L"Windows::Foundation::IReference") + { + INITIALIZE_NULLABLE_ENUM_LIST_AND_VALUE(TabSwitcherMode, Model::TabSwitcherMode, L"Actions_TabSwitcherMode", L"Content"); + } + else if (_type == L"Windows::Foundation::IReference") + { + INITIALIZE_NULLABLE_FLAG_LIST_AND_VALUE(CopyFormat, Control::CopyFormat, L"Actions_CopyFormat", L"Content"); + } + else if (_type == L"Windows::Foundation::IReference" || + _type == L"Windows::Foundation::IReference") + { + ColorSchemeRequested.raise(*this, *this); + } + else if (_tag == Model::ArgTag::ColorScheme) + { + // special case of string, emit an event letting the actionsVM know we need the list of color scheme names + ColorSchemeNamesRequested.raise(*this, *this); + + // even though the arg type is technically a string, we want an enum list for color schemes specifically + std::vector namesList; + const auto currentSchemeName = unbox_value(_Value); + auto nullEntry = winrt::make(RS_(L"Actions_NullEnumValue"), nullptr); + if (currentSchemeName.empty()) + { + _EnumValue = nullEntry; + } + for (const auto colorSchemeName : _ColorSchemeNamesList) + { + // eventually we will want to use localized names for the enum entries, for now just use what the settings model gives us + auto entry = winrt::make(colorSchemeName, winrt::box_value(colorSchemeName)); + namesList.emplace_back(entry); + if (currentSchemeName == colorSchemeName) + { + _EnumValue = entry; + } + } + std::sort(namesList.begin(), namesList.end(), EnumEntryReverseComparator()); + namesList.emplace_back(nullEntry); + _EnumList = winrt::single_threaded_observable_vector(std::move(namesList)); + _NotifyChanges(L"EnumList", L"EnumValue"); + } + } + + safe_void_coroutine ArgWrapper::Browse_Click(const IInspectable&, const RoutedEventArgs&) + { + WindowRootRequested.raise(*this, *this); + auto lifetime = get_strong(); + + static constexpr winrt::guid clientGuidFiles{ 0xbd00ae34, 0x839b, 0x43f6, { 0x8b, 0x94, 0x12, 0x37, 0x1a, 0xfe, 0xea, 0xb5 } }; + const auto parentHwnd{ reinterpret_cast(_WindowRoot.GetHostingWindow()) }; + auto path = co_await OpenFilePicker(parentHwnd, [](auto&& dialog) { + THROW_IF_FAILED(dialog->SetClientGuid(clientGuidFiles)); + try + { + auto folderShellItem{ winrt::capture(&SHGetKnownFolderItem, FOLDERID_ComputerFolder, KF_FLAG_DEFAULT, nullptr) }; + dialog->SetDefaultFolder(folderShellItem.get()); + } + CATCH_LOG(); // non-fatal + }); + + if (!path.empty()) + { + StringBindBack(path); + } + } + + void ArgWrapper::EnumValue(const Windows::Foundation::IInspectable& enumValue) + { + if (_EnumValue != enumValue) + { + _EnumValue = enumValue; + Value(_EnumValue.as().EnumValue()); + } + } + + winrt::hstring ArgWrapper::UnboxString(const Windows::Foundation::IInspectable& value) + { + return winrt::unbox_value(value); + } + + winrt::hstring ArgWrapper::UnboxGuid(const Windows::Foundation::IInspectable& value) + { + return winrt::to_hstring(winrt::unbox_value(value)); + } + + int32_t ArgWrapper::UnboxInt32(const Windows::Foundation::IInspectable& value) + { + return winrt::unbox_value(value); + } + + float ArgWrapper::UnboxInt32Optional(const Windows::Foundation::IInspectable& value) + { + const auto unboxed = winrt::unbox_value>(value); + if (unboxed) + { + return static_cast(unboxed.Value()); + } + else + { + return NAN; + } + } + + uint32_t ArgWrapper::UnboxUInt32(const Windows::Foundation::IInspectable& value) + { + return winrt::unbox_value(value); + } + + float ArgWrapper::UnboxUInt32Optional(const Windows::Foundation::IInspectable& value) + { + const auto unboxed = winrt::unbox_value>(value); + if (unboxed) + { + return static_cast(unboxed.Value()); + } + else + { + return NAN; + } + } + + float ArgWrapper::UnboxUInt64(const Windows::Foundation::IInspectable& value) + { + return static_cast(winrt::unbox_value(value)); + } + + float ArgWrapper::UnboxFloat(const Windows::Foundation::IInspectable& value) + { + return winrt::unbox_value(value); + } + + bool ArgWrapper::UnboxBool(const Windows::Foundation::IInspectable& value) + { + return winrt::unbox_value(value); + } + + winrt::Windows::Foundation::IReference ArgWrapper::UnboxBoolOptional(const Windows::Foundation::IInspectable& value) + { + if (!value) + { + return nullptr; + } + return winrt::unbox_value>(value); + } + + winrt::Windows::Foundation::IReference ArgWrapper::UnboxTerminalCoreColorOptional(const Windows::Foundation::IInspectable& value) + { + if (value) + { + return unbox_value>(value); + } + else + { + return nullptr; + } + } + + winrt::Windows::Foundation::IReference ArgWrapper::UnboxWindowsUIColorOptional(const Windows::Foundation::IInspectable& value) + { + if (value) + { + const auto winUIColor = unbox_value>(value).Value(); + const Microsoft::Terminal::Core::Color terminalColor{ winUIColor.R, winUIColor.G, winUIColor.B, winUIColor.A }; + return Windows::Foundation::IReference{ terminalColor }; + } + else + { + return nullptr; + } + } + + void ArgWrapper::StringBindBack(const winrt::hstring& newValue) + { + if (UnboxString(_Value) != newValue) + { + Value(box_value(newValue)); + } + } + + void ArgWrapper::GuidBindBack(const winrt::hstring& newValue) + { + if (UnboxGuid(_Value) != newValue) + { + // todo: probably need some validation? + Value(box_value(winrt::guid{ newValue })); + } + } + + void ArgWrapper::Int32BindBack(const double newValue) + { + if (UnboxInt32(_Value) != newValue) + { + Value(box_value(static_cast(newValue))); + } + } + + void ArgWrapper::Int32OptionalBindBack(const double newValue) + { + if (!isnan(newValue)) + { + const auto currentValue = UnboxInt32Optional(_Value); + if (isnan(currentValue) || static_cast(currentValue) != static_cast(newValue)) + { + Value(box_value(static_cast(newValue))); + } + } + else if (!_Value) + { + Value(nullptr); + } + } + + void ArgWrapper::UInt32BindBack(const double newValue) + { + if (UnboxUInt32(_Value) != newValue) + { + Value(box_value(static_cast(newValue))); + } + } + + void ArgWrapper::UInt32OptionalBindBack(const double newValue) + { + if (!isnan(newValue)) + { + const auto currentValue = UnboxUInt32Optional(_Value); + if (isnan(currentValue) || static_cast(currentValue) != static_cast(newValue)) + { + Value(box_value(static_cast(newValue))); + } + } + else if (!_Value) + { + Value(nullptr); + } + } + + void ArgWrapper::UInt64BindBack(const double newValue) + { + if (UnboxUInt64(_Value) != newValue) + { + Value(box_value(static_cast(newValue))); + } + } + + void ArgWrapper::FloatBindBack(const double newValue) + { + if (UnboxFloat(_Value) != newValue) + { + Value(box_value(static_cast(newValue))); + } + } + + void ArgWrapper::BoolOptionalBindBack(const Windows::Foundation::IReference newValue) + { + if (newValue) + { + const auto currentValue = UnboxBoolOptional(_Value); + if (!currentValue || currentValue.Value() != newValue.Value()) + { + Value(box_value(newValue)); + } + } + else if (_Value) + { + Value(nullptr); + } + } + + void ArgWrapper::TerminalCoreColorBindBack(const winrt::Windows::Foundation::IReference newValue) + { + if (newValue) + { + const auto currentValue = UnboxTerminalCoreColorOptional(_Value); + if (!currentValue || currentValue.Value() != newValue.Value()) + { + Value(box_value(newValue)); + } + } + else if (_Value) + { + Value(nullptr); + } + } + + void ArgWrapper::WindowsUIColorBindBack(const winrt::Windows::Foundation::IReference newValue) + { + if (newValue) + { + const auto terminalCoreColor = unbox_value>(newValue).Value(); + const Windows::UI::Color winuiColor{ + .A = terminalCoreColor.A, + .R = terminalCoreColor.R, + .G = terminalCoreColor.G, + .B = terminalCoreColor.B + }; + // only set to the new value if our current value is not the same + // unfortunately the Value setter does not do this check properly since + // we create a whole new IReference even for the same underlying color + if (_Value) + { + const auto currentValue = unbox_value>(_Value).Value(); + if (currentValue == winuiColor) + { + return; + } + } + Value(box_value(Windows::Foundation::IReference{ winuiColor })); + } + else if (_Value) + { + Value(nullptr); + } + } + + ActionArgsViewModel::ActionArgsViewModel(const Model::ActionAndArgs actionAndArgs) : + _actionAndArgs{ actionAndArgs } + { + } + + void ActionArgsViewModel::Initialize() + { + const auto shortcutArgs = _actionAndArgs.Args(); + if (shortcutArgs) + { + const auto shortcutArgsNumItems = shortcutArgs.GetArgCount(); + std::vector argValues; + for (uint32_t i = 0; i < shortcutArgsNumItems; i++) + { + const auto argAtIndex = shortcutArgs.GetArgAt(i); + const auto argDescription = shortcutArgs.GetArgDescriptionAt(i); + const auto argName = argDescription.Name; + const auto argType = argDescription.Type; + const auto argTag = argDescription.Tag; + const auto argRequired = argDescription.Required; + const auto item = make_self(argName, argType, argRequired, argTag, argAtIndex); + item->PropertyChanged([weakThis = get_weak(), i](const IInspectable& sender, const PropertyChangedEventArgs& args) { + if (auto weak = weakThis.get()) + { + const auto itemProperty{ args.PropertyName() }; + if (itemProperty == L"Value") + { + const auto argWrapper = sender.as(); + const auto newValue = argWrapper.Value(); + weak->_actionAndArgs.Args().SetArgAt(i, newValue); + weak->WrapperValueChanged.raise(*weak, nullptr); + } + } + }); + item->ColorSchemeRequested([weakThis = get_weak()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) { + if (auto weak = weakThis.get()) + { + if (wrapper) + { + weak->PropagateColorSchemeRequested.raise(*weak, wrapper); + } + } + }); + item->ColorSchemeNamesRequested([weakThis = get_weak()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) { + if (auto weak = weakThis.get()) + { + if (wrapper) + { + weak->PropagateColorSchemeNamesRequested.raise(*weak, wrapper); + } + } + }); + item->WindowRootRequested([weakThis = get_weak()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) { + if (auto weak = weakThis.get()) + { + if (wrapper) + { + weak->PropagateWindowRootRequested.raise(*weak, wrapper); + } + } + }); + item->Initialize(); + argValues.push_back(*item); + } + + _ArgValues = single_threaded_observable_vector(std::move(argValues)); + } + } + + bool ActionArgsViewModel::HasArgs() const noexcept + { + return _actionAndArgs.Args() != nullptr; + } + + void ActionArgsViewModel::ReplaceActionAndArgs(Model::ActionAndArgs newActionAndArgs) + { + _actionAndArgs = newActionAndArgs; + } + + KeyChordViewModel::KeyChordViewModel(Control::KeyChord currentKeys) + { + CurrentKeys(currentKeys); + } + + void KeyChordViewModel::CurrentKeys(const Control::KeyChord& newKeys) + { + _currentKeys = newKeys; + KeyChordText(Model::KeyChordSerialization::ToString(_currentKeys)); + } + + Control::KeyChord KeyChordViewModel::CurrentKeys() const noexcept + { + return _currentKeys; + } + + void KeyChordViewModel::ToggleEditMode() + { + // toggle edit mode + IsInEditMode(!_IsInEditMode); + if (_IsInEditMode) + { + // if we're in edit mode, + // - pre-populate the text box with the current keys + ProposedKeys(_currentKeys); + } + } + + void KeyChordViewModel::AttemptAcceptChanges() + { + if (!_currentKeys) + { + AddKeyChordRequested.raise(*this, _ProposedKeys); + } + else if (_currentKeys.Modifiers() != _ProposedKeys.Modifiers() || _currentKeys.Vkey() != _ProposedKeys.Vkey()) + { + const auto args{ make_self(_currentKeys, // OldKeys + _ProposedKeys) }; // NewKeys + ModifyKeyChordRequested.raise(*this, *args); + } + else + { + // no changes being requested, toggle edit mode + ToggleEditMode(); + } + } + + void KeyChordViewModel::CancelChanges() + { + ToggleEditMode(); + } + + void KeyChordViewModel::DeleteKeyChord() + { + DeleteKeyChordRequested.raise(*this, _currentKeys); + } + ActionsViewModel::ActionsViewModel(Model::CascadiaSettings settings) : _Settings{ settings } + { + _MakeCommandVMsHelper(); + } + + void ActionsViewModel::UpdateSettings(const Model::CascadiaSettings& settings) + { + _Settings = settings; + + // We want to re-initialize our CommandList, but we want to make sure + // we still have the same CurrentCommand as before (if that command still exists) + + // Store the ID of the current command + const auto currentCommandID = CurrentCommand() ? CurrentCommand().ID() : hstring{}; + + // Re-initialize the command vm list + _MakeCommandVMsHelper(); + + // Re-select the previously selected command if it exists + if (!currentCommandID.empty()) + { + const auto it = _CommandList.First(); + while (it.HasCurrent()) + { + auto cmd = *it; + if (cmd.ID() == currentCommandID) + { + CurrentCommand(cmd); + break; + } + it.MoveNext(); + } + if (!it.HasCurrent()) + { + // we didn't find the previously selected command + CurrentCommand(nullptr); + CurrentPage(ActionsSubPage::Base); + } + } + else + { + // didn't have a command, + // so skip over looking through the command + CurrentCommand(nullptr); + CurrentPage(ActionsSubPage::Base); + } + } + + void ActionsViewModel::_MakeCommandVMsHelper() { // Populate AvailableActionAndArgs _AvailableActionMap = single_threaded_map(); @@ -119,6 +1091,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation std::sort(begin(availableActionAndArgs), end(availableActionAndArgs)); _AvailableActionAndArgs = single_threaded_observable_vector(std::move(availableActionAndArgs)); + _AvailableActionsAndNamesMap = Model::CascadiaSettings::AvailableShortcutActionsAndNames(); + for (const auto unimplemented : UnimplementedShortcutActions) + { + _AvailableActionsAndNamesMap.Remove(unimplemented); + } + // Convert the key bindings from our settings into a view model representation const auto& keyBindingMap{ _Settings.ActionMap().KeyBindings() }; std::vector keyBindingList; @@ -131,8 +1109,29 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation keyBindingList.push_back(*container); } + const auto& allCommands{ _Settings.ActionMap().AllCommands() }; + std::vector commandList; + commandList.reserve(allCommands.Size()); + for (const auto& cmd : allCommands) + { + if (!UnimplementedShortcutActions.contains(cmd.ActionAndArgs().Action())) + { + std::vector keyChordList; + for (const auto& keys : _Settings.ActionMap().AllKeyBindingsForAction(cmd.ID())) + { + keyChordList.emplace_back(keys); + } + auto cmdVM{ make_self(cmd, keyChordList, *this, _AvailableActionsAndNamesMap) }; + _RegisterCmdVMEvents(cmdVM); + cmdVM->Initialize(); + commandList.push_back(*cmdVM); + } + } + std::sort(begin(keyBindingList), end(keyBindingList), KeyBindingViewModelComparator{}); _KeyBindingList = single_threaded_observable_vector(std::move(keyBindingList)); + std::sort(begin(commandList), end(commandList), CommandViewModelComparator{}); + _CommandList = single_threaded_observable_vector(std::move(commandList)); } void ActionsViewModel::OnAutomationPeerAttached() @@ -167,6 +1166,128 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation FocusContainer.raise(*this, *kbdVM); } + void ActionsViewModel::AddNewCommand() + { + const auto newCmd = Model::Command::NewUserCommand(); + // construct a command using the first shortcut action from our list + const auto shortcutAction = _AvailableActionsAndNamesMap.First().Current().Key(); + const auto args = CascadiaSettings::GetEmptyArgsForAction(shortcutAction); + newCmd.ActionAndArgs(Model::ActionAndArgs{ shortcutAction, args }); + _Settings.ActionMap().AddAction(newCmd, nullptr); + auto cmdVM{ make_self(newCmd, std::vector{}, *this, _AvailableActionsAndNamesMap) }; + cmdVM->IsNewCommand(true); + _RegisterCmdVMEvents(cmdVM); + cmdVM->Initialize(); + _CommandList.Append(*cmdVM); + CurrentCommand(*cmdVM); + CurrentPage(ActionsSubPage::Edit); + } + + void ActionsViewModel::CurrentCommand(const Editor::CommandViewModel& newCommand) + { + _CurrentCommand = newCommand; + } + + Editor::CommandViewModel ActionsViewModel::CurrentCommand() + { + return _CurrentCommand; + } + + void ActionsViewModel::CmdListItemClicked(const IInspectable& /*sender*/, const winrt::Windows::UI::Xaml::Controls::ItemClickEventArgs& e) + { + if (const auto item = e.ClickedItem()) + { + CurrentCommand(item.try_as()); + CurrentPage(ActionsSubPage::Edit); + } + } + + void ActionsViewModel::AttemptDeleteKeyChord(const Control::KeyChord& keys) + { + // Update the settings model + if (keys) + { + _Settings.ActionMap().DeleteKeyBinding(keys); + } + } + + void ActionsViewModel::AttemptAddOrModifyKeyChord(const Editor::KeyChordViewModel& senderVM, winrt::hstring commandID, const Control::KeyChord& newKeys, const Control::KeyChord& oldKeys) + { + auto applyChangesToSettingsModel = [=]() { + // update settings model + if (oldKeys) + { + // if oldKeys is not null, this is a rebinding + // delete oldKeys and then add newKeys + _Settings.ActionMap().DeleteKeyBinding(oldKeys); + } + if (!Model::KeyChordSerialization::ToString(newKeys).empty()) + { + _Settings.ActionMap().AddKeyBinding(newKeys, commandID); + + // update view model + auto senderVMImpl{ get_self(senderVM) }; + senderVMImpl->CurrentKeys(newKeys); + } + + // reset the flyout if it's there + if (const auto flyout = senderVM.AcceptChangesFlyout()) + { + flyout.Hide(); + senderVM.AcceptChangesFlyout(nullptr); + } + // toggle edit mode + senderVM.ToggleEditMode(); + }; + + const auto& conflictingCmd{ _Settings.ActionMap().GetActionByKeyChord(newKeys) }; + if (conflictingCmd) + { + // We're about to overwrite another key chord. + // Display a confirmation dialog. + TextBlock errorMessageTB{}; + errorMessageTB.Text(RS_(L"Actions_RenameConflictConfirmationMessage")); + + const auto conflictingCmdName{ conflictingCmd.Name() }; + TextBlock conflictingCommandNameTB{}; + conflictingCommandNameTB.Text(fmt::format(L"\"{}\"", conflictingCmdName.empty() ? RS_(L"Actions_UnnamedCommandName") : conflictingCmdName)); + conflictingCommandNameTB.FontStyle(Windows::UI::Text::FontStyle::Italic); + + TextBlock confirmationQuestionTB{}; + confirmationQuestionTB.Text(RS_(L"Actions_RenameConflictConfirmationQuestion")); + + Button acceptBTN{}; + acceptBTN.Content(box_value(RS_(L"Actions_RenameConflictConfirmationAcceptButton"))); + acceptBTN.Click([=](auto&, auto&) { + // update settings model and view model + applyChangesToSettingsModel(); + }); + + StackPanel flyoutStack{}; + flyoutStack.Children().Append(errorMessageTB); + flyoutStack.Children().Append(conflictingCommandNameTB); + flyoutStack.Children().Append(confirmationQuestionTB); + flyoutStack.Children().Append(acceptBTN); + + Flyout acceptChangesFlyout{}; + acceptChangesFlyout.Content(flyoutStack); + senderVM.AcceptChangesFlyout(acceptChangesFlyout); + } + else + { + // update settings model and view model + applyChangesToSettingsModel(); + } + } + + void ActionsViewModel::AttemptAddCopiedCommand(const Model::Command& newCommand) + { + // The command VM calls this when the user has edited an in-box action + // newCommand is a copy of the in-box action that was edited, but with OriginTag::User + // add it to the action map + _Settings.ActionMap().AddAction(newCommand, nullptr); + } + void ActionsViewModel::_KeyBindingViewModelPropertyChangedHandler(const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) { const auto senderVM{ sender.as() }; @@ -213,6 +1334,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } + void ActionsViewModel::_CmdVMPropertyChangedHandler(const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) + { + const auto senderVM{ sender.as() }; + const auto propertyName{ args.PropertyName() }; + if (propertyName == L"ProposedAction") + { + } + } + void ActionsViewModel::_KeyBindingViewModelDeleteKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Control::KeyChord& keys) { // Update the settings model @@ -366,6 +1496,58 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } + void ActionsViewModel::_CmdVMEditRequestedHandler(const Editor::CommandViewModel& senderVM, const IInspectable& /*args*/) + { + CurrentCommand(senderVM); + CurrentPage(ActionsSubPage::Edit); + } + + void ActionsViewModel::_CmdVMDeleteRequestedHandler(const Editor::CommandViewModel& senderVM, const IInspectable& /*args*/) + { + for (uint32_t i = 0; i < _CommandList.Size(); i++) + { + if (_CommandList.GetAt(i) == senderVM) + { + CommandList().RemoveAt(i); + break; + } + } + _Settings.ActionMap().DeleteUserCommand(senderVM.ID()); + CurrentCommand(nullptr); + CurrentPage(ActionsSubPage::Base); + } + + void ActionsViewModel::_CmdVMPropagateColorSchemeRequestedHandler(const IInspectable& /*senderVM*/, const Editor::ArgWrapper& wrapper) + { + if (wrapper) + { + const auto schemes = _Settings.GlobalSettings().ColorSchemes(); + const auto defaultAppearanceSchemeName = _Settings.ProfileDefaults().DefaultAppearance().LightColorSchemeName(); + for (const auto [name, scheme] : schemes) + { + if (name == defaultAppearanceSchemeName) + { + const auto schemeVM = winrt::make(scheme, nullptr, _Settings); + wrapper.DefaultColorScheme(schemeVM); + } + } + } + } + + void ActionsViewModel::_CmdVMPropagateColorSchemeNamesRequestedHandler(const IInspectable& /*senderVM*/, const Editor::ArgWrapper& wrapper) + { + if (wrapper) + { + std::vector namesList; + const auto schemes = _Settings.GlobalSettings().ColorSchemes(); + for (const auto [name, _] : schemes) + { + namesList.emplace_back(name); + } + wrapper.ColorSchemeNamesList(winrt::single_threaded_vector(std::move(namesList))); + } + } + // Method Description: // - performs a search on KeyBindingList by key chord. // Arguments: @@ -397,4 +1579,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation kbdVM->ModifyKeyBindingRequested({ this, &ActionsViewModel::_KeyBindingViewModelModifyKeyBindingHandler }); kbdVM->IsAutomationPeerAttached(_AutomationPeerAttached); } + + void ActionsViewModel::_RegisterCmdVMEvents(com_ptr& cmdVM) + { + cmdVM->EditRequested({ this, &ActionsViewModel::_CmdVMEditRequestedHandler }); + cmdVM->DeleteRequested({ this, &ActionsViewModel::_CmdVMDeleteRequestedHandler }); + cmdVM->PropertyChanged({ this, &ActionsViewModel::_CmdVMPropertyChangedHandler }); + cmdVM->PropagateColorSchemeRequested({ this, &ActionsViewModel::_CmdVMPropagateColorSchemeRequestedHandler }); + cmdVM->PropagateColorSchemeNamesRequested({ this, &ActionsViewModel::_CmdVMPropagateColorSchemeNamesRequestedHandler }); + } } diff --git a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h index e78cd946fd8..1006a5cce49 100644 --- a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h @@ -4,8 +4,14 @@ #pragma once #include "ActionsViewModel.g.h" +#include "NavigateToCommandArgs.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 +25,29 @@ 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 NavigateToCommandArgs : NavigateToCommandArgsT + { + public: + NavigateToCommandArgs(CommandViewModel command, Editor::IHostedInWindow windowRoot) : + _Command(command), + _WindowRoot(windowRoot) {} + + Editor::IHostedInWindow WindowRoot() const noexcept { return _WindowRoot; } + Editor::CommandViewModel Command() const noexcept { return _Command; } + + private: + Editor::IHostedInWindow _WindowRoot; + Editor::CommandViewModel _Command{ nullptr }; + }; + struct ModifyKeyBindingEventArgs : ModifyKeyBindingEventArgsT { public: @@ -34,6 +63,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation WINRT_PROPERTY(hstring, NewActionName); }; + struct ModifyKeyChordEventArgs : ModifyKeyChordEventArgsT + { + 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, ViewModelHelper { public: @@ -99,32 +139,220 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation hstring _KeyChordText{}; }; + struct CommandViewModel : CommandViewModelT, ViewModelHelper + { + public: + CommandViewModel(winrt::Microsoft::Terminal::Settings::Model::Command cmd, + std::vector keyChordList, + const Editor::ActionsViewModel actionsPageVM, + const Windows::Foundation::Collections::IMap& 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 EditRequested; + + void Delete_Click(); + til::typed_event DeleteRequested; + + void AddKeybinding_Click(); + + til::typed_event PropagateColorSchemeRequested; + til::typed_event PropagateColorSchemeNamesRequested; + til::typed_event PropagateWindowRootRequested; + + VIEW_MODEL_OBSERVABLE_PROPERTY(IInspectable, ProposedShortcutAction); + VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::ActionArgsViewModel, ActionArgsVM, nullptr); + WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, AvailableShortcutActions, nullptr); + WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, KeyChordViewModelList, nullptr); + WINRT_PROPERTY(bool, IsNewCommand, false); + + private: + winrt::Microsoft::Terminal::Settings::Model::Command _command; + std::vector _keyChordList; + weak_ref _actionsPageVM{ nullptr }; + void _RegisterKeyChordVMEvents(Editor::KeyChordViewModel kcVM); + void _RegisterActionArgsVMEvents(Editor::ActionArgsViewModel actionArgsVM); + void _ReplaceCommandWithUserCopy(bool reinitialize); + void _CreateAndInitializeActionArgsVMHelper(); + Windows::Foundation::Collections::IMap _AvailableActionsAndNamesMap; + std::unordered_map _NameToActionMap; + }; + + struct ArgWrapper : ArgWrapperT, ViewModelHelper + { + public: + ArgWrapper(const winrt::hstring& name, const winrt::hstring& type, const bool required, const Model::ArgTag tag, const Windows::Foundation::IInspectable& value); + void Initialize(); + + winrt::hstring Name() const noexcept { return _name; }; + winrt::hstring Type() const noexcept { return _type; }; + Model::ArgTag Tag() const noexcept { return _tag; }; + 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 EnumList() const noexcept { return _EnumList; }; + Windows::Foundation::Collections::IObservableVector 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); + bool UnboxBool(const Windows::Foundation::IInspectable& value); + winrt::Windows::Foundation::IReference UnboxBoolOptional(const Windows::Foundation::IInspectable& value); + winrt::Windows::Foundation::IReference UnboxTerminalCoreColorOptional(const Windows::Foundation::IInspectable& value); + winrt::Windows::Foundation::IReference 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 newValue); + void TerminalCoreColorBindBack(const winrt::Windows::Foundation::IReference newValue); + void WindowsUIColorBindBack(const winrt::Windows::Foundation::IReference newValue); + + safe_void_coroutine Browse_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); + + // some argWrappers need to know additional information (like the default color scheme or the list of all color scheme names) + // to avoid populating all ArgWrappers with that information, instead we emit an event when we need that information + // (these events then get propagated up to the ActionsVM) and then the actionsVM will populate the value in us + // since there's an actionArgsVM above us and a commandVM above that, the event does get propagated through a few times but that's + // probably better than having every argWrapper contain the information by default + til::typed_event ColorSchemeRequested; + til::typed_event ColorSchemeNamesRequested; + til::typed_event WindowRootRequested; + + VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::ColorSchemeViewModel, DefaultColorScheme, nullptr); + VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::Foundation::IInspectable, Value, nullptr); + WINRT_PROPERTY(Windows::Foundation::Collections::IVector, ColorSchemeNamesList, nullptr); + WINRT_PROPERTY(Editor::IHostedInWindow, WindowRoot, nullptr); + + private: + winrt::hstring _name; + winrt::hstring _type; + Model::ArgTag _tag; + bool _required; + Windows::Foundation::IInspectable _EnumValue{ nullptr }; + Windows::Foundation::Collections::IObservableVector _EnumList; + Windows::Foundation::Collections::IObservableVector _FlagList; + }; + + struct ActionArgsViewModel : ActionArgsViewModelT, ViewModelHelper + { + public: + ActionArgsViewModel(const Microsoft::Terminal::Settings::Model::ActionAndArgs actionAndArgs); + void Initialize(); + + bool HasArgs() const noexcept; + void ReplaceActionAndArgs(Model::ActionAndArgs newActionAndArgs); + + til::typed_event WrapperValueChanged; + til::typed_event PropagateColorSchemeRequested; + til::typed_event PropagateColorSchemeNamesRequested; + til::typed_event PropagateWindowRootRequested; + + WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, ArgValues, nullptr); + + private: + Model::ActionAndArgs _actionAndArgs{ nullptr }; + }; + + struct KeyChordViewModel : KeyChordViewModelT, ViewModelHelper + { + 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 AddKeyChordRequested; + til::typed_event ModifyKeyChordRequested; + til::typed_event DeleteKeyChordRequested; + + private: + Control::KeyChord _currentKeys; + }; + struct ActionsViewModel : ActionsViewModelT, ViewModelHelper { 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 FocusContainer; til::typed_event UpdateBackground; WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, KeyBindingList); + WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, CommandList); + WINRT_OBSERVABLE_PROPERTY(ActionsSubPage, CurrentPage, _propertyChangedHandlers, ActionsSubPage::Base); private: + Editor::CommandViewModel _CurrentCommand{ nullptr }; bool _AutomationPeerAttached{ false }; Model::CascadiaSettings _Settings; Windows::Foundation::Collections::IObservableVector _AvailableActionAndArgs; Windows::Foundation::Collections::IMap _AvailableActionMap; + Windows::Foundation::Collections::IMap _AvailableActionsAndNamesMap; + + void _MakeCommandVMsHelper(); std::optional _GetContainerIndexByKeyChord(const Control::KeyChord& keys); void _RegisterEvents(com_ptr& kbdVM); + void _RegisterCmdVMEvents(com_ptr& 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); + void _CmdVMPropagateColorSchemeNamesRequestedHandler(const IInspectable& sender, const Editor::ArgWrapper& wrapper); }; } diff --git a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl index f091149d778..3d0c0c57c94 100644 --- a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl @@ -1,8 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import "EnumEntry.idl"; +import "ColorSchemeViewModel.idl"; +import "MainPage.idl"; + namespace Microsoft.Terminal.Settings.Editor { + runtimeclass NavigateToCommandArgs + { + CommandViewModel Command { get; }; + IHostedInWindow WindowRoot { get; }; + } + runtimeclass ModifyKeyBindingEventArgs { Microsoft.Terminal.Control.KeyChord OldKeys { get; }; @@ -11,6 +21,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 +62,136 @@ namespace Microsoft.Terminal.Settings.Editor event Windows.Foundation.TypedEventHandler DeleteKeyBindingRequested; } + runtimeclass CommandViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + // Settings Model side + String Name; + String ID; + Boolean IsUserAction { get; }; + // keybindings + IObservableVector KeyChordViewModelList { get; }; + // action args + ActionArgsViewModel ActionArgsVM { get; }; + + // View-model specific + String DisplayName { get; }; + + // UI side (command list page) + void Edit_Click(); + + // UI side (edit command page) + IObservableVector AvailableShortcutActions { get; }; + Object ProposedShortcutAction; + void Delete_Click(); + void AddKeybinding_Click(); + event Windows.Foundation.TypedEventHandler PropagateColorSchemeRequested; + event Windows.Foundation.TypedEventHandler PropagateColorSchemeNamesRequested; + event Windows.Foundation.TypedEventHandler PropagateWindowRootRequested; + } + + runtimeclass ArgWrapper : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + String Name { get; }; + String Type { get; }; + Microsoft.Terminal.Settings.Model.ArgTag Tag { get; }; + Boolean Required { get; }; + IInspectable Value; + IInspectable EnumValue; + Windows.Foundation.Collections.IObservableVector EnumList { get; }; + Windows.Foundation.Collections.IObservableVector FlagList { get; }; + ColorSchemeViewModel DefaultColorScheme; + Windows.Foundation.Collections.IVector ColorSchemeNamesList; + IHostedInWindow WindowRoot; + + // 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); + Boolean UnboxBool(Object value); + Windows.Foundation.IReference UnboxBoolOptional(Object value); + Windows.Foundation.IReference UnboxTerminalCoreColorOptional(Object value); + Windows.Foundation.IReference 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 newValue); + void TerminalCoreColorBindBack(Windows.Foundation.IReference newValue); + void WindowsUIColorBindBack(Windows.Foundation.IReference newValue); + + void Browse_Click(IInspectable sender, Windows.UI.Xaml.RoutedEventArgs args); + + event Windows.Foundation.TypedEventHandler ColorSchemeRequested; + event Windows.Foundation.TypedEventHandler ColorSchemeNamesRequested; + event Windows.Foundation.TypedEventHandler WindowRootRequested; + } + + runtimeclass ActionArgsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + Boolean HasArgs { get; }; + IObservableVector ArgValues; + event Windows.Foundation.TypedEventHandler WrapperValueChanged; + event Windows.Foundation.TypedEventHandler PropagateColorSchemeRequested; + event Windows.Foundation.TypedEventHandler PropagateColorSchemeNamesRequested; + event Windows.Foundation.TypedEventHandler PropagateWindowRootRequested; + } + + 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 AddKeyChordRequested; + event Windows.Foundation.TypedEventHandler ModifyKeyChordRequested; + event Windows.Foundation.TypedEventHandler 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 KeyBindingList { get; }; event Windows.Foundation.TypedEventHandler FocusContainer; event Windows.Foundation.TypedEventHandler 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 CommandList { get; }; + void CmdListItemClicked(IInspectable sender, Windows.UI.Xaml.Controls.ItemClickEventArgs args); } } diff --git a/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.cpp b/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.cpp new file mode 100644 index 00000000000..36b45710d91 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.cpp @@ -0,0 +1,117 @@ +// 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() }) + { + const auto argType = argWrapper.Type(); + if (argType == L"winrt::hstring") + { + // string has some special cases - check the tag + const auto argTag = argWrapper.Tag(); + if (argTag == Model::ArgTag::ColorScheme) + { + return ColorSchemeTemplate(); + } + else if (argTag == Model::ArgTag::FilePath) + { + return FilePickerTemplate(); + } + + // no special handling required, just return the normal string template + 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") + { + return BoolTemplate(); + } + else if (argType == L"Windows::Foundation::IReference") + { + return BoolOptionalTemplate(); + } + else if (argType == L"Windows::Foundation::IReference") + { + return Int32OptionalTemplate(); + } + else if (argType == L"Windows::Foundation::IReference") + { + 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" || + argType == L"Model::SplitDirection" || + argType == L"SplitType") + { + return EnumTemplate(); + } + else if (argType == L"SuggestionsSource") + { + return FlagTemplate(); + } + else if (argType == L"Windows::Foundation::IReference") + { + return TerminalCoreColorOptionalTemplate(); + } + else if (argType == L"Windows::Foundation::IReference") + { + return WindowsUIColorOptionalTemplate(); + } + else if (argType == L"Windows::Foundation::IReference") + { + return FlagTemplate(); + } + } + return NoArgTemplate(); + } +} diff --git a/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.h b/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.h new file mode 100644 index 00000000000..d570ac6b553 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.h @@ -0,0 +1,40 @@ +// 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() = 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, ColorSchemeTemplate); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, FilePickerTemplate); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, BoolTemplate); + 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); +} diff --git a/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.idl b/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.idl new file mode 100644 index 00000000000..658f82fa77e --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.idl @@ -0,0 +1,28 @@ +// 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 ColorSchemeTemplate; + Windows.UI.Xaml.DataTemplate FilePickerTemplate; + Windows.UI.Xaml.DataTemplate BoolTemplate; + 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; + } +} diff --git a/src/cascadia/TerminalSettingsEditor/EditAction.cpp b/src/cascadia/TerminalSettingsEditor/EditAction.cpp new file mode 100644 index 00000000000..8aa85b88d38 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/EditAction.cpp @@ -0,0 +1,56 @@ +// 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(); + _listItemTemplate = Resources().Lookup(winrt::box_value(L"ListItemTemplate")).try_as(); + } + + void EditAction::OnNavigatedTo(const NavigationEventArgs& e) + { + const auto args = e.Parameter().as(); + _ViewModel = args.Command(); + _windowRoot = args.WindowRoot(); + _ViewModel.PropagateWindowRootRequested([weakThis = get_weak()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) { + if (auto weak = weakThis.get()) + { + if (wrapper) + { + wrapper.WindowRoot(weak->_windowRoot); + } + } + }); + } + + 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(); + listViewItem.ContentTemplate(dataTemplate); + + args.ItemContainer(listViewItem); + } + + args.IsContainerPrepared(true); + } +} diff --git a/src/cascadia/TerminalSettingsEditor/EditAction.h b/src/cascadia/TerminalSettingsEditor/EditAction.h new file mode 100644 index 00000000000..a4c387736a0 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/EditAction.h @@ -0,0 +1,35 @@ +// 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, EditActionT + { + 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; // for Xaml to bind events + Editor::IHostedInWindow _windowRoot; + 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); +} diff --git a/src/cascadia/TerminalSettingsEditor/EditAction.idl b/src/cascadia/TerminalSettingsEditor/EditAction.idl new file mode 100644 index 00000000000..3e078ccb96e --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/EditAction.idl @@ -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; }; + } +} diff --git a/src/cascadia/TerminalSettingsEditor/EditAction.xaml b/src/cascadia/TerminalSettingsEditor/EditAction.xaml new file mode 100644 index 00000000000..304fe4bcbdf --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/EditAction.xaml @@ -0,0 +1,702 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 32 + 15 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/EnumEntry.h b/src/cascadia/TerminalSettingsEditor/EnumEntry.h index fe45a583961..fc67734954b 100644 --- a/src/cascadia/TerminalSettingsEditor/EnumEntry.h +++ b/src/cascadia/TerminalSettingsEditor/EnumEntry.h @@ -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 + struct FlagEntryComparator + { + bool operator()(const Editor::FlagEntry& lhs, const Editor::FlagEntry& rhs) const + { + return lhs.FlagValue().as() < rhs.FlagValue().as(); + } + }; + + template + struct FlagEntryReverseComparator + { + bool operator()(const Editor::FlagEntry& lhs, const Editor::FlagEntry& rhs) const + { + return lhs.FlagValue().as() > rhs.FlagValue().as(); + } + }; + + struct FlagEntry : FlagEntryT + { + 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); + }; } diff --git a/src/cascadia/TerminalSettingsEditor/EnumEntry.idl b/src/cascadia/TerminalSettingsEditor/EnumEntry.idl index ce92d75762a..c27e5ea6c3a 100644 --- a/src/cascadia/TerminalSettingsEditor/EnumEntry.idl +++ b/src/cascadia/TerminalSettingsEditor/EnumEntry.idl @@ -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; + } } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 6e66fac02a2..ad40accdbda 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -10,6 +10,7 @@ #include "Rendering.h" #include "RenderingViewModel.h" #include "Actions.h" +#include "NewActions.h" #include "ProfileViewModel.h" #include "GlobalAppearance.h" #include "GlobalAppearanceViewModel.h" @@ -44,6 +45,7 @@ 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 globalProfileTag{ L"GlobalProfile_Nav" }; static const std::wstring_view addProfileTag{ L"AddProfile" }; @@ -112,6 +114,24 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } }); + _actionsVM = winrt::make(_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(), winrt::make(_actionsVM.CurrentCommand(), *this)); + const auto crumb = winrt::make(box_value(newActionsTag), L"Edit Action...", BreadcrumbSubPage::Actions_Edit); + _breadcrumbs.Append(crumb); + } + else if (_actionsVM.CurrentPage() == ActionsSubPage::Base) + { + _Navigate(winrt::hstring{ newActionsTag }, BreadcrumbSubPage::None); + } + } + }); + // 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,6 +181,7 @@ 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); // We'll update the profile in the _profilesNavState whenever we actually navigate to one @@ -441,6 +462,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation const auto crumb = winrt::make(box_value(clickedItemTag), RS_(L"Nav_Actions/Content"), BreadcrumbSubPage::None); _breadcrumbs.Append(crumb); } + else if (clickedItemTag == newActionsTag) + { + const auto crumb = winrt::make(box_value(clickedItemTag), RS_(L"Nav_Actions/Content"), BreadcrumbSubPage::None); + _breadcrumbs.Append(crumb); + contentFrame().Navigate(xaml_typename(), _actionsVM); + + if (subPage == BreadcrumbSubPage::Actions_Edit && _actionsVM.CurrentCommand() != nullptr) + { + _actionsVM.CurrentPage(ActionsSubPage::Edit); + } + } else if (clickedItemTag == newTabMenuTag) { if (_newTabMenuPageVM.CurrentFolder()) diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.h b/src/cascadia/TerminalSettingsEditor/MainPage.h index 582f8813b1d..e50073a7c1b 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.h +++ b/src/cascadia/TerminalSettingsEditor/MainPage.h @@ -73,10 +73,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation 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 }; 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; }; } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.idl b/src/cascadia/TerminalSettingsEditor/MainPage.idl index 0f5a6e49b8f..e64a2762399 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.idl +++ b/src/cascadia/TerminalSettingsEditor/MainPage.idl @@ -20,6 +20,7 @@ namespace Microsoft.Terminal.Settings.Editor Profile_Terminal, Profile_Advanced, ColorSchemes_Edit, + Actions_Edit, NewTabMenu_Folder }; diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.xaml b/src/cascadia/TerminalSettingsEditor/MainPage.xaml index c6d36e8f200..492e1505b2f 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.xaml +++ b/src/cascadia/TerminalSettingsEditor/MainPage.xaml @@ -148,11 +148,18 @@ + + + + + + - - + + diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj index 918fa89e063..def3b6bb8a4 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj @@ -44,6 +44,16 @@ Actions.xaml + + NewActions.xaml + + + ArgsTemplateSelectors.idl + Code + + + EditAction.xaml + AddProfile.xaml @@ -163,6 +173,12 @@ Designer + + Designer + + + Designer + Designer @@ -229,6 +245,16 @@ Actions.xaml + + NewActions.xaml + + + ArgsTemplateSelectors.idl + Code + + + EditAction.xaml + AddProfile.xaml @@ -350,6 +376,17 @@ Actions.xaml Code + + NewActions.xaml + Code + + + Designer + + + EditAction.xaml + Code + AddProfile.xaml Code diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters index 97caa229d14..d601ac2eb11 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters @@ -29,6 +29,7 @@ + @@ -49,6 +50,8 @@ + + diff --git a/src/cascadia/TerminalSettingsEditor/NewActions.cpp b/src/cascadia/TerminalSettingsEditor/NewActions.cpp new file mode 100644 index 00000000000..e79193f7a8e --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/NewActions.cpp @@ -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(); + _ViewModel.CurrentPage(ActionsSubPage::Base); + } + + void NewActions::AddNew_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*eventArgs*/) + { + _ViewModel.AddNewCommand(); + } +} diff --git a/src/cascadia/TerminalSettingsEditor/NewActions.h b/src/cascadia/TerminalSettingsEditor/NewActions.h new file mode 100644 index 00000000000..43719cc8b21 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/NewActions.h @@ -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, NewActionsT + { + 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); +} diff --git a/src/cascadia/TerminalSettingsEditor/NewActions.idl b/src/cascadia/TerminalSettingsEditor/NewActions.idl new file mode 100644 index 00000000000..d9ce03da074 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/NewActions.idl @@ -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; }; + } +} diff --git a/src/cascadia/TerminalSettingsEditor/NewActions.xaml b/src/cascadia/TerminalSettingsEditor/NewActions.xaml new file mode 100644 index 00000000000..059b8dfe43f --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/NewActions.xaml @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index e1e96da8338..b6089e4646c 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -684,6 +684,10 @@ Actions Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + New Actions + Header for the "new actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + Background opacity Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -1784,6 +1788,38 @@ Delete the unfocused appearance for this profile. A description for what the delete unfocused appearance button does. + + Learn more about actions + Disclaimer presented at the top of the actions page to redirect the user to documentation regarding actions. + + + Delete action + Button label that deletes the selected action. + + + Action name + Label for the text box that edits the action name. + + + Action name + Placeholder text for the text box where the user can edit the action name. + + + Action type + Label for the combo box that edits the action type. + + + Additional arguments + Label for the list of editable arguments for the currently selected action. + + + Keybindings + Label for the list of editable keybindings for the current command. + + + Add keybinding + Button label that adds a keybinding to the current action. + Yes, delete key binding Button label that confirms deletion of a key binding entry. @@ -1792,6 +1828,14 @@ Are you sure you want to delete this key binding? Confirmation message displayed when the user attempts to delete a key binding entry. + + Yes, delete action + Button label that confirms deletion of an action. + + + Are you sure you want to delete this action? + Confirmation message displayed when the user attempts to delete an action. + Invalid key chord. Please enter a valid key chord. Error message displayed when an invalid key chord is input by the user. @@ -1836,6 +1880,278 @@ Action Label for a control that sets the action of a key binding. + + Null (use global setting) + An option to choose from for nullable enums. Clears the enum value. + + + HTML + An option to choose from for the "copy format". Copies content in HTML format. + + + RTF + An option to choose from for the "copy format". Copies content in Rich Text Format (RTF). + + + Automatic + An option to choose from for the "split direction". Automatically determines the split direction. + + + Up + An option to choose from for the "split direction". Splits upward. + + + Right + An option to choose from for the "split direction". Splits to the right. + + + Down + An option to choose from for the "split direction". Splits downward. + + + Left + An option to choose from for the "split direction". Splits to the left. + + + Vertical + An option to choose from for the "split direction". Splits to the left. + + + Horizontal + An option to choose from for the "split direction". Splits to the left. + + + Manual + An option to choose from for the "split type". Creates a manual split. + + + Duplicate + An option to choose from for the "split type". Creates a split by duplicating the current session. + + + None + An option to choose from for the "resize direction". None option. + + + Left + An option to choose from for the "resize direction". Left option. + + + Right + An option to choose from for the "resize direction". Right option. + + + Up + An option to choose from for the "resize direction". Up option. + + + Down + An option to choose from for the "resize direction". Down option. + + + None + An option to choose from for the "focus direction". None option. + + + Left + An option to choose from for the "focus direction". Left option. + + + Right + An option to choose from for the "focus direction". Right option. + + + Up + An option to choose from for the "focus direction". Up option. + + + Down + An option to choose from for the "focus direction". Down option. + + + Previous + An option to choose from for the "focus direction". Previous option. + + + Previous In Order + An option to choose from for the "focus direction". Previous in order option. + + + Next In Order + An option to choose from for the "focus direction". Next in order option. + + + First + An option to choose from for the "focus direction". First option. + + + Parent + An option to choose from for the "focus direction". Parent option. + + + Child + An option to choose from for the "focus direction". Child option. + + + Settings File + An option to choose from for the "settings target". Targets the settings file. + + + Defaults File + An option to choose from for the "settings target". Targets the defaults file. + + + All Files + An option to choose from for the "settings target". Targets all files. + + + Settings UI + An option to choose from for the "settings target". Targets the settings UI. + + + Directory + An option to choose from for the "settings target". Targets the directory. + + + None + An option to choose from for the "move tab direction". No movement. + + + Forward + An option to choose from for the "move tab direction". Moves the tab forward. + + + Backward + An option to choose from for the "move tab direction". Moves the tab backward. + + + Previous + An option to choose from for the "scroll to mark direction". Scrolls to the previous mark. + + + Next + An option to choose from for the "scroll to mark direction". Scrolls to the next mark. + + + First + An option to choose from for the "scroll to mark direction". Scrolls to the first mark. + + + Last + An option to choose from for the "scroll to mark direction". Scrolls to the last mark. + + + Action + An option to choose from for the "command palette launch mode". Launches in action mode. + + + Command Line + An option to choose from for the "command palette launch mode". Launches in command line mode. + + + None + An option to choose from for the "suggestions source". No suggestions source. + + + Tasks + An option to choose from for the "suggestions source". Suggestions come from tasks. + + + Snippets + An option to choose from for the "suggestions source". Suggestions come from snippets. + + + Command History + An option to choose from for the "suggestions source". Suggestions come from command history. + + + Directory History + An option to choose from for the "suggestions source". Suggestions come from directory history. + + + Quick Fixes + An option to choose from for the "suggestions source". Suggestions come from quick fixes. + + + All + An option to choose from for the "suggestions source". Includes all suggestion sources. + + + None + An option to choose from for the "find match direction". No direction selected. + + + Next + An option to choose from for the "find match direction". Finds the next match. + + + Previous + An option to choose from for the "find match direction". Finds the previous match. + + + Any + An option to choose from for the "desktop behavior". Applies to any desktop. + + + To Current + An option to choose from for the "desktop behavior". Moves to the current desktop. + + + On Current + An option to choose from for the "desktop behavior". Stays on the current desktop. + + + Any + An option to choose from for the "monitor behavior". Applies to any monitor. + + + To Current + An option to choose from for the "monitor behavior". Moves to the current monitor. + + + To Mouse + An option to choose from for the "monitor behavior". Moves to the monitor where the mouse is located. + + + Screen + An option to choose from for the "clear buffer type". Clears only the screen. + + + Scrollback + An option to choose from for the "clear buffer type". Clears only the scrollback buffer. + + + All + An option to choose from for the "clear buffer type". Clears both the screen and the scrollback buffer. + + + Previous + An option to choose from for the "select output direction". Selects the previous output. + + + Next + An option to choose from for the "select output direction". Selects the next output. + + + Most Recently Used + An option to choose from for the "tab switcher mode". Switches tabs based on most recently used order. + + + In Order + An option to choose from for the "tab switcher mode". Switches tabs in sequential order. + + + Disabled + An option to choose from for the "tab switcher mode". Disables tab switching. + + + No color + Label for a button directing the user to opt out of choosing a color. + + + Browse... + Button label that opens a file picker in a new window. The "..." is standard to mean it will open a new window. + Input your desired keyboard shortcut. Help text directing users how to use the "KeyChordListener" control. Pressing a keyboard shortcut will be recorded by this control. diff --git a/src/cascadia/UIHelpers/Converters.cpp b/src/cascadia/UIHelpers/Converters.cpp index 8c0cffb8af8..3c364c0a1b6 100644 --- a/src/cascadia/UIHelpers/Converters.cpp +++ b/src/cascadia/UIHelpers/Converters.cpp @@ -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; + } } diff --git a/src/cascadia/UIHelpers/Converters.h b/src/cascadia/UIHelpers/Converters.h index 998daad41f2..af8bcecb458 100644 --- a/src/cascadia/UIHelpers/Converters.h +++ b/src/cascadia/UIHelpers/Converters.h @@ -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); }; } diff --git a/src/cascadia/UIHelpers/Converters.idl b/src/cascadia/UIHelpers/Converters.idl index 5cd5b553257..baf8a97af65 100644 --- a/src/cascadia/UIHelpers/Converters.idl +++ b/src/cascadia/UIHelpers/Converters.idl @@ -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); } } From ce8b31b82540495232c95f9e5d66b647a302abbb Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 14 May 2025 16:44:07 -0700 Subject: [PATCH 03/85] use x-macro here instead --- .../CascadiaSettings.cpp | 90 ++----------------- 1 file changed, 8 insertions(+), 82 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index 95a1787e654..a7733e13ae8 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -1035,88 +1035,14 @@ Model::IActionArgs CascadiaSettings::GetEmptyArgsForAction(Model::ShortcutAction { switch (shortcutAction) { - case Model::ShortcutAction::CopyText: - return winrt::make(); - case Model::ShortcutAction::MovePane: - return winrt::make(); - case Model::ShortcutAction::SwitchToTab: - return winrt::make(); - case Model::ShortcutAction::ResizePane: - return winrt::make(); - case Model::ShortcutAction::MoveFocus: - return winrt::make(); - case Model::ShortcutAction::SwapPane: - return winrt::make(); - case Model::ShortcutAction::AdjustFontSize: - return winrt::make(); - case Model::ShortcutAction::SendInput: - return winrt::make(); - case Model::ShortcutAction::OpenSettings: - return winrt::make(); - case Model::ShortcutAction::SetFocusMode: - return winrt::make(); - case Model::ShortcutAction::SetFullScreen: - return winrt::make(); - case Model::ShortcutAction::SetMaximized: - return winrt::make(); - case Model::ShortcutAction::SetColorScheme: - return winrt::make(); - case Model::ShortcutAction::RenameTab: - return winrt::make(); - case Model::ShortcutAction::ExecuteCommandline: - return winrt::make(); - case Model::ShortcutAction::CloseOtherTabs: - return winrt::make(); - case Model::ShortcutAction::CloseTabsAfter: - return winrt::make(); - case Model::ShortcutAction::CloseTab: - return winrt::make(); - case Model::ShortcutAction::MoveTab: - return winrt::make(); - case Model::ShortcutAction::ScrollUp: - return winrt::make(); - case Model::ShortcutAction::ScrollDown: - return winrt::make(); - case Model::ShortcutAction::ScrollToMark: - return winrt::make(); - case Model::ShortcutAction::ToggleCommandPalette: - return winrt::make(); - case Model::ShortcutAction::Suggestions: - return winrt::make(); - case Model::ShortcutAction::FindMatch: - return winrt::make(); - case Model::ShortcutAction::RenameWindow: - return winrt::make(); - case Model::ShortcutAction::SearchForText: - return winrt::make(); - case Model::ShortcutAction::GlobalSummon: - return winrt::make(); - case Model::ShortcutAction::FocusPane: - return winrt::make(); - case Model::ShortcutAction::ExportBuffer: - return winrt::make(); - case Model::ShortcutAction::ClearBuffer: - return winrt::make(); - case Model::ShortcutAction::AdjustOpacity: - return winrt::make(); - case Model::ShortcutAction::SelectCommand: - return winrt::make(); - case Model::ShortcutAction::SelectOutput: - return winrt::make(); - case Model::ShortcutAction::AddMark: - return winrt::make(); - case Model::ShortcutAction::SetTabColor: - return winrt::make(); - case Model::ShortcutAction::PrevTab: - return winrt::make(); - case Model::ShortcutAction::NextTab: - return winrt::make(); - case Model::ShortcutAction::NewTab: - return winrt::make(); - case Model::ShortcutAction::NewWindow: - return winrt::make(); - case Model::ShortcutAction::SplitPane: - return winrt::make(); +#define ON_ALL_ACTIONS_WITH_ARGS(name) \ + case Model::ShortcutAction::name: \ + return winrt::make(); + + ALL_SHORTCUT_ACTIONS_WITH_ARGS + +#undef ON_ALL_ACTIONS_WITH_ARGS + default: return nullptr; } From 63ec8684553df83c766f36eb25d4fe34e8ef37e3 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 14 May 2025 17:32:31 -0700 Subject: [PATCH 04/85] remove old actions page --- .../TerminalSettingsEditor/Actions.cpp | 41 +--- .../TerminalSettingsEditor/Actions.xaml | 179 ++------------- .../TerminalSettingsEditor/MainPage.cpp | 14 +- .../TerminalSettingsEditor/MainPage.xaml | 11 +- ...Microsoft.Terminal.Settings.Editor.vcxproj | 13 -- ...t.Terminal.Settings.Editor.vcxproj.filters | 1 - .../TerminalSettingsEditor/NewActions.cpp | 38 ---- .../TerminalSettingsEditor/NewActions.h | 31 --- .../TerminalSettingsEditor/NewActions.idl | 13 -- .../TerminalSettingsEditor/NewActions.xaml | 212 ------------------ .../Resources/en-US/Resources.resw | 4 - 11 files changed, 26 insertions(+), 531 deletions(-) delete mode 100644 src/cascadia/TerminalSettingsEditor/NewActions.cpp delete mode 100644 src/cascadia/TerminalSettingsEditor/NewActions.h delete mode 100644 src/cascadia/TerminalSettingsEditor/NewActions.idl delete mode 100644 src/cascadia/TerminalSettingsEditor/NewActions.xaml diff --git a/src/cascadia/TerminalSettingsEditor/Actions.cpp b/src/cascadia/TerminalSettingsEditor/Actions.cpp index dafc7b51da2..996a35238bc 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.cpp +++ b/src/cascadia/TerminalSettingsEditor/Actions.cpp @@ -28,48 +28,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void Actions::OnNavigatedTo(const NavigationEventArgs& e) { _ViewModel = e.Parameter().as(); - - // Subscribe to the view model's FocusContainer event. - // Use the KeyBindingViewModel or index provided in the event to focus the corresponding container - _ViewModel.FocusContainer([this](const auto& /*sender*/, const auto& args) { - if (auto kbdVM{ args.try_as() }) - { - if (const auto& container = KeyBindingsListView().ContainerFromItem(*kbdVM)) - { - container.as().Focus(FocusState::Programmatic); - } - } - else if (const auto& index = args.try_as()) - { - if (const auto& container = KeyBindingsListView().ContainerFromIndex(*index)) - { - container.as().Focus(FocusState::Programmatic); - } - } - }); - - // Subscribe to the view model's UpdateBackground event. - // The view model does not have access to the page resources, so it asks us - // to update the key binding's container background - _ViewModel.UpdateBackground([this](const auto& /*sender*/, const auto& args) { - if (auto kbdVM{ args.try_as() }) - { - if (kbdVM->IsInEditMode()) - { - const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackgroundEditing")).as() }; - kbdVM->ContainerBackground(containerBackground); - } - else - { - const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackground")).as() }; - kbdVM->ContainerBackground(containerBackground); - } - } - }); + _ViewModel.CurrentPage(ActionsSubPage::Base); } void Actions::AddNew_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*eventArgs*/) { - _ViewModel.AddNewKeybinding(); + _ViewModel.AddNewCommand(); } } diff --git a/src/cascadia/TerminalSettingsEditor/Actions.xaml b/src/cascadia/TerminalSettingsEditor/Actions.xaml index 5eb09daf421..4e30743f211 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Actions.xaml @@ -122,7 +122,7 @@ @@ -137,24 +137,6 @@ - 32 - 15 - - - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Text="{x:Bind DisplayName, Mode=OneWay}" /> @@ -330,6 +180,13 @@ HorizontalAlignment="Left" Spacing="8" Style="{StaticResource SettingsStackStyle}"> + + + + + - + diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index ad40accdbda..bf23b8ffd0b 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -10,7 +10,6 @@ #include "Rendering.h" #include "RenderingViewModel.h" #include "Actions.h" -#include "NewActions.h" #include "ProfileViewModel.h" #include "GlobalAppearance.h" #include "GlobalAppearanceViewModel.h" @@ -45,7 +44,6 @@ 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 globalProfileTag{ L"GlobalProfile_Nav" }; static const std::wstring_view addProfileTag{ L"AddProfile" }; @@ -122,12 +120,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation if (_actionsVM.CurrentPage() == ActionsSubPage::Edit) { contentFrame().Navigate(xaml_typename(), winrt::make(_actionsVM.CurrentCommand(), *this)); - const auto crumb = winrt::make(box_value(newActionsTag), L"Edit Action...", BreadcrumbSubPage::Actions_Edit); + const auto crumb = winrt::make(box_value(actionsTag), L"Edit Action...", BreadcrumbSubPage::Actions_Edit); _breadcrumbs.Append(crumb); } else if (_actionsVM.CurrentPage() == ActionsSubPage::Base) { - _Navigate(winrt::hstring{ newActionsTag }, BreadcrumbSubPage::None); + _Navigate(winrt::hstring{ actionsTag }, BreadcrumbSubPage::None); } } }); @@ -458,15 +456,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } else if (clickedItemTag == actionsTag) { - contentFrame().Navigate(xaml_typename(), winrt::make(_settingsClone)); const auto crumb = winrt::make(box_value(clickedItemTag), RS_(L"Nav_Actions/Content"), BreadcrumbSubPage::None); _breadcrumbs.Append(crumb); - } - else if (clickedItemTag == newActionsTag) - { - const auto crumb = winrt::make(box_value(clickedItemTag), RS_(L"Nav_Actions/Content"), BreadcrumbSubPage::None); - _breadcrumbs.Append(crumb); - contentFrame().Navigate(xaml_typename(), _actionsVM); + contentFrame().Navigate(xaml_typename(), _actionsVM); if (subPage == BreadcrumbSubPage::Actions_Edit && _actionsVM.CurrentCommand() != nullptr) { diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.xaml b/src/cascadia/TerminalSettingsEditor/MainPage.xaml index 492e1505b2f..fe01cc0f4b7 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.xaml +++ b/src/cascadia/TerminalSettingsEditor/MainPage.xaml @@ -147,19 +147,12 @@ - - - - - - - - + + diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj index def3b6bb8a4..5b1369c2da8 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj @@ -44,9 +44,6 @@ Actions.xaml - - NewActions.xaml - ArgsTemplateSelectors.idl Code @@ -173,9 +170,6 @@ Designer - - Designer - Designer @@ -245,9 +239,6 @@ Actions.xaml - - NewActions.xaml - ArgsTemplateSelectors.idl Code @@ -376,10 +367,6 @@ Actions.xaml Code - - NewActions.xaml - Code - Designer diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters index d601ac2eb11..d1e9dba07d4 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters @@ -50,7 +50,6 @@ - diff --git a/src/cascadia/TerminalSettingsEditor/NewActions.cpp b/src/cascadia/TerminalSettingsEditor/NewActions.cpp deleted file mode 100644 index e79193f7a8e..00000000000 --- a/src/cascadia/TerminalSettingsEditor/NewActions.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// 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(); - _ViewModel.CurrentPage(ActionsSubPage::Base); - } - - void NewActions::AddNew_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*eventArgs*/) - { - _ViewModel.AddNewCommand(); - } -} diff --git a/src/cascadia/TerminalSettingsEditor/NewActions.h b/src/cascadia/TerminalSettingsEditor/NewActions.h deleted file mode 100644 index 43719cc8b21..00000000000 --- a/src/cascadia/TerminalSettingsEditor/NewActions.h +++ /dev/null @@ -1,31 +0,0 @@ -// 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, NewActionsT - { - 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); -} diff --git a/src/cascadia/TerminalSettingsEditor/NewActions.idl b/src/cascadia/TerminalSettingsEditor/NewActions.idl deleted file mode 100644 index d9ce03da074..00000000000 --- a/src/cascadia/TerminalSettingsEditor/NewActions.idl +++ /dev/null @@ -1,13 +0,0 @@ -// 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; }; - } -} diff --git a/src/cascadia/TerminalSettingsEditor/NewActions.xaml b/src/cascadia/TerminalSettingsEditor/NewActions.xaml deleted file mode 100644 index 059b8dfe43f..00000000000 --- a/src/cascadia/TerminalSettingsEditor/NewActions.xaml +++ /dev/null @@ -1,212 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index b6089e4646c..1e3d6fe5710 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -684,10 +684,6 @@ Actions Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. - - New Actions - Header for the "new actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. - Background opacity Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. From 8d0f947e940364bb88081ae8744ee8e4af5c8050 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 14 May 2025 17:59:18 -0700 Subject: [PATCH 05/85] remove old stuff --- .../TerminalSettingsEditor/Actions.cpp | 6 - src/cascadia/TerminalSettingsEditor/Actions.h | 1 - .../ActionsViewModel.cpp | 376 +----------------- .../TerminalSettingsEditor/ActionsViewModel.h | 107 ----- .../ActionsViewModel.idl | 49 --- 5 files changed, 1 insertion(+), 538 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Actions.cpp b/src/cascadia/TerminalSettingsEditor/Actions.cpp index 996a35238bc..65a281ffd87 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.cpp +++ b/src/cascadia/TerminalSettingsEditor/Actions.cpp @@ -19,12 +19,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Automation::AutomationProperties::SetName(AddNewButton(), RS_(L"Actions_AddNewTextBlock/Text")); } - Automation::Peers::AutomationPeer Actions::OnCreateAutomationPeer() - { - _ViewModel.OnAutomationPeerAttached(); - return nullptr; - } - void Actions::OnNavigatedTo(const NavigationEventArgs& e) { _ViewModel = e.Parameter().as(); diff --git a/src/cascadia/TerminalSettingsEditor/Actions.h b/src/cascadia/TerminalSettingsEditor/Actions.h index a79003afbfb..8feb4ab7d0d 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.h +++ b/src/cascadia/TerminalSettingsEditor/Actions.h @@ -16,7 +16,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Actions(); 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); diff --git a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp index fef3c93ac83..cf2685766d5 100644 --- a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp @@ -4,7 +4,6 @@ #include "pch.h" #include "ActionsViewModel.h" #include "ActionsViewModel.g.cpp" -#include "KeyBindingViewModel.g.cpp" #include "CommandViewModel.g.cpp" #include "ArgWrapper.g.cpp" #include "ActionArgsViewModel.g.cpp" @@ -13,7 +12,6 @@ #include "../TerminalSettingsModel/AllShortcutActions.h" #include "EnumEntry.h" #include "ColorSchemeViewModel.h" -#include #include "..\WinRTUtils\inc\Utils.h" using namespace winrt::Windows::Foundation; @@ -195,91 +193,6 @@ inline const std::set& availableActions) : - KeyBindingViewModel(nullptr, availableActions.First().Current(), availableActions) {} - - KeyBindingViewModel::KeyBindingViewModel(const Control::KeyChord& keys, const hstring& actionName, const IObservableVector& availableActions) : - _CurrentKeys{ keys }, - _KeyChordText{ KeyChordSerialization::ToString(keys) }, - _CurrentAction{ actionName }, - _ProposedAction{ box_value(actionName) }, - _AvailableActions{ availableActions } - { - // Add a property changed handler to our own property changed event. - // This propagates changes from the settings model to anybody listening to our - // unique view model members. - PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) { - const auto viewModelProperty{ args.PropertyName() }; - if (viewModelProperty == L"CurrentKeys") - { - _KeyChordText = KeyChordSerialization::ToString(_CurrentKeys); - _NotifyChanges(L"KeyChordText"); - } - else if (viewModelProperty == L"IsContainerFocused" || - viewModelProperty == L"IsEditButtonFocused" || - viewModelProperty == L"IsHovered" || - viewModelProperty == L"IsAutomationPeerAttached" || - viewModelProperty == L"IsInEditMode") - { - _NotifyChanges(L"ShowEditButton"); - } - else if (viewModelProperty == L"CurrentAction") - { - _NotifyChanges(L"Name"); - } - }); - } - - hstring KeyBindingViewModel::EditButtonName() const noexcept { return RS_(L"Actions_EditButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } - hstring KeyBindingViewModel::CancelButtonName() const noexcept { return RS_(L"Actions_CancelButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } - hstring KeyBindingViewModel::AcceptButtonName() const noexcept { return RS_(L"Actions_AcceptButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } - hstring KeyBindingViewModel::DeleteButtonName() const noexcept { return RS_(L"Actions_DeleteButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } - - bool KeyBindingViewModel::ShowEditButton() const noexcept - { - return (IsContainerFocused() || IsEditButtonFocused() || IsHovered() || IsAutomationPeerAttached()) && !IsInEditMode(); - } - - void KeyBindingViewModel::ToggleEditMode() - { - // toggle edit mode - IsInEditMode(!_IsInEditMode); - if (_IsInEditMode) - { - // if we're in edit mode, - // - prepopulate the text box with the current keys - // - reset the combo box with the current action - ProposedKeys(_CurrentKeys); - ProposedAction(box_value(_CurrentAction)); - } - } - - void KeyBindingViewModel::AttemptAcceptChanges() - { - AttemptAcceptChanges(_ProposedKeys); - } - - void KeyBindingViewModel::AttemptAcceptChanges(const Control::KeyChord newKeys) - { - const auto args{ make_self(_CurrentKeys, // OldKeys - newKeys, // NewKeys - _IsNewlyAdded ? hstring{} : _CurrentAction, // OldAction - unbox_value(_ProposedAction)) }; // NewAction - ModifyKeyBindingRequested.raise(*this, *args); - } - - void KeyBindingViewModel::CancelChanges() - { - if (_IsNewlyAdded) - { - DeleteNewlyAddedKeyBinding.raise(*this, nullptr); - } - else - { - ToggleEditMode(); - } - } - CommandViewModel::CommandViewModel(Command cmd, std::vector keyChordList, const Editor::ActionsViewModel actionsPageVM, const Windows::Foundation::Collections::IMap& availableShortcutActionsAndNames) : _command{ cmd }, _keyChordList{ keyChordList }, @@ -1080,35 +993,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void ActionsViewModel::_MakeCommandVMsHelper() { - // Populate AvailableActionAndArgs - _AvailableActionMap = single_threaded_map(); - std::vector availableActionAndArgs; - for (const auto& [name, actionAndArgs] : _Settings.ActionMap().AvailableActions()) - { - availableActionAndArgs.push_back(name); - _AvailableActionMap.Insert(name, actionAndArgs); - } - std::sort(begin(availableActionAndArgs), end(availableActionAndArgs)); - _AvailableActionAndArgs = single_threaded_observable_vector(std::move(availableActionAndArgs)); - + // Populate AvailableActionsAndNames _AvailableActionsAndNamesMap = Model::CascadiaSettings::AvailableShortcutActionsAndNames(); for (const auto unimplemented : UnimplementedShortcutActions) { _AvailableActionsAndNamesMap.Remove(unimplemented); } - // Convert the key bindings from our settings into a view model representation - const auto& keyBindingMap{ _Settings.ActionMap().KeyBindings() }; - std::vector keyBindingList; - keyBindingList.reserve(keyBindingMap.Size()); - for (const auto& [keys, cmd] : keyBindingMap) - { - // convert the cmd into a KeyBindingViewModel - auto container{ make_self(keys, cmd.Name(), _AvailableActionAndArgs) }; - _RegisterEvents(container); - keyBindingList.push_back(*container); - } - const auto& allCommands{ _Settings.ActionMap().AllCommands() }; std::vector commandList; commandList.reserve(allCommands.Size()); @@ -1128,44 +1019,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - std::sort(begin(keyBindingList), end(keyBindingList), KeyBindingViewModelComparator{}); - _KeyBindingList = single_threaded_observable_vector(std::move(keyBindingList)); std::sort(begin(commandList), end(commandList), CommandViewModelComparator{}); _CommandList = single_threaded_observable_vector(std::move(commandList)); } - void ActionsViewModel::OnAutomationPeerAttached() - { - _AutomationPeerAttached = true; - for (const auto& kbdVM : _KeyBindingList) - { - // To create a more accessible experience, we want the "edit" buttons to _always_ - // appear when a screen reader is attached. This ensures that the edit buttons are - // accessible via the UIA tree. - get_self(kbdVM)->IsAutomationPeerAttached(_AutomationPeerAttached); - } - } - - void ActionsViewModel::AddNewKeybinding() - { - // Create the new key binding and register all of the event handlers. - auto kbdVM{ make_self(_AvailableActionAndArgs) }; - _RegisterEvents(kbdVM); - kbdVM->DeleteNewlyAddedKeyBinding({ this, &ActionsViewModel::_KeyBindingViewModelDeleteNewlyAddedKeyBindingHandler }); - - // Manually add the editing background. This needs to be done in Actions not the view model. - // We also have to do this manually because it hasn't been added to the list yet. - kbdVM->IsInEditMode(true); - // Emit an event to let the page know to update the background of this key binding VM - UpdateBackground.raise(*this, *kbdVM); - - // IMPORTANT: do this _after_ setting IsInEditMode. Otherwise, it'll get deleted immediately - // by the PropertyChangedHandler below (where we delete any IsNewlyAdded items) - kbdVM->IsNewlyAdded(true); - _KeyBindingList.InsertAt(0, *kbdVM); - FocusContainer.raise(*this, *kbdVM); - } - void ActionsViewModel::AddNewCommand() { const auto newCmd = Model::Command::NewUserCommand(); @@ -1288,52 +1145,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _Settings.ActionMap().AddAction(newCommand, nullptr); } - void ActionsViewModel::_KeyBindingViewModelPropertyChangedHandler(const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) - { - const auto senderVM{ sender.as() }; - const auto propertyName{ args.PropertyName() }; - if (propertyName == L"IsInEditMode") - { - if (senderVM.IsInEditMode()) - { - // Ensure that... - // 1. we move focus to the edit mode controls - // 2. any actions that were newly added are removed - // 3. this is the only entry that is in edit mode - for (int32_t i = _KeyBindingList.Size() - 1; i >= 0; --i) - { - const auto& kbdVM{ _KeyBindingList.GetAt(i) }; - if (senderVM == kbdVM) - { - // This is the view model entry that went into edit mode. - // Emit an event to let the page know to move focus to - // this VM's container. - FocusContainer.raise(*this, senderVM); - } - else if (kbdVM.IsNewlyAdded()) - { - // Remove any actions that were newly added - _KeyBindingList.RemoveAt(i); - } - else - { - // Exit edit mode for all other containers - get_self(kbdVM)->DisableEditMode(); - } - } - } - else - { - // Emit an event to let the page know to move focus to - // this VM's container. - FocusContainer.raise(*this, senderVM); - } - - // Emit an event to let the page know to update the background of this key binding VM - UpdateBackground.raise(*this, senderVM); - } - } - void ActionsViewModel::_CmdVMPropertyChangedHandler(const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) { const auto senderVM{ sender.as() }; @@ -1343,159 +1154,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - void ActionsViewModel::_KeyBindingViewModelDeleteKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Control::KeyChord& keys) - { - // Update the settings model - _Settings.ActionMap().DeleteKeyBinding(keys); - - // Find the current container in our list and remove it. - // This is much faster than rebuilding the entire ActionMap. - uint32_t index; - if (_KeyBindingList.IndexOf(senderVM, index)) - { - _KeyBindingList.RemoveAt(index); - - // Focus the new item at this index - if (_KeyBindingList.Size() != 0) - { - const auto newFocusedIndex{ std::clamp(index, 0u, _KeyBindingList.Size() - 1) }; - // Emit an event to let the page know to move focus to - // this VM's container. - FocusContainer.raise(*this, winrt::box_value(newFocusedIndex)); - } - } - } - - void ActionsViewModel::_KeyBindingViewModelModifyKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Editor::ModifyKeyBindingEventArgs& args) - { - const auto isNewAction{ !args.OldKeys() && args.OldActionName().empty() }; - - auto applyChangesToSettingsModel = [=]() { - // If the key chord was changed, - // update the settings model and view model appropriately - // NOTE: we still need to update the view model if we're working with a newly added action - if (isNewAction || args.OldKeys().Modifiers() != args.NewKeys().Modifiers() || args.OldKeys().Vkey() != args.NewKeys().Vkey()) - { - if (!isNewAction) - { - // update settings model - _Settings.ActionMap().RebindKeys(args.OldKeys(), args.NewKeys()); - } - - // update view model - auto senderVMImpl{ get_self(senderVM) }; - senderVMImpl->CurrentKeys(args.NewKeys()); - } - - // If the action was changed, - // update the settings model and view model appropriately - // NOTE: no need to check for "isNewAction" here. != already. - if (args.OldActionName() != args.NewActionName()) - { - // convert the action's name into a view model. - const auto& newAction{ _AvailableActionMap.Lookup(args.NewActionName()) }; - - // update settings model - _Settings.ActionMap().RegisterKeyBinding(args.NewKeys(), newAction); - - // update view model - auto senderVMImpl{ get_self(senderVM) }; - senderVMImpl->CurrentAction(args.NewActionName()); - senderVMImpl->IsNewlyAdded(false); - } - }; - - // Check for this special case: - // we're changing the key chord, - // but the new key chord is already in use - bool conflictFound{ false }; - if (isNewAction || args.OldKeys().Modifiers() != args.NewKeys().Modifiers() || args.OldKeys().Vkey() != args.NewKeys().Vkey()) - { - const auto& conflictingCmd{ _Settings.ActionMap().GetActionByKeyChord(args.NewKeys()) }; - if (conflictingCmd) - { - conflictFound = true; - // We're about to overwrite another key chord. - // Display a confirmation dialog. - TextBlock errorMessageTB{}; - errorMessageTB.Text(RS_(L"Actions_RenameConflictConfirmationMessage")); - errorMessageTB.TextWrapping(TextWrapping::Wrap); - - const auto conflictingCmdName{ conflictingCmd.Name() }; - TextBlock conflictingCommandNameTB{}; - conflictingCommandNameTB.Text(fmt::format(L"\"{}\"", conflictingCmdName.empty() ? RS_(L"Actions_UnnamedCommandName") : conflictingCmdName)); - conflictingCommandNameTB.FontStyle(Windows::UI::Text::FontStyle::Italic); - conflictingCommandNameTB.TextWrapping(TextWrapping::Wrap); - - TextBlock confirmationQuestionTB{}; - confirmationQuestionTB.Text(RS_(L"Actions_RenameConflictConfirmationQuestion")); - confirmationQuestionTB.TextWrapping(TextWrapping::Wrap); - - Button acceptBTN{}; - acceptBTN.Content(box_value(RS_(L"Actions_RenameConflictConfirmationAcceptButton"))); - acceptBTN.Click([=](auto&, auto&) { - // remove conflicting key binding from list view - const auto containerIndex{ _GetContainerIndexByKeyChord(args.NewKeys()) }; - _KeyBindingList.RemoveAt(*containerIndex); - - // remove flyout - senderVM.AcceptChangesFlyout().Hide(); - senderVM.AcceptChangesFlyout(nullptr); - - // update settings model and view model - applyChangesToSettingsModel(); - senderVM.ToggleEditMode(); - }); - - StackPanel flyoutStack{}; - flyoutStack.Children().Append(errorMessageTB); - flyoutStack.Children().Append(conflictingCommandNameTB); - flyoutStack.Children().Append(confirmationQuestionTB); - flyoutStack.Children().Append(acceptBTN); - - // This should match CustomFlyoutPresenterStyle in CommonResources.xaml! - // We don't have access to those resources here, so it's easier to just copy them over. - // This allows the flyout text to wrap - Style acceptChangesFlyoutStyle{ winrt::xaml_typename() }; - Setter horizontalScrollModeStyleSetter{ ScrollViewer::HorizontalScrollModeProperty(), box_value(ScrollMode::Disabled) }; - Setter horizontalScrollBarVisibilityStyleSetter{ ScrollViewer::HorizontalScrollBarVisibilityProperty(), box_value(ScrollBarVisibility::Disabled) }; - acceptChangesFlyoutStyle.Setters().Append(horizontalScrollModeStyleSetter); - acceptChangesFlyoutStyle.Setters().Append(horizontalScrollBarVisibilityStyleSetter); - - Flyout acceptChangesFlyout{}; - acceptChangesFlyout.FlyoutPresenterStyle(acceptChangesFlyoutStyle); - acceptChangesFlyout.Content(flyoutStack); - senderVM.AcceptChangesFlyout(acceptChangesFlyout); - } - } - - // if there was a conflict, the flyout we created will handle whether changes need to be propagated - // otherwise, go ahead and apply the changes - if (!conflictFound) - { - // update settings model and view model - applyChangesToSettingsModel(); - - // We NEED to toggle the edit mode here, - // so that if nothing changed, we still exit - // edit mode. - senderVM.ToggleEditMode(); - } - } - - void ActionsViewModel::_KeyBindingViewModelDeleteNewlyAddedKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const IInspectable& /*args*/) - { - for (uint32_t i = 0; i < _KeyBindingList.Size(); ++i) - { - const auto& kbdVM{ _KeyBindingList.GetAt(i) }; - if (kbdVM == senderVM) - { - _KeyBindingList.RemoveAt(i); - return; - } - } - } - void ActionsViewModel::_CmdVMEditRequestedHandler(const Editor::CommandViewModel& senderVM, const IInspectable& /*args*/) { CurrentCommand(senderVM); @@ -1548,38 +1206,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - // Method Description: - // - performs a search on KeyBindingList by key chord. - // Arguments: - // - keys - the associated key chord of the command we're looking for - // Return Value: - // - the index of the view model referencing the command. If the command doesn't exist, nullopt - std::optional ActionsViewModel::_GetContainerIndexByKeyChord(const Control::KeyChord& keys) - { - for (uint32_t i = 0; i < _KeyBindingList.Size(); ++i) - { - const auto kbdVM{ get_self(_KeyBindingList.GetAt(i)) }; - const auto& otherKeys{ kbdVM->CurrentKeys() }; - if (otherKeys && keys.Modifiers() == otherKeys.Modifiers() && keys.Vkey() == otherKeys.Vkey()) - { - return i; - } - } - - // TODO GH #6900: - // an expedited search can be done if we use cmd.Name() - // to quickly search through the sorted list. - return std::nullopt; - } - - void ActionsViewModel::_RegisterEvents(com_ptr& kbdVM) - { - kbdVM->PropertyChanged({ this, &ActionsViewModel::_KeyBindingViewModelPropertyChangedHandler }); - kbdVM->DeleteKeyBindingRequested({ this, &ActionsViewModel::_KeyBindingViewModelDeleteKeyBindingHandler }); - kbdVM->ModifyKeyBindingRequested({ this, &ActionsViewModel::_KeyBindingViewModelModifyKeyBindingHandler }); - kbdVM->IsAutomationPeerAttached(_AutomationPeerAttached); - } - void ActionsViewModel::_RegisterCmdVMEvents(com_ptr& cmdVM) { cmdVM->EditRequested({ this, &ActionsViewModel::_CmdVMEditRequestedHandler }); diff --git a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h index 1006a5cce49..af3f4b0f5c4 100644 --- a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h @@ -5,26 +5,16 @@ #include "ActionsViewModel.g.h" #include "NavigateToCommandArgs.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" namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { - struct KeyBindingViewModelComparator - { - bool operator()(const Editor::KeyBindingViewModel& lhs, const Editor::KeyBindingViewModel& rhs) const - { - return lhs.Name() < rhs.Name(); - } - }; - struct CommandViewModelComparator { bool operator()(const Editor::CommandViewModel& lhs, const Editor::CommandViewModel& rhs) const @@ -48,21 +38,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Editor::CommandViewModel _Command{ nullptr }; }; - struct ModifyKeyBindingEventArgs : ModifyKeyBindingEventArgsT - { - public: - ModifyKeyBindingEventArgs(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys, const hstring oldActionName, const hstring newActionName) : - _OldKeys{ oldKeys }, - _NewKeys{ newKeys }, - _OldActionName{ std::move(oldActionName) }, - _NewActionName{ std::move(newActionName) } {} - - WINRT_PROPERTY(Control::KeyChord, OldKeys, nullptr); - WINRT_PROPERTY(Control::KeyChord, NewKeys, nullptr); - WINRT_PROPERTY(hstring, OldActionName); - WINRT_PROPERTY(hstring, NewActionName); - }; - struct ModifyKeyChordEventArgs : ModifyKeyChordEventArgsT { public: @@ -74,71 +49,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation WINRT_PROPERTY(Control::KeyChord, NewKeys, nullptr); }; - struct KeyBindingViewModel : KeyBindingViewModelT, ViewModelHelper - { - public: - KeyBindingViewModel(const Windows::Foundation::Collections::IObservableVector& availableActions); - KeyBindingViewModel(const Control::KeyChord& keys, const hstring& name, const Windows::Foundation::Collections::IObservableVector& availableActions); - - hstring Name() const { return _CurrentAction; } - hstring KeyChordText() const { return _KeyChordText; } - - // UIA Text - hstring EditButtonName() const noexcept; - hstring CancelButtonName() const noexcept; - hstring AcceptButtonName() const noexcept; - hstring DeleteButtonName() const noexcept; - - void EnterHoverMode() { IsHovered(true); }; - void ExitHoverMode() { IsHovered(false); }; - void ActionGotFocus() { IsContainerFocused(true); }; - void ActionLostFocus() { IsContainerFocused(false); }; - void EditButtonGettingFocus() { IsEditButtonFocused(true); }; - void EditButtonLosingFocus() { IsEditButtonFocused(false); }; - bool ShowEditButton() const noexcept; - void ToggleEditMode(); - void DisableEditMode() { IsInEditMode(false); } - void AttemptAcceptChanges(); - void AttemptAcceptChanges(const Control::KeyChord newKeys); - void CancelChanges(); - void DeleteKeyBinding() { DeleteKeyBindingRequested.raise(*this, _CurrentKeys); } - - // ProposedAction: the entry selected by the combo box; may disagree with the settings model. - // CurrentAction: the combo box item that maps to the settings model value. - // AvailableActions: the list of options in the combo box; both actions above must be in this list. - // NOTE: ProposedAction and CurrentAction may disagree mainly due to the "edit mode" system in place. - // Current Action serves as... - // 1 - a record of what to set ProposedAction to on a cancellation - // 2 - a form of translation between ProposedAction and the settings model - // We would also need an ActionMap reference to remove this, but this is a better separation - // of responsibilities. - VIEW_MODEL_OBSERVABLE_PROPERTY(IInspectable, ProposedAction); - VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, CurrentAction); - WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, AvailableActions, nullptr); - - // ProposedKeys: the keys proposed by the control; may disagree with the settings model. - // CurrentKeys: the key chord bound in the settings model. - VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, ProposedKeys); - VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, CurrentKeys, nullptr); - - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsInEditMode, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsNewlyAdded, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Controls::Flyout, AcceptChangesFlyout, nullptr); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsAutomationPeerAttached, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsHovered, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsContainerFocused, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsEditButtonFocused, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Media::Brush, ContainerBackground, nullptr); - - public: - til::typed_event ModifyKeyBindingRequested; - til::typed_event DeleteKeyBindingRequested; - til::typed_event DeleteNewlyAddedKeyBinding; - - private: - hstring _KeyChordText{}; - }; - struct CommandViewModel : CommandViewModelT, ViewModelHelper { public: @@ -310,8 +220,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation ActionsViewModel(Model::CascadiaSettings settings); void UpdateSettings(const Model::CascadiaSettings& settings); - void OnAutomationPeerAttached(); - void AddNewKeybinding(); void AddNewCommand(); void CurrentCommand(const Editor::CommandViewModel& newCommand); @@ -322,32 +230,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation 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 FocusContainer; - til::typed_event UpdateBackground; - - WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, KeyBindingList); WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, CommandList); WINRT_OBSERVABLE_PROPERTY(ActionsSubPage, CurrentPage, _propertyChangedHandlers, ActionsSubPage::Base); private: Editor::CommandViewModel _CurrentCommand{ nullptr }; - bool _AutomationPeerAttached{ false }; Model::CascadiaSettings _Settings; - Windows::Foundation::Collections::IObservableVector _AvailableActionAndArgs; - Windows::Foundation::Collections::IMap _AvailableActionMap; Windows::Foundation::Collections::IMap _AvailableActionsAndNamesMap; void _MakeCommandVMsHelper(); - - std::optional _GetContainerIndexByKeyChord(const Control::KeyChord& keys); - void _RegisterEvents(com_ptr& kbdVM); void _RegisterCmdVMEvents(com_ptr& 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); diff --git a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl index 3d0c0c57c94..c0571a08972 100644 --- a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl @@ -13,55 +13,12 @@ namespace Microsoft.Terminal.Settings.Editor IHostedInWindow WindowRoot { get; }; } - runtimeclass ModifyKeyBindingEventArgs - { - Microsoft.Terminal.Control.KeyChord OldKeys { get; }; - Microsoft.Terminal.Control.KeyChord NewKeys { get; }; - String OldActionName { get; }; - 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 - String Name { get; }; - String KeyChordText { get; }; - - // UI side - Boolean ShowEditButton { get; }; - Boolean IsInEditMode { get; }; - Boolean IsNewlyAdded { get; }; - Microsoft.Terminal.Control.KeyChord ProposedKeys; - Object ProposedAction; - Windows.UI.Xaml.Controls.Flyout AcceptChangesFlyout; - String EditButtonName { get; }; - String CancelButtonName { get; }; - String AcceptButtonName { get; }; - String DeleteButtonName { get; }; - Windows.UI.Xaml.Media.Brush ContainerBackground { get; }; - - void EnterHoverMode(); - void ExitHoverMode(); - void ActionGotFocus(); - void ActionLostFocus(); - void EditButtonGettingFocus(); - void EditButtonLosingFocus(); - IObservableVector AvailableActions { get; }; - void ToggleEditMode(); - void AttemptAcceptChanges(); - void CancelChanges(); - void DeleteKeyBinding(); - - event Windows.Foundation.TypedEventHandler ModifyKeyBindingRequested; - event Windows.Foundation.TypedEventHandler DeleteKeyBindingRequested; - } - runtimeclass CommandViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged { // Settings Model side @@ -176,14 +133,8 @@ namespace Microsoft.Terminal.Settings.Editor ActionsViewModel(Microsoft.Terminal.Settings.Model.CascadiaSettings settings); void UpdateSettings(Microsoft.Terminal.Settings.Model.CascadiaSettings settings); - void OnAutomationPeerAttached(); - void AddNewKeybinding(); void AddNewCommand(); - IObservableVector KeyBindingList { get; }; - event Windows.Foundation.TypedEventHandler FocusContainer; - event Windows.Foundation.TypedEventHandler UpdateBackground; - ActionsSubPage CurrentPage; CommandViewModel CurrentCommand; From 72e9b64e2d4a4da87ba6dc792f9d0af01077281a Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Thu, 15 May 2025 12:01:20 -0700 Subject: [PATCH 06/85] format --- .../TerminalSettingsModel/ActionArgs.h | 47 ++--- .../TerminalSettingsModel/ActionArgsMagic.h | 184 +++++++++--------- 2 files changed, 115 insertions(+), 116 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 16bd15e5234..08749fd7d10 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -287,22 +287,22 @@ protected: \ X(winrt::Microsoft::Terminal::Core::MatchMode, MatchMode, "matchMode", false, ArgTag::None, winrt::Microsoft::Terminal::Core::MatchMode::None) //////////////////////////////////////////////////////////////////////////////// -#define NEW_TERMINAL_ARGS(X) \ - X(winrt::hstring, Commandline, "commandline", false, ArgTag::None, L"") \ - X(winrt::hstring, StartingDirectory, "startingDirectory", false, ArgTag::None, L"") \ - X(winrt::hstring, TabTitle, "tabTitle", false, ArgTag::None, L"") \ - X(Windows::Foundation::IReference, TabColor, "tabColor", false, ArgTag::None, nullptr) \ - X(Windows::Foundation::IReference, ProfileIndex, "index", false, ArgTag::None, nullptr) \ - X(winrt::hstring, Profile, "profile", false, ArgTag::None, L"") \ - X(Windows::Foundation::IReference, SuppressApplicationTitle, "suppressApplicationTitle", false, ArgTag::None, nullptr) \ - X(winrt::hstring, ColorScheme, "colorScheme", args->SchemeName().empty(), ArgTag::ColorScheme, L"") \ - X(Windows::Foundation::IReference, Elevate, "elevate", false, ArgTag::None, nullptr) \ +#define NEW_TERMINAL_ARGS(X) \ + X(winrt::hstring, Commandline, "commandline", false, ArgTag::None, L"") \ + X(winrt::hstring, StartingDirectory, "startingDirectory", false, ArgTag::None, L"") \ + X(winrt::hstring, TabTitle, "tabTitle", false, ArgTag::None, L"") \ + X(Windows::Foundation::IReference, TabColor, "tabColor", false, ArgTag::None, nullptr) \ + X(Windows::Foundation::IReference, ProfileIndex, "index", false, ArgTag::None, nullptr) \ + X(winrt::hstring, Profile, "profile", false, ArgTag::None, L"") \ + X(Windows::Foundation::IReference, SuppressApplicationTitle, "suppressApplicationTitle", false, ArgTag::None, nullptr) \ + X(winrt::hstring, ColorScheme, "colorScheme", args->SchemeName().empty(), ArgTag::ColorScheme, L"") \ + X(Windows::Foundation::IReference, Elevate, "elevate", false, ArgTag::None, nullptr) \ X(Windows::Foundation::IReference, ReloadEnvironmentVariables, "reloadEnvironmentVariables", false, ArgTag::None, nullptr) //////////////////////////////////////////////////////////////////////////////// -#define SPLIT_PANE_ARGS(X) \ - X(Model::SplitDirection, SplitDirection, "split", false, ArgTag::None, SplitDirection::Automatic) \ - X(SplitType, SplitMode, "splitMode", false, ArgTag::None, SplitType::Manual) \ +#define SPLIT_PANE_ARGS(X) \ + X(Model::SplitDirection, SplitDirection, "split", false, ArgTag::None, SplitDirection::Automatic) \ + X(SplitType, SplitMode, "splitMode", false, ArgTag::None, SplitType::Manual) \ X(float, SplitSize, "size", false, ArgTag::None, 0.5f) //////////////////////////////////////////////////////////////////////////////// @@ -389,7 +389,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation public: NewTerminalArgs(int32_t& profileIndex) : - _ProfileIndex{ profileIndex } { + _ProfileIndex{ profileIndex } + { NEW_TERMINAL_ARGS(APPEND_ARG_DESCRIPTION); }; hstring GenerateName() const; @@ -652,7 +653,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation struct SplitPaneArgs : public SplitPaneArgsT { - SplitPaneArgs() { + SplitPaneArgs(){ SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION) }; SplitPaneArgs(SplitType splitMode, SplitDirection direction, float size, const Model::INewContentArgs& terminalArgs) : @@ -660,23 +661,23 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _SplitDirection{ direction }, _SplitSize{ size }, _ContentArgs{ terminalArgs } { - SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION) - }; + SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION) + }; SplitPaneArgs(SplitDirection direction, float size, const Model::INewContentArgs& terminalArgs) : _SplitDirection{ direction }, _SplitSize{ size }, _ContentArgs{ terminalArgs } { - SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION) - }; + SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION) + }; SplitPaneArgs(SplitDirection direction, const Model::INewContentArgs& terminalArgs) : _SplitDirection{ direction }, _ContentArgs{ terminalArgs } { - SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION) - }; + SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION) + }; SplitPaneArgs(SplitType splitMode) : _SplitMode{ splitMode } { - SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION) - }; + SPLIT_PANE_ARGS(APPEND_ARG_DESCRIPTION) + }; SPLIT_PANE_ARGS(DECLARE_ARGS); WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, Model::NewTerminalArgs{}); diff --git a/src/cascadia/TerminalSettingsModel/ActionArgsMagic.h b/src/cascadia/TerminalSettingsModel/ActionArgsMagic.h index ab4c2958b9f..443a218f0c7 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgsMagic.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgsMagic.h @@ -54,8 +54,8 @@ struct InitListPlaceholder // expanded. Pretty critical for tracking down extraneous commas, etc. // Property definitions, and JSON keys -#define DECLARE_ARGS(type, name, jsonKey, required, tag, ...) \ - static constexpr std::string_view name##Key{ jsonKey }; \ +#define DECLARE_ARGS(type, name, jsonKey, required, tag, ...) \ + static constexpr std::string_view name##Key{ jsonKey }; \ ACTION_ARG(type, name, ##__VA_ARGS__); // Parameters to the non-default ctor @@ -67,7 +67,7 @@ struct InitListPlaceholder _##name{ name##Param }, // append this argument's description to the internal vector -#define APPEND_ARG_DESCRIPTION(type, name, jsonKey, required, tag, ...) \ +#define APPEND_ARG_DESCRIPTION(type, name, jsonKey, required, tag, ...) \ _argDescriptions.push_back({ L## #name, L## #type, std::wstring_view(L## #required) != L"false", tag }); // check each property in the Equals() method. You'll note there's a stray @@ -77,17 +77,17 @@ struct InitListPlaceholder &&(otherAsUs->_##name == _##name) // getter and setter for each property by index -#define GET_ARG_BY_INDEX(type, name, jsonKey, required, tag, ...) \ - if (index == curIndex++) \ - { \ - if (_##name.has_value()) \ - { \ - return winrt::box_value(_##name.value()); \ - } \ - else \ - { \ - return winrt::box_value(static_cast(__VA_ARGS__)); \ - } \ +#define GET_ARG_BY_INDEX(type, name, jsonKey, required, tag, ...) \ + if (index == curIndex++) \ + { \ + if (_##name.has_value()) \ + { \ + return winrt::box_value(_##name.value()); \ + } \ + else \ + { \ + return winrt::box_value(static_cast(__VA_ARGS__)); \ + } \ } #define SET_ARG_BY_INDEX(type, name, jsonKey, required, tag, ...) \ @@ -110,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, tag, ...) \ - JsonUtils::GetValueForKey(json, jsonKey, args->_##name); \ - if (required) \ - { \ - return { nullptr, { SettingsLoadWarnings::MissingRequiredParameter } }; \ +#define FROM_JSON_ARGS(type, name, jsonKey, required, tag, ...) \ + JsonUtils::GetValueForKey(json, jsonKey, args->_##name); \ + if (required) \ + { \ + return { nullptr, { SettingsLoadWarnings::MissingRequiredParameter } }; \ } // JSON serialization @@ -142,78 +142,77 @@ struct InitListPlaceholder // * NewTerminalArgs has a ToCommandline method it needs to additionally declare. // * GlobalSummonArgs has the QuakeModeFromJson helper -#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 _argDescriptions; \ - \ -public: \ - hstring GenerateName() const; \ - bool Equals(const IActionArgs& other) \ - { \ - auto otherAsUs = other.try_as(); \ - if (otherAsUs) \ - { \ - return true argsMacro(EQUALS_ARGS); \ - } \ - return false; \ - }; \ - static FromJsonResult FromJson(const Json::Value& json) \ - { \ - auto args = winrt::make_self(); \ - 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(val) }; \ - argsMacro(TO_JSON_ARGS); \ - return json; \ - } \ - IActionArgs Copy() const \ - { \ - auto copy{ winrt::make_self() }; \ - 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(_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 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 _argDescriptions; \ + \ +public: \ + hstring GenerateName() const; \ + bool Equals(const IActionArgs& other) \ + { \ + auto otherAsUs = other.try_as(); \ + if (otherAsUs) \ + { \ + return true argsMacro(EQUALS_ARGS); \ + } \ + return false; \ + }; \ + static FromJsonResult FromJson(const Json::Value& json) \ + { \ + auto args = winrt::make_self(); \ + 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(val) }; \ + argsMacro(TO_JSON_ARGS); \ + return json; \ + } \ + IActionArgs Copy() const \ + { \ + auto copy{ winrt::make_self() }; \ + 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(_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) \ @@ -241,8 +240,7 @@ public: \ IInspectable GetArgAt(uint32_t index) const \ { \ uint32_t curIndex{ 0 }; \ - argsMacro(GET_ARG_BY_INDEX) \ - return nullptr; \ + argsMacro(GET_ARG_BY_INDEX) return nullptr; \ } \ void SetArgAt(uint32_t index, IInspectable value) \ { \ From eecb32d29737ddf883246d1bde5967e0c4c57402 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Thu, 15 May 2025 12:02:50 -0700 Subject: [PATCH 07/85] format --- .../TerminalSettingsEditor/Actions.xaml | 14 +- .../ActionsViewModel.cpp | 2 +- .../TerminalSettingsEditor/EditAction.xaml | 897 +++++++++--------- .../TerminalSettingsEditor/MainPage.xaml | 2 +- 4 files changed, 458 insertions(+), 457 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Actions.xaml b/src/cascadia/TerminalSettingsEditor/Actions.xaml index 4e30743f211..b0314dda2ad 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Actions.xaml @@ -182,10 +182,10 @@ Style="{StaticResource SettingsStackStyle}"> - - - + + +