diff --git a/.github/actions/spell-check/dictionary/names.txt b/.github/actions/spell-check/dictionary/names.txt index c7f4c1f2b15..23e4f9b6430 100644 --- a/.github/actions/spell-check/dictionary/names.txt +++ b/.github/actions/spell-check/dictionary/names.txt @@ -31,9 +31,11 @@ mbadolato Mehrain mgravell michaelniksa +michkap migrie mikegr mikemaccana +miloush miniksa niksa oising diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 500e2c89ec1..38b799aa487 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -503,6 +503,14 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleOpenTabSearch(const IInspectable& /*sender*/, const ActionEventArgs& args) { + // Tab search is always in-order. + auto tabCommands = winrt::single_threaded_vector(); + for (const auto& tab : _tabs) + { + tabCommands.Append(tab.SwitchToTabCommand()); + } + CommandPalette().SetTabActions(tabCommands); + auto opt = _GetFocusedTabIndex(); uint32_t startIdx = opt.value_or(0); diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index 88ee649012f..354abb8a239 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -615,6 +615,12 @@ namespace winrt::TerminalApp::implementation _updateFilteredActions(); } + void CommandPalette::SetTabActions(Collections::IVector const& tabs) + { + _allTabActions = tabs; + _updateFilteredActions(); + } + void CommandPalette::EnableCommandPaletteMode() { _switchToMode(CommandPaletteMode::ActionMode); @@ -976,45 +982,6 @@ namespace winrt::TerminalApp::implementation _currentNestedCommands.Clear(); } - // Method Description: - // - Listens for changes to TerminalPage's _tabs vector. Updates our vector of - // tab switching commands accordingly. - // Arguments: - // - s: The vector being listened to. - // - e: The vector changed args that tells us whether a change, insert, or removal was performed - // on the listened-to vector. - // Return Value: - // - - void CommandPalette::OnTabsChanged(const IInspectable& s, const IVectorChangedEventArgs& e) - { - if (auto tabList = s.try_as>()) - { - auto idx = e.Index(); - auto changedEvent = e.CollectionChange(); - - switch (changedEvent) - { - case CollectionChange::ItemChanged: - { - break; - } - case CollectionChange::ItemInserted: - { - auto tab = tabList.GetAt(idx); - _allTabActions.InsertAt(idx, tab.SwitchToTabCommand()); - break; - } - case CollectionChange::ItemRemoved: - { - _allTabActions.RemoveAt(idx); - break; - } - } - - _updateFilteredActions(); - } - } - void CommandPalette::EnableTabSwitcherMode(const bool searchMode, const uint32_t startIdx) { _switcherStartIdx = startIdx; diff --git a/src/cascadia/TerminalApp/CommandPalette.h b/src/cascadia/TerminalApp/CommandPalette.h index 36bb95704c1..1b67df246a5 100644 --- a/src/cascadia/TerminalApp/CommandPalette.h +++ b/src/cascadia/TerminalApp/CommandPalette.h @@ -23,6 +23,7 @@ namespace winrt::TerminalApp::implementation Windows::Foundation::Collections::IObservableVector FilteredActions(); void SetCommands(Windows::Foundation::Collections::IVector const& actions); + void SetTabActions(Windows::Foundation::Collections::IVector const& tabs); void SetKeyBindings(Microsoft::Terminal::TerminalControl::IKeyBindings bindings); void EnableCommandPaletteMode(); @@ -35,7 +36,6 @@ namespace winrt::TerminalApp::implementation // Tab Switcher void EnableTabSwitcherMode(const bool searchMode, const uint32_t startIdx); - void OnTabsChanged(const Windows::Foundation::IInspectable& s, const Windows::Foundation::Collections::IVectorChangedEventArgs& e); WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); OBSERVABLE_GETSET_PROPERTY(winrt::hstring, NoMatchesText, _PropertyChangedHandlers); diff --git a/src/cascadia/TerminalApp/CommandPalette.idl b/src/cascadia/TerminalApp/CommandPalette.idl index 256cff7758c..ec0011c578c 100644 --- a/src/cascadia/TerminalApp/CommandPalette.idl +++ b/src/cascadia/TerminalApp/CommandPalette.idl @@ -19,6 +19,7 @@ namespace TerminalApp Windows.Foundation.Collections.IObservableVector FilteredActions { get; }; void SetCommands(Windows.Foundation.Collections.IVector actions); + void SetTabActions(Windows.Foundation.Collections.IVector tabs); void SetKeyBindings(Microsoft.Terminal.TerminalControl.IKeyBindings bindings); void EnableCommandPaletteMode(); @@ -27,6 +28,5 @@ namespace TerminalApp void SetDispatch(ShortcutActionDispatch dispatch); void EnableTabSwitcherMode(Boolean searchMode, UInt32 startIdx); - void OnTabsChanged(IInspectable s, Windows.Foundation.Collections.IVectorChangedEventArgs e); } } diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index f97b1220123..c331bc0831c 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -44,6 +44,7 @@ namespace winrt::TerminalApp::implementation { TerminalPage::TerminalPage() : _tabs{ winrt::single_threaded_observable_vector() }, + _mruTabActions{ winrt::single_threaded_vector() }, _startupActions{ winrt::single_threaded_vector() } { InitializeComponent(); @@ -224,13 +225,6 @@ namespace winrt::TerminalApp::implementation } }); - _tabs.VectorChanged([weakThis{ get_weak() }](auto&& s, auto&& e) { - if (auto page{ weakThis.get() }) - { - page->CommandPalette().OnTabsChanged(s, e); - } - }); - // Settings AllowDependentAnimations will affect whether animations are // enabled application-wide, so we don't need to check it each time we // want to create an animation. @@ -674,6 +668,7 @@ namespace winrt::TerminalApp::implementation // Add the new tab to the list of our tabs. _tabs.Append(*newTabImpl); + _mruTabActions.Append(newTabImpl->SwitchToTabCommand()); newTabImpl->SetDispatch(*_actionDispatch); @@ -1042,6 +1037,13 @@ namespace winrt::TerminalApp::implementation auto tab{ _tabs.GetAt(tabIndex) }; tab.Shutdown(); + uint32_t mruIndex; + if (_mruTabActions.IndexOf(_tabs.GetAt(tabIndex).SwitchToTabCommand(), mruIndex)) + { + _mruTabActions.RemoveAt(mruIndex); + CommandPalette().SetTabActions(_mruTabActions); + } + _tabs.RemoveAt(tabIndex); _tabView.TabItems().RemoveAt(tabIndex); _UpdateTabIndices(); @@ -1170,29 +1172,34 @@ namespace winrt::TerminalApp::implementation // - Sets focus to the tab to the right or left the currently selected tab. void TerminalPage::_SelectNextTab(const bool bMoveRight) { - if (auto index{ _GetFocusedTabIndex() }) + if (_settings.GlobalSettings().UseTabSwitcher()) { - uint32_t tabCount = _tabs.Size(); - // Wraparound math. By adding tabCount and then calculating modulo tabCount, - // we clamp the values to the range [0, tabCount) while still supporting moving - // leftward from 0 to tabCount - 1. - const auto newTabIndex = ((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount); + CommandPalette().SetTabActions(_mruTabActions); - if (_settings.GlobalSettings().UseTabSwitcher()) - { - if (CommandPalette().Visibility() == Visibility::Visible) - { - CommandPalette().SelectNextItem(bMoveRight); - } + // Since ATS is always MRU, our focused tab index is always 0. + // So, going next should go to index 1, and going prev should wrap to the end. + uint32_t tabCount = _mruTabActions.Size(); + auto newTabIndex = ((tabCount + (bMoveRight ? 1 : -1)) % tabCount); - CommandPalette().EnableTabSwitcherMode(false, newTabIndex); - CommandPalette().Visibility(Visibility::Visible); + if (CommandPalette().Visibility() == Visibility::Visible) + { + CommandPalette().SelectNextItem(bMoveRight); } else { - _SelectTab(newTabIndex); + CommandPalette().EnableTabSwitcherMode(false, newTabIndex); + CommandPalette().Visibility(Visibility::Visible); } } + else if (auto index{ _GetFocusedTabIndex() }) + { + uint32_t tabCount = _tabs.Size(); + // Wraparound math. By adding tabCount and then calculating modulo tabCount, + // we clamp the values to the range [0, tabCount) while still supporting moving + // leftward from 0 to tabCount - 1. + auto newTabIndex = ((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount); + _SelectTab(newTabIndex); + } } // Method Description: @@ -1927,10 +1934,6 @@ namespace winrt::TerminalApp::implementation _rearrangeTo = eventArgs.Index(); } } - else - { - _UpdateCommandsForPalette(); - } _UpdateTabView(); } @@ -1983,6 +1986,7 @@ namespace winrt::TerminalApp::implementation if (CommandPalette().Visibility() != Visibility::Visible) { tab.Focus(FocusState::Programmatic); + _UpdateMRUTab(index); } // Raise an event that our title changed @@ -2501,6 +2505,7 @@ namespace winrt::TerminalApp::implementation if (auto index{ _GetFocusedTabIndex() }) { _tabs.GetAt(*index).Focus(FocusState::Programmatic); + _UpdateMRUTab(index.value()); } } @@ -2560,6 +2565,7 @@ namespace winrt::TerminalApp::implementation // Add the new tab to the list of our tabs. _tabs.Append(*newTabImpl); + _mruTabActions.Append(newTabImpl->SwitchToTabCommand()); // Don't capture a strong ref to the tab. If the tab is removed as this // is called, we don't really care anymore about handling the event. @@ -2656,6 +2662,27 @@ namespace winrt::TerminalApp::implementation tab.SwitchToTabCommand(command); } + // Method Description: + // - Bumps the tab in its in-order index up to the top of the mru list. + // Arguments: + // - index: the in-order index of the tab to bump. + // Return Value: + // - + void TerminalPage::_UpdateMRUTab(const uint32_t index) + { + uint32_t mruIndex; + auto command = _tabs.GetAt(index).SwitchToTabCommand(); + if (_mruTabActions.IndexOf(command, mruIndex)) + { + if (mruIndex > 0) + { + _mruTabActions.RemoveAt(mruIndex); + _mruTabActions.InsertAt(0, command); + CommandPalette().SetTabActions(_mruTabActions); + } + } + } + // -------------------------------- WinRT Events --------------------------------- // Winrt events need a method for adding a callback to the event and removing the callback. // These macros will define them both for you. diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 2996abd1002..a1130f0f904 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -89,7 +89,9 @@ namespace winrt::TerminalApp::implementation Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr }; Windows::Foundation::Collections::IObservableVector _tabs; + Windows::Foundation::Collections::IVector _mruTabActions; winrt::com_ptr _GetTerminalTabImpl(const ITab& tab) const; + void _UpdateTabIndices(); winrt::Microsoft::Terminal::Settings::Model::Command _switchToSettingsCommand{ nullptr }; @@ -211,6 +213,8 @@ namespace winrt::TerminalApp::implementation void _ReapplyCompactTabSize(); void _MakeSwitchToTabCommand(const ITab& tab, const uint32_t index); + void _UpdateTabSwitcherCommands(const bool mru); + void _UpdateMRUTab(const uint32_t index); #pragma region ActionHandlers // These are all defined in AppActionHandlers.cpp diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 592cd733f7f..fc33c7603c7 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -895,7 +895,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // keybindings on the keyUp, then we'll still send the keydown to the // connected terminal application, and something like ctrl+shift+T will // emit a ^T to the pipe. - if (!modifiers.IsAltGrPressed() && keyDown && _TryHandleKeyBinding(vkey, modifiers)) + if (!modifiers.IsAltGrPressed() && keyDown && _TryHandleKeyBinding(vkey, scanCode, modifiers)) { e.Handled(true); return; @@ -917,8 +917,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - Attempt to handle this key combination as a key binding // Arguments: // - vkey: The vkey of the key pressed. + // - scanCode: The scan code of the key pressed. // - modifiers: The ControlKeyStates representing the modifier key states. - bool TermControl::_TryHandleKeyBinding(const WORD vkey, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const + bool TermControl::_TryHandleKeyBinding(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const { auto bindings = _settings.KeyBindings(); if (!bindings) @@ -926,12 +927,44 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation return false; } - return bindings.TryKeyChord({ + auto success = bindings.TryKeyChord({ modifiers.IsCtrlPressed(), modifiers.IsAltPressed(), modifiers.IsShiftPressed(), vkey, }); + if (!success) + { + return false; + } + + // Let's assume the user has bound the dead key "^" to a sendInput command that sends "b". + // If the user presses the two keys "^a" it'll produce "bâ", despite us marking the key event as handled. + // The following is used to manually "consume" such dead keys and clear them from the keyboard state. + _ClearKeyboardState(vkey, scanCode); + return true; + } + + // Method Description: + // - Discards currently pressed dead keys. + // Arguments: + // - vkey: The vkey of the key pressed. + // - scanCode: The scan code of the key pressed. + void TermControl::_ClearKeyboardState(const WORD vkey, const WORD scanCode) const noexcept + { + std::array keyState; + if (!GetKeyboardState(keyState.data())) + { + return; + } + + // As described in "Sometimes you *want* to interfere with the keyboard's state buffer": + // http://archives.miloush.net/michkap/archive/2006/09/10/748775.html + // > "The key here is to keep trying to pass stuff to ToUnicode until -1 is not returned." + std::array buffer; + while (ToUnicodeEx(vkey, scanCode, keyState.data(), buffer.data(), gsl::narrow_cast(buffer.size()), 0b1, nullptr) < 0) + { + } } // Method Description: @@ -941,6 +974,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - Makes the cursor briefly visible during typing. // Arguments: // - vkey: The vkey of the key pressed. + // - scanCode: The scan code of the key pressed. // - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states. // - keyDown: If true, the key was pressed, otherwise the key was released. bool TermControl::_TrySendKeyEvent(const WORD vkey, diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index dd1118c3f1c..3d16738e020 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -285,7 +285,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void _KeyHandler(Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e, const bool keyDown); ::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const; - bool _TryHandleKeyBinding(const WORD vkey, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const; + bool _TryHandleKeyBinding(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const; + void _ClearKeyboardState(const WORD vkey, const WORD scanCode) const noexcept; bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown); bool _TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point); bool _CanSendVTMouseInput();