1218 lines
50 KiB
C++
1218 lines
50 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "ActionPaletteItem.h"
|
|
#include "TabPaletteItem.h"
|
|
#include "CommandLinePaletteItem.h"
|
|
#include "CommandPalette.h"
|
|
#include <LibraryResources.h>
|
|
|
|
#include "CommandPalette.g.cpp"
|
|
|
|
using namespace winrt;
|
|
using namespace winrt::TerminalApp;
|
|
using namespace winrt::Windows::UI::Core;
|
|
using namespace winrt::Windows::UI::Xaml;
|
|
using namespace winrt::Windows::UI::Xaml::Controls;
|
|
using namespace winrt::Windows::System;
|
|
using namespace winrt::Windows::Foundation;
|
|
using namespace winrt::Windows::Foundation::Collections;
|
|
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
|
|
|
namespace winrt::TerminalApp::implementation
|
|
{
|
|
CommandPalette::CommandPalette() :
|
|
_switcherStartIdx{ 0 }
|
|
{
|
|
InitializeComponent();
|
|
|
|
_itemTemplateSelector = Resources().Lookup(winrt::box_value(L"PaletteItemTemplateSelector")).try_as<PaletteItemTemplateSelector>();
|
|
_listItemTemplate = Resources().Lookup(winrt::box_value(L"ListItemTemplate")).try_as<DataTemplate>();
|
|
|
|
_filteredActions = winrt::single_threaded_observable_vector<winrt::TerminalApp::FilteredCommand>();
|
|
_nestedActionStack = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
|
_currentNestedCommands = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
|
_allCommands = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
|
_tabActions = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
|
_mruTabActions = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
|
_commandLineHistory = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
|
|
|
_switchToMode(CommandPaletteMode::ActionMode);
|
|
|
|
if (CommandPaletteShadow())
|
|
{
|
|
// Hook up the shadow on the command palette to the backdrop that
|
|
// will actually show it. This needs to be done at runtime, and only
|
|
// if the shadow actually exists. ThemeShadow isn't supported below
|
|
// version 18362.
|
|
CommandPaletteShadow().Receivers().Append(_shadowBackdrop());
|
|
// "raise" the command palette up by 16 units, so it will cast a shadow.
|
|
_backdrop().Translation({ 0, 0, 16 });
|
|
}
|
|
|
|
// Whatever is hosting us will enable us by setting our visibility to
|
|
// "Visible". When that happens, set focus to our search box.
|
|
RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
|
|
if (Visibility() == Visibility::Visible)
|
|
{
|
|
// Force immediate binding update so we can select an item
|
|
Bindings->Update();
|
|
|
|
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
|
{
|
|
_searchBox().Visibility(Visibility::Collapsed);
|
|
_filteredActionsView().SelectedIndex(_switcherStartIdx);
|
|
_filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem());
|
|
_filteredActionsView().Focus(FocusState::Keyboard);
|
|
|
|
// Do this right after becoming visible so we can quickly catch scenarios where
|
|
// modifiers aren't held down (e.g. command palette invocation).
|
|
_anchorKeyUpHandler();
|
|
}
|
|
else
|
|
{
|
|
_filteredActionsView().SelectedIndex(0);
|
|
_searchBox().Focus(FocusState::Programmatic);
|
|
}
|
|
|
|
TraceLoggingWrite(
|
|
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
|
|
"CommandPaletteOpened",
|
|
TraceLoggingDescription("Event emitted when the Command Palette is opened"),
|
|
TraceLoggingWideString(L"Action", "Mode", "which mode the palette was opened in"),
|
|
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
|
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
|
}
|
|
else
|
|
{
|
|
// Raise an event to return control to the Terminal.
|
|
_dismissPalette();
|
|
}
|
|
});
|
|
|
|
// Focusing the ListView when the Command Palette control is set to Visible
|
|
// for the first time fails because the ListView hasn't finished loading by
|
|
// the time Focus is called. Luckily, We can listen to SizeChanged to know
|
|
// when the ListView has been measured out and is ready, and we'll immediately
|
|
// revoke the handler because we only needed to handle it once on initialization.
|
|
_sizeChangedRevoker = _filteredActionsView().SizeChanged(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
|
|
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
|
{
|
|
_filteredActionsView().Focus(FocusState::Keyboard);
|
|
}
|
|
_sizeChangedRevoker.revoke();
|
|
});
|
|
|
|
_filteredActionsView().SelectionChanged({ this, &CommandPalette::_selectedCommandChanged });
|
|
|
|
_appArgs.DisableHelpInExitMessage();
|
|
}
|
|
|
|
// Method Description:
|
|
// - Moves the focus up or down the list of commands. If we're at the top,
|
|
// we'll loop around to the bottom, and vice-versa.
|
|
// Arguments:
|
|
// - moveDown: if true, we're attempting to move to the next item in the
|
|
// list. Otherwise, we're attempting to move to the previous.
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::SelectNextItem(const bool moveDown)
|
|
{
|
|
auto selected = _filteredActionsView().SelectedIndex();
|
|
const int numItems = ::base::saturated_cast<int>(_filteredActionsView().Items().Size());
|
|
|
|
// Do not try to select an item if
|
|
// - the list is empty
|
|
// - if no item is selected and "up" is pressed
|
|
if (numItems != 0 && (selected != -1 || moveDown))
|
|
{
|
|
// Wraparound math. By adding numItems and then calculating modulo numItems,
|
|
// we clamp the values to the range [0, numItems) while still supporting moving
|
|
// upward from 0 to numItems - 1.
|
|
const auto newIndex = ((numItems + selected + (moveDown ? 1 : -1)) % numItems);
|
|
_filteredActionsView().SelectedIndex(newIndex);
|
|
_filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem());
|
|
}
|
|
}
|
|
|
|
// Method Description:
|
|
// - Scroll the command palette to the specified index
|
|
// Arguments:
|
|
// - index within a list view of commands
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_scrollToIndex(uint32_t index)
|
|
{
|
|
auto numItems = _filteredActionsView().Items().Size();
|
|
|
|
if (numItems == 0)
|
|
{
|
|
// if the list is empty no need to scroll
|
|
return;
|
|
}
|
|
|
|
auto clampedIndex = std::clamp<int32_t>(index, 0, numItems - 1);
|
|
_filteredActionsView().SelectedIndex(clampedIndex);
|
|
_filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem());
|
|
}
|
|
|
|
// Method Description:
|
|
// - Computes the number of visible commands
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - the approximate number of items visible in the list (in other words the size of the page)
|
|
uint32_t CommandPalette::_getNumVisibleItems()
|
|
{
|
|
const auto container = _filteredActionsView().ContainerFromIndex(0);
|
|
const auto item = container.try_as<winrt::Windows::UI::Xaml::Controls::ListViewItem>();
|
|
const auto itemHeight = ::base::saturated_cast<int>(item.ActualHeight());
|
|
const auto listHeight = ::base::saturated_cast<int>(_filteredActionsView().ActualHeight());
|
|
return listHeight / itemHeight;
|
|
}
|
|
|
|
// Method Description:
|
|
// - Scrolls the focus one page up the list of commands.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::ScrollPageUp()
|
|
{
|
|
auto selected = _filteredActionsView().SelectedIndex();
|
|
auto numVisibleItems = _getNumVisibleItems();
|
|
_scrollToIndex(selected - numVisibleItems);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Scrolls the focus one page down the list of commands.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::ScrollPageDown()
|
|
{
|
|
auto selected = _filteredActionsView().SelectedIndex();
|
|
auto numVisibleItems = _getNumVisibleItems();
|
|
_scrollToIndex(selected + numVisibleItems);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Moves the focus to the top item in the list of commands.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::ScrollToTop()
|
|
{
|
|
_scrollToIndex(0);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Moves the focus to the bottom item in the list of commands.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::ScrollToBottom()
|
|
{
|
|
_scrollToIndex(_filteredActionsView().Items().Size() - 1);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Called when the command selection changes. We'll use this in the tab
|
|
// switcher to "preview" tabs as the user navigates the list of tabs. To
|
|
// do that, we'll dispatch the switch to tab command for this tab, but not
|
|
// dismiss the switcher.
|
|
// Arguments:
|
|
// - <unused>
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_selectedCommandChanged(const IInspectable& /*sender*/,
|
|
const Windows::UI::Xaml::RoutedEventArgs& /*args*/)
|
|
{
|
|
const auto selectedCommand = _filteredActionsView().SelectedItem();
|
|
const auto filteredCommand{ selectedCommand.try_as<winrt::TerminalApp::FilteredCommand>() };
|
|
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
|
{
|
|
_switchToTab(filteredCommand);
|
|
}
|
|
else if (_currentMode == CommandPaletteMode::ActionMode && filteredCommand != nullptr)
|
|
{
|
|
if (const auto actionPaletteItem{ filteredCommand.Item().try_as<winrt::TerminalApp::ActionPaletteItem>() })
|
|
{
|
|
_PreviewActionHandlers(*this, actionPaletteItem.Command());
|
|
}
|
|
}
|
|
}
|
|
|
|
void CommandPalette::_previewKeyDownHandler(IInspectable const& /*sender*/,
|
|
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
|
{
|
|
auto key = e.OriginalKey();
|
|
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
|
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
|
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
|
|
|
// Some keypresses such as Tab, Return, Esc, and Arrow Keys are ignored by controls because
|
|
// they're not considered input key presses. While they don't raise KeyDown events,
|
|
// they do raise PreviewKeyDown events.
|
|
//
|
|
// Only give anchored tab switcher the ability to cycle through tabs with the tab button.
|
|
// For unanchored mode, accessibility becomes an issue when we try to hijack tab since it's
|
|
// a really widely used keyboard navigation key.
|
|
if (_currentMode == CommandPaletteMode::TabSwitchMode && _actionMap)
|
|
{
|
|
winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast<int32_t>(key) };
|
|
if (const auto cmd{ _actionMap.GetActionByKeyChord(kc) })
|
|
{
|
|
if (cmd.ActionAndArgs().Action() == ShortcutAction::PrevTab)
|
|
{
|
|
SelectNextItem(false);
|
|
e.Handled(true);
|
|
return;
|
|
}
|
|
else if (cmd.ActionAndArgs().Action() == ShortcutAction::NextTab)
|
|
{
|
|
SelectNextItem(true);
|
|
e.Handled(true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (key == VirtualKey::Home && ctrlDown)
|
|
{
|
|
ScrollToTop();
|
|
e.Handled(true);
|
|
}
|
|
else if (key == VirtualKey::End && ctrlDown)
|
|
{
|
|
ScrollToBottom();
|
|
e.Handled(true);
|
|
}
|
|
else if (key == VirtualKey::Up)
|
|
{
|
|
// Action Mode: Move focus to the next item in the list.
|
|
SelectNextItem(false);
|
|
e.Handled(true);
|
|
}
|
|
else if (key == VirtualKey::Down)
|
|
{
|
|
// Action Mode: Move focus to the previous item in the list.
|
|
SelectNextItem(true);
|
|
e.Handled(true);
|
|
}
|
|
else if (key == VirtualKey::PageUp)
|
|
{
|
|
// Action Mode: Move focus to the first visible item in the list.
|
|
ScrollPageUp();
|
|
e.Handled(true);
|
|
}
|
|
else if (key == VirtualKey::PageDown)
|
|
{
|
|
// Action Mode: Move focus to the last visible item in the list.
|
|
ScrollPageDown();
|
|
e.Handled(true);
|
|
}
|
|
else if (key == VirtualKey::Enter)
|
|
{
|
|
if (const auto& button = e.OriginalSource().try_as<Button>())
|
|
{
|
|
// Let the button handle the Enter key so an eventually attached click handler will be called
|
|
e.Handled(false);
|
|
return;
|
|
}
|
|
|
|
const auto selectedCommand = _filteredActionsView().SelectedItem();
|
|
const auto filteredCommand = selectedCommand.try_as<winrt::TerminalApp::FilteredCommand>();
|
|
_dispatchCommand(filteredCommand);
|
|
e.Handled(true);
|
|
}
|
|
else if (key == VirtualKey::Escape)
|
|
{
|
|
// Dismiss the palette if the text is empty, otherwise clear the
|
|
// search string.
|
|
if (_searchBox().Text().empty())
|
|
{
|
|
_dismissPalette();
|
|
}
|
|
else
|
|
{
|
|
_searchBox().Text(L"");
|
|
}
|
|
|
|
e.Handled(true);
|
|
}
|
|
else if (key == VirtualKey::Back && _searchBox().Text().empty() && _lastFilterTextWasEmpty && _currentMode == CommandPaletteMode::ActionMode)
|
|
{
|
|
// If the last filter text was empty, and we're backspacing from
|
|
// that state, then the user "backspaced" the virtual '>' we're
|
|
// using as the action mode indicator. Switch into commandline mode.
|
|
_switchToMode(CommandPaletteMode::CommandlineMode);
|
|
e.Handled(true);
|
|
}
|
|
else if (key == VirtualKey::C && ctrlDown)
|
|
{
|
|
_searchBox().CopySelectionToClipboard();
|
|
e.Handled(true);
|
|
}
|
|
else if (key == VirtualKey::V && ctrlDown)
|
|
{
|
|
_searchBox().PasteFromClipboard();
|
|
e.Handled(true);
|
|
}
|
|
}
|
|
|
|
// Method Description:
|
|
// - Implements the Alt handler
|
|
// Return value:
|
|
// - whether the key was handled
|
|
bool CommandPalette::OnDirectKeyEvent(const uint32_t vkey, const uint8_t /*scanCode*/, const bool down)
|
|
{
|
|
auto handled = false;
|
|
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
|
{
|
|
if (vkey == VK_MENU && !down)
|
|
{
|
|
_anchorKeyUpHandler();
|
|
handled = true;
|
|
}
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
void CommandPalette::_keyUpHandler(IInspectable const& /*sender*/,
|
|
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
|
{
|
|
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
|
{
|
|
_anchorKeyUpHandler();
|
|
e.Handled(true);
|
|
}
|
|
}
|
|
|
|
// Method Description:
|
|
// - Handles anchor key ups during TabSwitchMode.
|
|
// We assume that at least one modifier key should be held down in order to "anchor"
|
|
// the ATS UI in place. So this function is called to check if any modifiers are
|
|
// still held down, and if not, dispatch the selected tab action and close the ATS.
|
|
// Return value:
|
|
// - <none>
|
|
void CommandPalette::_anchorKeyUpHandler()
|
|
{
|
|
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
|
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
|
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
|
|
|
if (!ctrlDown && !altDown && !shiftDown)
|
|
{
|
|
const auto selectedCommand = _filteredActionsView().SelectedItem();
|
|
if (const auto filteredCommand = selectedCommand.try_as<winrt::TerminalApp::FilteredCommand>())
|
|
{
|
|
_dispatchCommand(filteredCommand);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Method Description:
|
|
// - This event is triggered when someone clicks anywhere in the bounds of
|
|
// the window that's _not_ the command palette UI. When that happens,
|
|
// we'll want to dismiss the palette.
|
|
// Arguments:
|
|
// - <unused>
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_rootPointerPressed(Windows::Foundation::IInspectable const& /*sender*/,
|
|
Windows::UI::Xaml::Input::PointerRoutedEventArgs const& /*e*/)
|
|
{
|
|
if (Visibility() != Visibility::Collapsed)
|
|
{
|
|
_dismissPalette();
|
|
}
|
|
}
|
|
|
|
// Method Description:
|
|
// - The purpose of this event handler is to hide the palette if it loses focus.
|
|
// We say we lost focus if our root element and all its descendants lost focus.
|
|
// This handler is invoked when our root element or some descendant loses focus.
|
|
// At this point we need to learn if the newly focused element belongs to this palette.
|
|
// To achieve this:
|
|
// - We start with the newly focused element and traverse its visual ancestors up to the Xaml root.
|
|
// - If one of the ancestors is this CommandPalette, then by our definition the focus is not lost
|
|
// - If we reach the Xaml root without meeting this CommandPalette,
|
|
// then the focus is not contained in it anymore and it should be dismissed
|
|
// Arguments:
|
|
// - <unused>
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_lostFocusHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
|
Windows::UI::Xaml::RoutedEventArgs const& /*args*/)
|
|
{
|
|
const auto flyout = _searchBox().ContextFlyout();
|
|
if (flyout && flyout.IsOpen())
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto focusedElementOrAncestor = Input::FocusManager::GetFocusedElement(this->XamlRoot()).try_as<DependencyObject>();
|
|
while (focusedElementOrAncestor)
|
|
{
|
|
if (focusedElementOrAncestor == *this)
|
|
{
|
|
// This palette is the focused element or an ancestor of the focused element. No need to dismiss.
|
|
return;
|
|
}
|
|
|
|
// Go up to the next ancestor
|
|
focusedElementOrAncestor = winrt::Windows::UI::Xaml::Media::VisualTreeHelper::GetParent(focusedElementOrAncestor);
|
|
}
|
|
|
|
// We got to the root (the element with no parent) and didn't meet this palette on the path.
|
|
// It means that it lost the focus and needs to be dismissed.
|
|
_dismissPalette();
|
|
}
|
|
|
|
// Method Description:
|
|
// - This event is only triggered when someone clicks in the space right
|
|
// next to the text box in the command palette. We _don't_ want that click
|
|
// to light dismiss the palette, so we'll mark it handled here.
|
|
// Arguments:
|
|
// - e: the PointerRoutedEventArgs that we want to mark as handled
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_backdropPointerPressed(Windows::Foundation::IInspectable const& /*sender*/,
|
|
Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e)
|
|
{
|
|
e.Handled(true);
|
|
}
|
|
|
|
// Method Description:
|
|
// - This event is called when the user clicks on an individual item from
|
|
// the list. We'll get the item that was clicked and dispatch the command
|
|
// that the user clicked on.
|
|
// Arguments:
|
|
// - e: an ItemClickEventArgs who's ClickedItem() will be the command that was clicked on.
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_listItemClicked(Windows::Foundation::IInspectable const& /*sender*/,
|
|
Windows::UI::Xaml::Controls::ItemClickEventArgs const& e)
|
|
{
|
|
const auto selectedCommand = e.ClickedItem();
|
|
if (const auto filteredCommand = selectedCommand.try_as<winrt::TerminalApp::FilteredCommand>())
|
|
{
|
|
_dispatchCommand(filteredCommand);
|
|
}
|
|
}
|
|
|
|
// Method Description:
|
|
// This event is called when the user clicks on an ChevronLeft button right
|
|
// next to the ParentCommandName (e.g. New Tab...) above the subcommands list.
|
|
// It'll go up a level when the users click the button.
|
|
// Arguments:
|
|
// - sender: the button that got clicked
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_moveBackButtonClicked(Windows::Foundation::IInspectable const& /*sender*/,
|
|
Windows::UI::Xaml::RoutedEventArgs const&)
|
|
{
|
|
_PreviewActionHandlers(*this, nullptr);
|
|
_nestedActionStack.Clear();
|
|
ParentCommandName(L"");
|
|
_currentNestedCommands.Clear();
|
|
_searchBox().Focus(FocusState::Programmatic);
|
|
_updateFilteredActions();
|
|
_filteredActionsView().SelectedIndex(0);
|
|
}
|
|
|
|
// Method Description:
|
|
// - This is called when the user selects a command with subcommands. It
|
|
// will update our UI to now display the list of subcommands instead, and
|
|
// clear the search text so the user can search from the new list of
|
|
// commands.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_updateUIForStackChange()
|
|
{
|
|
if (_searchBox().Text().empty())
|
|
{
|
|
// Manually call _filterTextChanged, because setting the text to the
|
|
// empty string won't update it for us (as it won't actually change value.)
|
|
_filterTextChanged(nullptr, nullptr);
|
|
}
|
|
|
|
// Changing the value of the search box will trigger _filterTextChanged,
|
|
// which will cause us to refresh the list of filterable commands.
|
|
_searchBox().Text(L"");
|
|
_searchBox().Focus(FocusState::Programmatic);
|
|
|
|
if (auto automationPeer{ Automation::Peers::FrameworkElementAutomationPeer::FromElement(_searchBox()) })
|
|
{
|
|
automationPeer.RaiseNotificationEvent(
|
|
Automation::Peers::AutomationNotificationKind::ActionCompleted,
|
|
Automation::Peers::AutomationNotificationProcessing::CurrentThenMostRecent,
|
|
fmt::format(std::wstring_view{ RS_(L"CommandPalette_NestedCommandAnnouncement") }, ParentCommandName()),
|
|
L"CommandPaletteNestingLevelChanged" /* unique name for this notification category */);
|
|
}
|
|
}
|
|
|
|
// Method Description:
|
|
// - Retrieve the list of commands that we should currently be filtering.
|
|
// * If the user has command with subcommands, this will return that command's subcommands.
|
|
// * If we're in Tab Switcher mode, return the tab actions.
|
|
// * Otherwise, just return the list of all the top-level commands.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - A list of Commands to filter.
|
|
Collections::IVector<winrt::TerminalApp::FilteredCommand> CommandPalette::_commandsToFilter()
|
|
{
|
|
switch (_currentMode)
|
|
{
|
|
case CommandPaletteMode::ActionMode:
|
|
if (_nestedActionStack.Size() > 0)
|
|
{
|
|
return _currentNestedCommands;
|
|
}
|
|
|
|
return _allCommands;
|
|
case CommandPaletteMode::TabSearchMode:
|
|
return _tabActions;
|
|
case CommandPaletteMode::TabSwitchMode:
|
|
return _tabSwitcherMode == TabSwitcherMode::MostRecentlyUsed ? _mruTabActions : _tabActions;
|
|
case CommandPaletteMode::CommandlineMode:
|
|
return _commandLineHistory;
|
|
default:
|
|
return _allCommands;
|
|
}
|
|
}
|
|
|
|
// Method Description:
|
|
// - Helper method for retrieving the action from a command the user
|
|
// selected, and dispatching that command. Also fires a tracelogging event
|
|
// indicating that the user successfully found the action they were
|
|
// looking for.
|
|
// Arguments:
|
|
// - command: the Command to dispatch. This might be null.
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_dispatchCommand(winrt::TerminalApp::FilteredCommand const& filteredCommand)
|
|
{
|
|
if (_currentMode == CommandPaletteMode::CommandlineMode)
|
|
{
|
|
_dispatchCommandline(filteredCommand);
|
|
}
|
|
else if (_currentMode == CommandPaletteMode::TabSwitchMode || _currentMode == CommandPaletteMode::TabSearchMode)
|
|
{
|
|
_switchToTab(filteredCommand);
|
|
_close();
|
|
}
|
|
else if (filteredCommand)
|
|
{
|
|
if (const auto actionPaletteItem{ filteredCommand.Item().try_as<winrt::TerminalApp::ActionPaletteItem>() })
|
|
{
|
|
if (actionPaletteItem.Command().HasNestedCommands())
|
|
{
|
|
// If this Command had subcommands, then don't dispatch the
|
|
// action. Instead, display a new list of commands for the user
|
|
// to pick from.
|
|
_nestedActionStack.Append(filteredCommand);
|
|
ParentCommandName(actionPaletteItem.Command().Name());
|
|
_currentNestedCommands.Clear();
|
|
for (const auto& nameAndCommand : actionPaletteItem.Command().NestedCommands())
|
|
{
|
|
const auto action = nameAndCommand.Value();
|
|
auto nestedActionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action) };
|
|
auto nestedFilteredCommand{ winrt::make<FilteredCommand>(nestedActionPaletteItem) };
|
|
_currentNestedCommands.Append(nestedFilteredCommand);
|
|
}
|
|
|
|
_updateUIForStackChange();
|
|
}
|
|
else
|
|
{
|
|
// First stash the search text length, because _close will clear this.
|
|
const auto searchTextLength = _searchBox().Text().size();
|
|
|
|
// An action from the root command list has depth=0
|
|
const auto nestedCommandDepth = _nestedActionStack.Size();
|
|
|
|
// Close before we dispatch so that actions that open the command
|
|
// palette like the Tab Switcher will be able to have the last laugh.
|
|
_close();
|
|
|
|
// But make an exception for the Toggle Command Palette action: we don't want the dispatch
|
|
// make the command palette - that was just closed - visible again.
|
|
// All other actions can just be dispatched.
|
|
if (actionPaletteItem.Command().ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
|
|
{
|
|
_DispatchCommandRequestedHandlers(*this, actionPaletteItem.Command());
|
|
}
|
|
|
|
TraceLoggingWrite(
|
|
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
|
|
"CommandPaletteDispatchedAction",
|
|
TraceLoggingDescription("Event emitted when the user selects an action in the Command Palette"),
|
|
TraceLoggingUInt32(searchTextLength, "SearchTextLength", "Number of characters in the search string"),
|
|
TraceLoggingUInt32(nestedCommandDepth, "NestedCommandDepth", "the depth in the tree of commands for the dispatched action"),
|
|
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
|
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Method Description:
|
|
// - Get all the input text in _searchBox that follows any leading spaces.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - the string of input following any number of leading spaces
|
|
std::wstring CommandPalette::_getTrimmedInput()
|
|
{
|
|
const std::wstring input{ _searchBox().Text() };
|
|
if (input.empty())
|
|
{
|
|
return input;
|
|
}
|
|
|
|
// Trim leading whitespace
|
|
const auto firstNonSpace = input.find_first_not_of(L" ");
|
|
if (firstNonSpace == std::wstring::npos)
|
|
{
|
|
// All the following characters are whitespace.
|
|
return L"";
|
|
}
|
|
|
|
return input.substr(firstNonSpace);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Dispatch switch to tab action.
|
|
// Arguments:
|
|
// - filteredCommand - Selected filtered command - might be null
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_switchToTab(winrt::TerminalApp::FilteredCommand const& filteredCommand)
|
|
{
|
|
if (filteredCommand)
|
|
{
|
|
if (const auto tabPaletteItem{ filteredCommand.Item().try_as<winrt::TerminalApp::TabPaletteItem>() })
|
|
{
|
|
if (const auto tab{ tabPaletteItem.Tab() })
|
|
{
|
|
_SwitchToTabRequestedHandlers(*this, tab);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Method Description:
|
|
// - Dispatch the current search text as a ExecuteCommandline action.
|
|
// Arguments:
|
|
// - filteredCommand - Selected filtered command - might be null
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_dispatchCommandline(winrt::TerminalApp::FilteredCommand const& command)
|
|
{
|
|
const auto filteredCommand = command ? command : _buildCommandLineCommand(_getTrimmedInput());
|
|
if (filteredCommand.has_value())
|
|
{
|
|
if (_commandLineHistory.Size() == CommandLineHistoryLength)
|
|
{
|
|
_commandLineHistory.RemoveAtEnd();
|
|
}
|
|
_commandLineHistory.InsertAt(0, filteredCommand.value());
|
|
|
|
TraceLoggingWrite(
|
|
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
|
|
"CommandPaletteDispatchedCommandline",
|
|
TraceLoggingDescription("Event emitted when the user runs a commandline in the Command Palette"),
|
|
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
|
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
|
|
|
if (const auto commandLinePaletteItem{ filteredCommand.value().Item().try_as<winrt::TerminalApp::CommandLinePaletteItem>() })
|
|
{
|
|
_CommandLineExecutionRequestedHandlers(*this, commandLinePaletteItem.CommandLine());
|
|
_close();
|
|
}
|
|
}
|
|
}
|
|
|
|
std::optional<winrt::TerminalApp::FilteredCommand> CommandPalette::_buildCommandLineCommand(std::wstring const& commandLine)
|
|
{
|
|
if (commandLine.empty())
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
winrt::hstring cl{ commandLine };
|
|
auto commandLinePaletteItem{ winrt::make<winrt::TerminalApp::implementation::CommandLinePaletteItem>(cl) };
|
|
return winrt::make<FilteredCommand>(commandLinePaletteItem);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Helper method for closing the command palette, when the user has _not_
|
|
// selected an action. Also fires a tracelogging event indicating that the
|
|
// user closed the palette without running a command.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_dismissPalette()
|
|
{
|
|
_close();
|
|
|
|
TraceLoggingWrite(
|
|
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
|
|
"CommandPaletteDismissed",
|
|
TraceLoggingDescription("Event emitted when the user dismisses the Command Palette without selecting an action"),
|
|
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
|
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
|
}
|
|
|
|
// Method Description:
|
|
// - Event handler for when the text in the input box changes. In Action
|
|
// Mode, we'll update the list of displayed commands, and select the first one.
|
|
// Arguments:
|
|
// - <unused>
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_filterTextChanged(IInspectable const& /*sender*/,
|
|
Windows::UI::Xaml::RoutedEventArgs const& /*args*/)
|
|
{
|
|
if (_currentMode == CommandPaletteMode::CommandlineMode)
|
|
{
|
|
_evaluatePrefix();
|
|
}
|
|
|
|
// We're setting _lastFilterTextWasEmpty here, because if the user tries
|
|
// to backspace the last character in the input, the Backspace KeyDown
|
|
// event will fire _before_ _filterTextChanged does. Updating the value
|
|
// here will ensure that we can check this case appropriately.
|
|
_lastFilterTextWasEmpty = _searchBox().Text().empty();
|
|
|
|
_updateFilteredActions();
|
|
|
|
// In the command line mode we want the user to explicitly select the command
|
|
_filteredActionsView().SelectedIndex(_currentMode == CommandPaletteMode::CommandlineMode ? -1 : 0);
|
|
|
|
if (_currentMode == CommandPaletteMode::TabSearchMode || _currentMode == CommandPaletteMode::ActionMode)
|
|
{
|
|
const auto currentNeedleHasResults{ _filteredActions.Size() > 0 };
|
|
_noMatchesText().Visibility(currentNeedleHasResults ? Visibility::Collapsed : Visibility::Visible);
|
|
if (!currentNeedleHasResults)
|
|
{
|
|
if (auto automationPeer{ Automation::Peers::FrameworkElementAutomationPeer::FromElement(_searchBox()) })
|
|
{
|
|
automationPeer.RaiseNotificationEvent(
|
|
Automation::Peers::AutomationNotificationKind::ActionCompleted,
|
|
Automation::Peers::AutomationNotificationProcessing::ImportantMostRecent,
|
|
NoMatchesText(), // NoMatchesText contains the right text for the current mode
|
|
L"CommandPaletteResultAnnouncement" /* unique name for this notification */);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_noMatchesText().Visibility(Visibility::Collapsed);
|
|
}
|
|
|
|
if (_currentMode == CommandPaletteMode::CommandlineMode)
|
|
{
|
|
ParsedCommandLineText(L"");
|
|
|
|
const auto commandLine = _getTrimmedInput();
|
|
if (!commandLine.empty())
|
|
{
|
|
ExecuteCommandlineArgs args{ commandLine };
|
|
_appArgs.FullResetState();
|
|
if (_appArgs.ParseArgs(args) == 0)
|
|
{
|
|
const auto& commands = _appArgs.GetStartupActions();
|
|
if (commands.size() > 0)
|
|
{
|
|
std::wstring commandDescription{ RS_(L"CommandPalette_ParsedCommandLine") };
|
|
for (const auto& command : commands)
|
|
{
|
|
commandDescription += L"\n\t" + command.Args().GenerateName();
|
|
}
|
|
ParsedCommandLineText(commandDescription.data());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ParsedCommandLineText(RS_(L"CommandPalette_FailedParsingCommandLine") + L"\n\t" + til::u8u16(_appArgs.GetExitMessage()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CommandPalette::_evaluatePrefix()
|
|
{
|
|
// This will take you from commandline mode, into action mode. The
|
|
// backspace handler in _keyDownHandler will handle taking us from
|
|
// action mode to commandline mode.
|
|
auto newMode = CommandPaletteMode::CommandlineMode;
|
|
|
|
auto inputText = _getTrimmedInput();
|
|
if (inputText.size() > 0)
|
|
{
|
|
if (inputText[0] == L'>')
|
|
{
|
|
newMode = CommandPaletteMode::ActionMode;
|
|
}
|
|
}
|
|
|
|
if (newMode != _currentMode)
|
|
{
|
|
//_switchToMode will remove the '>' character from the input.
|
|
_switchToMode(newMode);
|
|
}
|
|
}
|
|
|
|
Collections::IObservableVector<winrt::TerminalApp::FilteredCommand> CommandPalette::FilteredActions()
|
|
{
|
|
return _filteredActions;
|
|
}
|
|
|
|
void CommandPalette::SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap)
|
|
{
|
|
_actionMap = actionMap;
|
|
}
|
|
|
|
void CommandPalette::SetCommands(Collections::IVector<Command> const& actions)
|
|
{
|
|
_allCommands.Clear();
|
|
for (const auto& action : actions)
|
|
{
|
|
auto actionPaletteItem{ winrt::make<winrt::TerminalApp::implementation::ActionPaletteItem>(action) };
|
|
auto filteredCommand{ winrt::make<FilteredCommand>(actionPaletteItem) };
|
|
_allCommands.Append(filteredCommand);
|
|
}
|
|
|
|
if (Visibility() == Visibility::Visible && _currentMode == CommandPaletteMode::ActionMode)
|
|
{
|
|
_updateFilteredActions();
|
|
}
|
|
}
|
|
|
|
// Method Description:
|
|
// - Replaces a list of filtered commands in the target collection with new
|
|
// commands based on the tabs in the source collection.
|
|
// Although the source observable we still don't register on it,
|
|
// so the palette user will need to reset the binding manually every time
|
|
// the source collection changes
|
|
// Arguments:
|
|
// - source: the tabs to use for creation filtered commands
|
|
// - target: the collection to store newly created filtered commands
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_bindTabs(
|
|
Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::TabBase> const& source,
|
|
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> const& target)
|
|
{
|
|
target.Clear();
|
|
for (const auto& tab : source)
|
|
{
|
|
auto tabPaletteItem{ winrt::make<winrt::TerminalApp::implementation::TabPaletteItem>(tab) };
|
|
auto filteredCommand{ winrt::make<FilteredCommand>(tabPaletteItem) };
|
|
target.Append(filteredCommand);
|
|
}
|
|
}
|
|
|
|
void CommandPalette::SetTabs(Collections::IObservableVector<TabBase> const& tabs, Collections::IObservableVector<TabBase> const& mruTabs)
|
|
{
|
|
_bindTabs(tabs, _tabActions);
|
|
_bindTabs(mruTabs, _mruTabActions);
|
|
}
|
|
|
|
void CommandPalette::EnableCommandPaletteMode(CommandPaletteLaunchMode const launchMode)
|
|
{
|
|
const auto mode = (launchMode == CommandPaletteLaunchMode::CommandLine) ?
|
|
CommandPaletteMode::CommandlineMode :
|
|
CommandPaletteMode::ActionMode;
|
|
|
|
_switchToMode(mode);
|
|
}
|
|
|
|
void CommandPalette::_switchToMode(CommandPaletteMode mode)
|
|
{
|
|
_currentMode = mode;
|
|
|
|
const auto currentlyVisible{ Visibility() == Visibility::Visible };
|
|
|
|
auto modeAnnouncementResourceKey{ USES_RESOURCE(L"CommandPaletteModeAnnouncement_ActionMode") };
|
|
ParsedCommandLineText(L"");
|
|
_searchBox().Text(L"");
|
|
_searchBox().Select(_searchBox().Text().size(), 0);
|
|
|
|
_nestedActionStack.Clear();
|
|
ParentCommandName(L"");
|
|
_currentNestedCommands.Clear();
|
|
// Leaving this block of code outside the above if-statement
|
|
// guarantees that the correct text is shown for the mode
|
|
// whenever _switchToMode is called.
|
|
switch (_currentMode)
|
|
{
|
|
case CommandPaletteMode::TabSearchMode:
|
|
case CommandPaletteMode::TabSwitchMode:
|
|
{
|
|
SearchBoxPlaceholderText(RS_(L"TabSwitcher_SearchBoxText"));
|
|
NoMatchesText(RS_(L"TabSwitcher_NoMatchesText"));
|
|
ControlName(RS_(L"TabSwitcherControlName"));
|
|
PrefixCharacter(L"");
|
|
modeAnnouncementResourceKey = USES_RESOURCE(L"CommandPaletteModeAnnouncement_TabSearchSwitchMode");
|
|
break;
|
|
}
|
|
case CommandPaletteMode::CommandlineMode:
|
|
SearchBoxPlaceholderText(RS_(L"CmdPalCommandlinePrompt"));
|
|
NoMatchesText(L"");
|
|
ControlName(RS_(L"CommandPaletteControlName"));
|
|
PrefixCharacter(L"");
|
|
modeAnnouncementResourceKey = USES_RESOURCE(L"CommandPaletteModeAnnouncement_CommandlineMode");
|
|
break;
|
|
case CommandPaletteMode::ActionMode:
|
|
default:
|
|
SearchBoxPlaceholderText(RS_(L"CommandPalette_SearchBox/PlaceholderText"));
|
|
NoMatchesText(RS_(L"CommandPalette_NoMatchesText/Text"));
|
|
ControlName(RS_(L"CommandPaletteControlName"));
|
|
PrefixCharacter(L">");
|
|
// modeAnnouncementResourceKey is already set to _ActionMode
|
|
// We did this above to deduce the type (and make it easier on ourselves later).
|
|
break;
|
|
}
|
|
|
|
if (currentlyVisible)
|
|
{
|
|
if (auto automationPeer{ Automation::Peers::FrameworkElementAutomationPeer::FromElement(_searchBox()) })
|
|
{
|
|
automationPeer.RaiseNotificationEvent(
|
|
Automation::Peers::AutomationNotificationKind::ActionCompleted,
|
|
Automation::Peers::AutomationNotificationProcessing::CurrentThenMostRecent,
|
|
GetLibraryResourceString(modeAnnouncementResourceKey),
|
|
L"CommandPaletteModeSwitch" /* unique ID for this notification */);
|
|
}
|
|
}
|
|
|
|
// The smooth remove/add animations that happen during
|
|
// UpdateFilteredActions don't work very well when switching between
|
|
// modes because of the sheer amount of remove/adds. So, let's just
|
|
// clear + append when switching between modes.
|
|
_filteredActions.Clear();
|
|
_updateFilteredActions();
|
|
}
|
|
|
|
// Method Description:
|
|
// - Produce a list of filtered actions to reflect the current contents of
|
|
// the input box.
|
|
// Arguments:
|
|
// - A collection that will receive the filtered actions
|
|
// Return Value:
|
|
// - <none>
|
|
std::vector<winrt::TerminalApp::FilteredCommand> CommandPalette::_collectFilteredActions()
|
|
{
|
|
std::vector<winrt::TerminalApp::FilteredCommand> actions;
|
|
|
|
winrt::hstring searchText{ _getTrimmedInput() };
|
|
|
|
auto commandsToFilter = _commandsToFilter();
|
|
|
|
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
|
{
|
|
std::copy(begin(commandsToFilter), end(commandsToFilter), std::back_inserter(actions));
|
|
}
|
|
else if (_currentMode == CommandPaletteMode::TabSearchMode || _currentMode == CommandPaletteMode::ActionMode || _currentMode == CommandPaletteMode::CommandlineMode)
|
|
{
|
|
for (const auto& action : commandsToFilter)
|
|
{
|
|
// Update filter for all commands
|
|
// This will modify the highlighting but will also lead to re-computation of weight (and consequently sorting).
|
|
// Pay attention that it already updates the highlighting in the UI
|
|
action.UpdateFilter(searchText);
|
|
|
|
// if there is active search we skip commands with 0 weight
|
|
if (searchText.empty() || action.Weight() > 0)
|
|
{
|
|
actions.push_back(action);
|
|
}
|
|
}
|
|
}
|
|
|
|
// We want to present the commands sorted
|
|
if (_currentMode == CommandPaletteMode::ActionMode)
|
|
{
|
|
std::sort(actions.begin(), actions.end(), FilteredCommand::Compare);
|
|
}
|
|
|
|
return actions;
|
|
}
|
|
|
|
// Method Description:
|
|
// - Update our list of filtered actions to reflect the current contents of
|
|
// the input box.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_updateFilteredActions()
|
|
{
|
|
auto actions = _collectFilteredActions();
|
|
|
|
// Make _filteredActions look identical to actions, using only Insert and Remove.
|
|
// This allows WinUI to nicely animate the ListView as it changes.
|
|
for (uint32_t i = 0; i < _filteredActions.Size() && i < actions.size(); i++)
|
|
{
|
|
for (uint32_t j = i; j < _filteredActions.Size(); j++)
|
|
{
|
|
if (_filteredActions.GetAt(j).Item() == actions[i].Item())
|
|
{
|
|
for (uint32_t k = i; k < j; k++)
|
|
{
|
|
_filteredActions.RemoveAt(i);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_filteredActions.GetAt(i).Item() != actions[i].Item())
|
|
{
|
|
_filteredActions.InsertAt(i, actions[i]);
|
|
}
|
|
}
|
|
|
|
// Remove any extra trailing items from the destination
|
|
while (_filteredActions.Size() > actions.size())
|
|
{
|
|
_filteredActions.RemoveAtEnd();
|
|
}
|
|
|
|
// Add any extra trailing items from the source
|
|
while (_filteredActions.Size() < actions.size())
|
|
{
|
|
_filteredActions.Append(actions[_filteredActions.Size()]);
|
|
}
|
|
}
|
|
|
|
// Method Description:
|
|
// - Dismiss the command palette. This will:
|
|
// * select all the current text in the input box
|
|
// * set our visibility to Hidden
|
|
// * raise our Closed event, so the page can return focus to the active Terminal
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_close()
|
|
{
|
|
Visibility(Visibility::Collapsed);
|
|
|
|
_PreviewActionHandlers(*this, nullptr);
|
|
|
|
// Reset visibility in case anchor mode tab switcher just finished.
|
|
_searchBox().Visibility(Visibility::Visible);
|
|
|
|
// Clear the text box each time we close the dialog. This is consistent with VsCode.
|
|
_searchBox().Text(L"");
|
|
|
|
_nestedActionStack.Clear();
|
|
|
|
ParentCommandName(L"");
|
|
_currentNestedCommands.Clear();
|
|
}
|
|
|
|
void CommandPalette::EnableTabSwitcherMode(const uint32_t startIdx, TabSwitcherMode tabSwitcherMode)
|
|
{
|
|
// The _switcherStartIdx field allows us to select the current tab
|
|
// We need to take it into account only in the in-order mode,
|
|
// as an MRU mode the current tab is on top by definition.
|
|
_switcherStartIdx = tabSwitcherMode == TabSwitcherMode::InOrder ? startIdx : 0;
|
|
_tabSwitcherMode = tabSwitcherMode;
|
|
_switchToMode(CommandPaletteMode::TabSwitchMode);
|
|
}
|
|
|
|
void CommandPalette::EnableTabSearchMode()
|
|
{
|
|
_switchToMode(CommandPaletteMode::TabSearchMode);
|
|
}
|
|
|
|
// Method Description:
|
|
// - This event is triggered when filteredActionView is looking for item container (ListViewItem)
|
|
// to use to present the filtered actions.
|
|
// GH#9288: unfortunately the default lookup seems to choose items with wrong data templates,
|
|
// e.g., using DataTemplate rendering actions for tab palette items.
|
|
// We handle this event by manually selecting an item from the cache.
|
|
// If no item is found we allocate a new one.
|
|
// Arguments:
|
|
// - args: the ChoosingItemContainerEventArgs allowing to get container candidate suggested by the
|
|
// system and replace it with another candidate if required.
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_choosingItemContainer(
|
|
Windows::UI::Xaml::Controls::ListViewBase const& /*sender*/,
|
|
Windows::UI::Xaml::Controls::ChoosingItemContainerEventArgs const& args)
|
|
{
|
|
const auto dataTemplate = _itemTemplateSelector.SelectTemplate(args.Item());
|
|
const auto itemContainer = args.ItemContainer();
|
|
if (itemContainer && itemContainer.ContentTemplate() == dataTemplate)
|
|
{
|
|
// If the suggested candidate is OK simply remove it from the cache
|
|
// (so we won't allocate it until it is released) and return
|
|
_listViewItemsCache[dataTemplate].erase(itemContainer);
|
|
}
|
|
else
|
|
{
|
|
// We need another candidate, let's look it up inside the cache
|
|
auto& containersByTemplate = _listViewItemsCache[dataTemplate];
|
|
if (!containersByTemplate.empty())
|
|
{
|
|
// There cache contains available items for required DataTemplate
|
|
// Let's return one of them (and remove it from the cache)
|
|
auto firstItem = containersByTemplate.begin();
|
|
args.ItemContainer(*firstItem);
|
|
containersByTemplate.erase(firstItem);
|
|
}
|
|
else
|
|
{
|
|
ElementFactoryGetArgs factoryArgs{};
|
|
const auto listViewItem = _listItemTemplate.GetElement(factoryArgs).try_as<Controls::ListViewItem>();
|
|
listViewItem.ContentTemplate(dataTemplate);
|
|
|
|
if (dataTemplate == _itemTemplateSelector.NestedItemTemplate())
|
|
{
|
|
const auto helpText = winrt::box_value(RS_(L"CommandPalette_MoreOptions/[using:Windows.UI.Xaml.Automation]AutomationProperties/HelpText"));
|
|
listViewItem.SetValue(Automation::AutomationProperties::HelpTextProperty(), helpText);
|
|
}
|
|
|
|
args.ItemContainer(listViewItem);
|
|
}
|
|
}
|
|
args.IsContainerPrepared(true);
|
|
}
|
|
|
|
// Method Description:
|
|
// - This event is triggered when the data item associate with filteredActionView list item is changing.
|
|
// We check if the item is being recycled. In this case we return it to the cache
|
|
// Arguments:
|
|
// - args: the ContainerContentChangingEventArgs describing the container change
|
|
// Return Value:
|
|
// - <none>
|
|
void CommandPalette::_containerContentChanging(
|
|
Windows::UI::Xaml::Controls::ListViewBase const& /*sender*/,
|
|
Windows::UI::Xaml::Controls::ContainerContentChangingEventArgs const& args)
|
|
{
|
|
const auto itemContainer = args.ItemContainer();
|
|
if (args.InRecycleQueue() && itemContainer && itemContainer.ContentTemplate())
|
|
{
|
|
_listViewItemsCache[itemContainer.ContentTemplate()].insert(itemContainer);
|
|
itemContainer.DataContext(nullptr);
|
|
}
|
|
else
|
|
{
|
|
itemContainer.DataContext(args.Item());
|
|
}
|
|
}
|
|
}
|