mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-06 06:09:50 +00:00
Advanced Tab Switcher (#6732)
 ## Summary of the Pull Request This PR adds the Advanced Tab Switcher (ATS) to Terminal. It'll work similarly to VSCode's tab switcher. Because this implementation rides off a lot of the Command Palette's XAML code, it'll look just like the Command Palette, and also have support for tab title search. ## References #3753 - ATS Spec Closes #1502
This commit is contained in:
@@ -67,6 +67,7 @@
|
||||
"wt",
|
||||
"closeOtherTabs",
|
||||
"closeTabsAfter",
|
||||
"tabSwitcher",
|
||||
"unbound"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -88,6 +89,14 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AnchorKey": {
|
||||
"enum": [
|
||||
"ctrl",
|
||||
"alt",
|
||||
"shift"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NewTerminalArgs": {
|
||||
"properties": {
|
||||
"commandline": {
|
||||
@@ -352,6 +361,22 @@
|
||||
],
|
||||
"required": [ "index" ]
|
||||
},
|
||||
"TabSwitcherAction": {
|
||||
"description": "Arguments corresponding to a Tab Switcher Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "tabSwitcher" },
|
||||
"anchorKey": {
|
||||
"$ref": "#/definitions/AnchorKey",
|
||||
"default": null,
|
||||
"description": "If provided, the tab switcher will stay open as long as the anchor key is held down. The anchor key should be part of the keybinding that opens the switcher."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Keybinding": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -372,6 +397,7 @@
|
||||
{ "$ref": "#/definitions/WtAction" },
|
||||
{ "$ref": "#/definitions/CloseOtherTabsAction" },
|
||||
{ "$ref": "#/definitions/CloseTabsAfterAction" },
|
||||
{ "$ref": "#/definitions/TabSwitcherAction" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
},
|
||||
|
||||
@@ -44,6 +44,7 @@ static constexpr std::string_view ExecuteCommandlineKey{ "wt" };
|
||||
static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" };
|
||||
static constexpr std::string_view CloseOtherTabsKey{ "closeOtherTabs" };
|
||||
static constexpr std::string_view CloseTabsAfterKey{ "closeTabsAfter" };
|
||||
static constexpr std::string_view ToggleTabSwitcherKey{ "tabSwitcher" };
|
||||
|
||||
static constexpr std::string_view ActionKey{ "action" };
|
||||
|
||||
@@ -100,6 +101,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{ ToggleCommandPaletteKey, ShortcutAction::ToggleCommandPalette },
|
||||
{ CloseOtherTabsKey, ShortcutAction::CloseOtherTabs },
|
||||
{ CloseTabsAfterKey, ShortcutAction::CloseTabsAfter },
|
||||
{ ToggleTabSwitcherKey, ShortcutAction::ToggleTabSwitcher },
|
||||
};
|
||||
|
||||
using ParseResult = std::tuple<IActionArgs, std::vector<::TerminalApp::SettingsLoadWarnings>>;
|
||||
@@ -139,6 +141,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
{ ShortcutAction::CloseTabsAfter, winrt::TerminalApp::implementation::CloseTabsAfterArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::ToggleTabSwitcher, winrt::TerminalApp::implementation::ToggleTabSwitcherArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::Invalid, nullptr },
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "SetTabColorArgs.g.cpp"
|
||||
#include "RenameTabArgs.g.cpp"
|
||||
#include "ExecuteCommandlineArgs.g.cpp"
|
||||
#include "ToggleTabSwitcherArgs.g.h"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
@@ -303,4 +304,22 @@ namespace winrt::TerminalApp::implementation
|
||||
_Index)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring ToggleTabSwitcherArgs::GenerateName() const
|
||||
{
|
||||
// If there's an anchor key set, don't generate a name so that
|
||||
// it won't show up in the command palette. Only an unanchored
|
||||
// tab switcher should be able to be toggled from the palette.
|
||||
// TODO: GH#7179 - once this goes in, make sure to hide the
|
||||
// anchor mode command that was given a name in settings.
|
||||
if (_AnchorKey != Windows::System::VirtualKey::None)
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
else
|
||||
{
|
||||
return RS_(L"ToggleTabSwitcherCommandKey");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "ExecuteCommandlineArgs.g.h"
|
||||
#include "CloseOtherTabsArgs.g.h"
|
||||
#include "CloseTabsAfterArgs.g.h"
|
||||
#include "ToggleTabSwitcherArgs.g.h"
|
||||
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
#include "Utils.h"
|
||||
@@ -512,6 +513,33 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
};
|
||||
|
||||
struct ToggleTabSwitcherArgs : public ToggleTabSwitcherArgsT<ToggleTabSwitcherArgs>
|
||||
{
|
||||
ToggleTabSwitcherArgs() = default;
|
||||
GETSET_PROPERTY(Windows::System::VirtualKey, AnchorKey, Windows::System::VirtualKey::None);
|
||||
|
||||
static constexpr std::string_view AnchorJsonKey{ "anchorKey" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<ToggleTabSwitcherArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_AnchorKey == _AnchorKey;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<ToggleTabSwitcherArgs>();
|
||||
JsonUtils::GetValueForKey(json, AnchorJsonKey, args->_AnchorKey);
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
|
||||
@@ -135,4 +135,9 @@ namespace TerminalApp
|
||||
{
|
||||
UInt32 Index { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ToggleTabSwitcherArgs : IActionArgs
|
||||
{
|
||||
Windows.System.VirtualKey AnchorKey { get; };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -285,6 +285,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// TODO GH#6677: When we add support for commandline mode, first set the
|
||||
// mode that the command palette should be in, before making it visible.
|
||||
CommandPalette().EnableCommandPaletteMode();
|
||||
CommandPalette().Visibility(CommandPalette().Visibility() == Visibility::Visible ?
|
||||
Visibility::Collapsed :
|
||||
Visibility::Visible);
|
||||
@@ -414,6 +415,7 @@ namespace winrt::TerminalApp::implementation
|
||||
actionArgs.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleCloseTabsAfter(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& actionArgs)
|
||||
{
|
||||
@@ -436,4 +438,28 @@ namespace winrt::TerminalApp::implementation
|
||||
actionArgs.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleToggleTabSwitcher(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::ToggleTabSwitcherArgs>())
|
||||
{
|
||||
auto anchorKey = realArgs.AnchorKey();
|
||||
|
||||
auto opt = _GetFocusedTabIndex();
|
||||
uint32_t startIdx = opt ? *opt : 0;
|
||||
|
||||
if (anchorKey != VirtualKey::None)
|
||||
{
|
||||
// TODO: GH#7178 - delta should also have the option of being -1, in the case when
|
||||
// a user decides to open the tab switcher going to the prev tab.
|
||||
int delta = 1;
|
||||
startIdx = (startIdx + _tabs.Size() + delta) % _tabs.Size();
|
||||
}
|
||||
|
||||
CommandPalette().EnableTabSwitcherMode(anchorKey, startIdx);
|
||||
CommandPalette().Visibility(Visibility::Visible);
|
||||
}
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +32,13 @@ namespace winrt::TerminalApp::implementation
|
||||
static std::vector<::TerminalApp::SettingsLoadWarnings> LayerJson(std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& commands,
|
||||
const Json::Value& json);
|
||||
|
||||
winrt::Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker propertyChangedRevoker;
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Name, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::TerminalApp::ActionAndArgs, Action, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, KeyChordText, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::Windows::UI::Xaml::Controls::IconSource, IconSource, _PropertyChangedHandlers, nullptr);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,5 +12,7 @@ namespace TerminalApp
|
||||
String Name;
|
||||
ActionAndArgs Action;
|
||||
String KeyChordText;
|
||||
|
||||
Windows.UI.Xaml.Controls.IconSource IconSource;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "CommandPalette.h"
|
||||
#include "ActionAndArgs.h"
|
||||
#include "ActionArgs.h"
|
||||
#include "Command.h"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
#include "CommandPalette.g.cpp"
|
||||
|
||||
@@ -12,15 +17,18 @@ using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
CommandPalette::CommandPalette()
|
||||
CommandPalette::CommandPalette() :
|
||||
_switcherStartIdx{ 0 }
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_filteredActions = winrt::single_threaded_observable_vector<winrt::TerminalApp::Command>();
|
||||
_allActions = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
|
||||
_allCommands = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
|
||||
_allTabActions = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
|
||||
|
||||
if (CommandPaletteShadow())
|
||||
{
|
||||
@@ -38,8 +46,26 @@ namespace winrt::TerminalApp::implementation
|
||||
RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
|
||||
if (Visibility() == Visibility::Visible)
|
||||
{
|
||||
_searchBox().Focus(FocusState::Programmatic);
|
||||
_filteredActionsView().SelectedIndex(0);
|
||||
if (_currentMode == CommandPaletteMode::TabSwitcherMode)
|
||||
{
|
||||
if (_anchorKey != VirtualKey::None)
|
||||
{
|
||||
_searchBox().Visibility(Visibility::Collapsed);
|
||||
_filteredActionsView().Focus(FocusState::Keyboard);
|
||||
}
|
||||
else
|
||||
{
|
||||
_searchBox().Focus(FocusState::Programmatic);
|
||||
}
|
||||
|
||||
_filteredActionsView().SelectedIndex(_switcherStartIdx);
|
||||
_filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem());
|
||||
}
|
||||
else
|
||||
{
|
||||
_searchBox().Focus(FocusState::Programmatic);
|
||||
_filteredActionsView().SelectedIndex(0);
|
||||
}
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
|
||||
@@ -55,6 +81,19 @@ namespace winrt::TerminalApp::implementation
|
||||
_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::TabSwitcherMode && _anchorKey != VirtualKey::None)
|
||||
{
|
||||
_filteredActionsView().Focus(FocusState::Keyboard);
|
||||
}
|
||||
_sizeChangedRevoker.revoke();
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -77,6 +116,36 @@ namespace winrt::TerminalApp::implementation
|
||||
_filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem());
|
||||
}
|
||||
|
||||
void CommandPalette::_previewKeyDownHandler(IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
auto key = e.OriginalKey();
|
||||
|
||||
// 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::TabSwitcherMode &&
|
||||
key == VirtualKey::Tab &&
|
||||
_anchorKey != VirtualKey::None)
|
||||
{
|
||||
auto const state = CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift);
|
||||
if (WI_IsFlagSet(state, CoreVirtualKeyStates::Down))
|
||||
{
|
||||
_selectNextItem(false);
|
||||
e.Handled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectNextItem(true);
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Process keystrokes in the input box. This is used for moving focus up
|
||||
// and down the list of commands in Action mode, and for executing
|
||||
@@ -108,7 +177,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
if (const auto selectedItem = _filteredActionsView().SelectedItem())
|
||||
{
|
||||
_dispatchCommand(selectedItem.try_as<Command>());
|
||||
_dispatchCommand(selectedItem.try_as<TerminalApp::Command>());
|
||||
}
|
||||
|
||||
e.Handled(true);
|
||||
@@ -129,6 +198,34 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void CommandPalette::_keyUpHandler(IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
auto key = e.OriginalKey();
|
||||
|
||||
if (_currentMode == CommandPaletteMode::TabSwitcherMode)
|
||||
{
|
||||
if (_anchorKey && key == _anchorKey.value())
|
||||
{
|
||||
// Once the user lifts the anchor key, we'll switch to the currently selected tab
|
||||
// then close the tab switcher.
|
||||
|
||||
if (const auto selectedItem = _filteredActionsView().SelectedItem())
|
||||
{
|
||||
if (const auto data = selectedItem.try_as<TerminalApp::Command>())
|
||||
{
|
||||
const auto actionAndArgs = data.Action();
|
||||
_dispatch.DoAction(actionAndArgs);
|
||||
_updateFilteredActions();
|
||||
_dismissPalette();
|
||||
}
|
||||
}
|
||||
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
@@ -171,6 +268,28 @@ namespace winrt::TerminalApp::implementation
|
||||
_dispatchCommand(e.ClickedItem().try_as<TerminalApp::Command>());
|
||||
}
|
||||
|
||||
// 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<TerminalApp::Command> CommandPalette::_commandsToFilter()
|
||||
{
|
||||
switch (_currentMode)
|
||||
{
|
||||
case CommandPaletteMode::ActionMode:
|
||||
return _allCommands;
|
||||
case CommandPaletteMode::TabSwitcherMode:
|
||||
return _allTabActions;
|
||||
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
|
||||
@@ -184,6 +303,10 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
if (command)
|
||||
{
|
||||
// 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();
|
||||
|
||||
const auto actionAndArgs = command.Action();
|
||||
_dispatch.DoAction(actionAndArgs);
|
||||
|
||||
@@ -194,8 +317,6 @@ namespace winrt::TerminalApp::implementation
|
||||
TraceLoggingUInt32(_searchBox().Text().size(), "SearchTextLength", "Number of characters in the search string"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
|
||||
_close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,17 +356,59 @@ namespace winrt::TerminalApp::implementation
|
||||
_noMatchesText().Visibility(_filteredActions.Size() > 0 ? Visibility::Collapsed : Visibility::Visible);
|
||||
}
|
||||
|
||||
Collections::IObservableVector<Command> CommandPalette::FilteredActions()
|
||||
Collections::IObservableVector<TerminalApp::Command> CommandPalette::FilteredActions()
|
||||
{
|
||||
return _filteredActions;
|
||||
}
|
||||
|
||||
void CommandPalette::SetActions(Collections::IVector<TerminalApp::Command> const& actions)
|
||||
void CommandPalette::SetCommands(Collections::IVector<TerminalApp::Command> const& actions)
|
||||
{
|
||||
_allActions = actions;
|
||||
_allCommands = actions;
|
||||
_updateFilteredActions();
|
||||
}
|
||||
|
||||
void CommandPalette::EnableCommandPaletteMode()
|
||||
{
|
||||
_switchToMode(CommandPaletteMode::ActionMode);
|
||||
_updateFilteredActions();
|
||||
}
|
||||
|
||||
void CommandPalette::_switchToMode(CommandPaletteMode mode)
|
||||
{
|
||||
// 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.
|
||||
if (mode != _currentMode)
|
||||
{
|
||||
_currentMode = mode;
|
||||
_filteredActions.Clear();
|
||||
auto commandsToFilter = _commandsToFilter();
|
||||
|
||||
for (auto action : commandsToFilter)
|
||||
{
|
||||
_filteredActions.Append(action);
|
||||
}
|
||||
|
||||
switch (_currentMode)
|
||||
{
|
||||
case CommandPaletteMode::TabSwitcherMode:
|
||||
{
|
||||
SearchBoxText(RS_(L"TabSwitcher_SearchBoxText"));
|
||||
NoMatchesText(RS_(L"TabSwitcher_NoMatchesText"));
|
||||
ControlName(RS_(L"TabSwitcherControlName"));
|
||||
break;
|
||||
}
|
||||
case CommandPaletteMode::ActionMode:
|
||||
default:
|
||||
SearchBoxText(RS_(L"CommandPalette_SearchBox/PlaceholderText"));
|
||||
NoMatchesText(RS_(L"CommandPalette_NoMatchesText/Text"));
|
||||
ControlName(RS_(L"CommandPaletteControlName"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
|
||||
static bool _compareCommandNames(const TerminalApp::Command& lhs, const TerminalApp::Command& rhs)
|
||||
{
|
||||
@@ -259,13 +422,23 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
TerminalApp::Command command;
|
||||
int weight;
|
||||
int inOrderCounter;
|
||||
|
||||
bool operator<(const WeightedCommand& other) const
|
||||
{
|
||||
// If two commands have the same weight, then we'll sort them alphabetically.
|
||||
if (weight == other.weight)
|
||||
{
|
||||
return !_compareCommandNames(command, other.command);
|
||||
// If two commands have the same weight, then we'll sort them alphabetically.
|
||||
// If they both have the same name, fall back to the order in which they were
|
||||
// pushed into the heap.
|
||||
if (command.Name() == other.command.Name())
|
||||
{
|
||||
return inOrderCounter > other.inOrderCounter;
|
||||
}
|
||||
else
|
||||
{
|
||||
return !_compareCommandNames(command, other.command);
|
||||
}
|
||||
}
|
||||
return weight < other.weight;
|
||||
}
|
||||
@@ -286,16 +459,30 @@ namespace winrt::TerminalApp::implementation
|
||||
auto searchText = _searchBox().Text();
|
||||
const bool addAll = searchText.empty();
|
||||
|
||||
auto commandsToFilter = _commandsToFilter();
|
||||
|
||||
// If there's no filter text, then just add all the commands in order to the list.
|
||||
// - TODO GH#6647:Possibly add the MRU commands first in order, followed
|
||||
// by the rest of the commands.
|
||||
if (addAll)
|
||||
{
|
||||
// If TabSwitcherMode, just add all as is. We don't want
|
||||
// them to be sorted alphabetically.
|
||||
if (_currentMode == CommandPaletteMode::TabSwitcherMode)
|
||||
{
|
||||
for (auto action : commandsToFilter)
|
||||
{
|
||||
actions.push_back(action);
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
// Add all the commands, but make sure they're sorted alphabetically.
|
||||
std::vector<TerminalApp::Command> sortedCommands;
|
||||
sortedCommands.reserve(_allActions.Size());
|
||||
sortedCommands.reserve(commandsToFilter.Size());
|
||||
|
||||
for (auto action : _allActions)
|
||||
for (auto action : commandsToFilter)
|
||||
{
|
||||
sortedCommands.push_back(action);
|
||||
}
|
||||
@@ -325,7 +512,12 @@ namespace winrt::TerminalApp::implementation
|
||||
// appear first in the list. The ordering will be determined by the
|
||||
// match weight produced by _getWeight.
|
||||
std::priority_queue<WeightedCommand> heap;
|
||||
for (auto action : _allActions)
|
||||
|
||||
// TODO GH#7205: Find a better way to ensure that WCs of the same
|
||||
// weight and name stay in the order in which they were pushed onto
|
||||
// the PQ.
|
||||
uint32_t counter = 0;
|
||||
for (auto action : commandsToFilter)
|
||||
{
|
||||
const auto weight = CommandPalette::_getWeight(searchText, action.Name());
|
||||
if (weight > 0)
|
||||
@@ -333,6 +525,8 @@ namespace winrt::TerminalApp::implementation
|
||||
WeightedCommand wc;
|
||||
wc.command = action;
|
||||
wc.weight = weight;
|
||||
wc.inOrderCounter = counter++;
|
||||
|
||||
heap.push(wc);
|
||||
}
|
||||
}
|
||||
@@ -502,8 +696,150 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
Visibility(Visibility::Collapsed);
|
||||
|
||||
// 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"");
|
||||
}
|
||||
|
||||
// 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:
|
||||
// - <none>
|
||||
void CommandPalette::OnTabsChanged(const IInspectable& s, const IVectorChangedEventArgs& e)
|
||||
{
|
||||
if (auto tabList = s.try_as<IObservableVector<TerminalApp::Tab>>())
|
||||
{
|
||||
auto idx = e.Index();
|
||||
auto changedEvent = e.CollectionChange();
|
||||
|
||||
switch (changedEvent)
|
||||
{
|
||||
case CollectionChange::ItemChanged:
|
||||
{
|
||||
winrt::com_ptr<Command> item;
|
||||
item.copy_from(winrt::get_self<Command>(_allTabActions.GetAt(idx)));
|
||||
item->propertyChangedRevoker.revoke();
|
||||
|
||||
auto tab = tabList.GetAt(idx);
|
||||
GenerateCommandForTab(idx, false, tab);
|
||||
UpdateTabIndices(idx);
|
||||
break;
|
||||
}
|
||||
case CollectionChange::ItemInserted:
|
||||
{
|
||||
auto tab = tabList.GetAt(idx);
|
||||
GenerateCommandForTab(idx, true, tab);
|
||||
UpdateTabIndices(idx);
|
||||
break;
|
||||
}
|
||||
case CollectionChange::ItemRemoved:
|
||||
{
|
||||
winrt::com_ptr<Command> item;
|
||||
item.copy_from(winrt::get_self<Command>(_allTabActions.GetAt(idx)));
|
||||
item->propertyChangedRevoker.revoke();
|
||||
|
||||
_allTabActions.RemoveAt(idx);
|
||||
UpdateTabIndices(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_updateFilteredActions();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - In the case where a tab is removed or reordered, the given indices of
|
||||
// the tab switch commands following the removed/reordered tab will get out of sync by 1
|
||||
// (e.g. if tab 1 is removed, tabs 2,3,4,... need to become tabs 1,2,3,...)
|
||||
// This function just loops through the tabs following startIdx and adjusts their given indices.
|
||||
// Arguments:
|
||||
// - startIdx: The index to start the update loop at.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::UpdateTabIndices(const uint32_t startIdx)
|
||||
{
|
||||
if (startIdx != _allTabActions.Size() - 1)
|
||||
{
|
||||
for (auto i = startIdx; i < _allTabActions.Size(); ++i)
|
||||
{
|
||||
auto command = _allTabActions.GetAt(i);
|
||||
|
||||
command.Action().Args().as<implementation::SwitchToTabArgs>()->TabIndex(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Create a tab switching command based on the given tab object and insert/update the command
|
||||
// at the given index. The command will call a SwitchToTab action on the given idx.
|
||||
// Arguments:
|
||||
// - idx: The index to insert or update the tab switch command.
|
||||
// - tab: The tab object to refer to when creating the tab switch command.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::GenerateCommandForTab(const uint32_t idx, bool inserted, TerminalApp::Tab& tab)
|
||||
{
|
||||
auto focusTabAction = winrt::make_self<implementation::ActionAndArgs>();
|
||||
auto args = winrt::make_self<implementation::SwitchToTabArgs>();
|
||||
args->TabIndex(idx);
|
||||
|
||||
focusTabAction->Action(ShortcutAction::SwitchToTab);
|
||||
focusTabAction->Args(*args);
|
||||
|
||||
auto command = winrt::make_self<implementation::Command>();
|
||||
command->Action(*focusTabAction);
|
||||
command->Name(tab.Title());
|
||||
command->IconSource(tab.IconSource());
|
||||
|
||||
// Listen for changes to the Tab so we can update this Command's attributes accordingly.
|
||||
auto weakThis{ get_weak() };
|
||||
auto weakCommand{ command->get_weak() };
|
||||
command->propertyChangedRevoker = tab.PropertyChanged(winrt::auto_revoke, [weakThis, weakCommand, tab](auto&&, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) {
|
||||
auto palette{ weakThis.get() };
|
||||
auto command{ weakCommand.get() };
|
||||
|
||||
if (palette && command)
|
||||
{
|
||||
if (args.PropertyName() == L"Title")
|
||||
{
|
||||
if (command->Name() != tab.Title())
|
||||
{
|
||||
command->Name(tab.Title());
|
||||
}
|
||||
}
|
||||
if (args.PropertyName() == L"IconSource")
|
||||
{
|
||||
if (command->IconSource() != tab.IconSource())
|
||||
{
|
||||
command->IconSource(tab.IconSource());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (inserted)
|
||||
{
|
||||
_allTabActions.InsertAt(idx, *command);
|
||||
}
|
||||
else
|
||||
{
|
||||
_allTabActions.SetAt(idx, *command);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandPalette::EnableTabSwitcherMode(const VirtualKey& anchorKey, const uint32_t startIdx)
|
||||
{
|
||||
_switcherStartIdx = startIdx;
|
||||
_anchorKey = anchorKey;
|
||||
_switchToMode(CommandPaletteMode::TabSwitcherMode);
|
||||
_updateFilteredActions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,26 +8,50 @@
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
enum class CommandPaletteMode
|
||||
{
|
||||
ActionMode = 0,
|
||||
TabSwitcherMode
|
||||
};
|
||||
|
||||
struct CommandPalette : CommandPaletteT<CommandPalette>
|
||||
{
|
||||
CommandPalette();
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> FilteredActions();
|
||||
void SetActions(Windows::Foundation::Collections::IVector<TerminalApp::Command> const& actions);
|
||||
|
||||
void SetCommands(Windows::Foundation::Collections::IVector<TerminalApp::Command> const& actions);
|
||||
void EnableCommandPaletteMode();
|
||||
|
||||
void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch);
|
||||
|
||||
// Tab Switcher
|
||||
void EnableTabSwitcherMode(const Windows::System::VirtualKey& anchorKey, 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);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, SearchBoxText, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, ControlName, _PropertyChangedHandlers);
|
||||
|
||||
private:
|
||||
friend struct CommandPaletteT<CommandPalette>; // for Xaml to bind events
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> _filteredActions{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _allActions{ nullptr };
|
||||
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _allCommands{ nullptr };
|
||||
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
|
||||
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _commandsToFilter();
|
||||
|
||||
void _filterTextChanged(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::RoutedEventArgs const& args);
|
||||
void _previewKeyDownHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
void _keyDownHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
void _keyUpHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
|
||||
void _rootPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
void _backdropPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
@@ -41,6 +65,18 @@ namespace winrt::TerminalApp::implementation
|
||||
static int _getWeight(const winrt::hstring& searchText, const winrt::hstring& name);
|
||||
void _close();
|
||||
|
||||
CommandPaletteMode _currentMode;
|
||||
void _switchToMode(CommandPaletteMode mode);
|
||||
|
||||
// Tab Switcher
|
||||
std::optional<winrt::Windows::System::VirtualKey> _anchorKey;
|
||||
void GenerateCommandForTab(const uint32_t idx, bool inserted, winrt::TerminalApp::Tab& tab);
|
||||
void UpdateTabIndices(const uint32_t startIdx);
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _allTabActions{ nullptr };
|
||||
uint32_t _switcherStartIdx;
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::ListView::SizeChanged_revoker _sizeChangedRevoker;
|
||||
|
||||
void _dispatchCommand(const TerminalApp::Command& command);
|
||||
|
||||
void _dismissPalette();
|
||||
|
||||
@@ -5,14 +5,22 @@ import "../Command.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass CommandPalette : Windows.UI.Xaml.Controls.Grid
|
||||
[default_interface] runtimeclass CommandPalette : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
CommandPalette();
|
||||
|
||||
String NoMatchesText { get; };
|
||||
String SearchBoxText { get; };
|
||||
String ControlName { get; };
|
||||
|
||||
Windows.Foundation.Collections.IObservableVector<Command> FilteredActions { get; };
|
||||
|
||||
void SetActions(Windows.Foundation.Collections.IVector<Command> actions);
|
||||
void SetCommands(Windows.Foundation.Collections.IVector<Command> actions);
|
||||
void EnableCommandPaletteMode();
|
||||
|
||||
void SetDispatch(ShortcutActionDispatch dispatch);
|
||||
|
||||
void EnableTabSwitcherMode(Windows.System.VirtualKey anchorKey, UInt32 startIdx);
|
||||
void OnTabsChanged(IInspectable s, Windows.Foundation.Collections.IVectorChangedEventArgs e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information. -->
|
||||
<Grid
|
||||
<UserControl
|
||||
x:Class="TerminalApp.CommandPalette"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
@@ -9,10 +9,17 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:Windows10version1903="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 8)"
|
||||
TabNavigation="Cycle"
|
||||
IsTabStop="True"
|
||||
AllowFocusOnInteraction="True"
|
||||
PointerPressed="_rootPointerPressed"
|
||||
mc:Ignorable="d">
|
||||
PreviewKeyDown="_previewKeyDownHandler"
|
||||
KeyDown="_keyDownHandler"
|
||||
PreviewKeyUp="_keyUpHandler"
|
||||
mc:Ignorable="d"
|
||||
AutomationProperties.Name="{x:Bind ControlName, Mode=OneWay}">
|
||||
|
||||
<Grid.Resources>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
<!-- ThemeShadow is only on 18362. This "Windows10version1903" bit
|
||||
@@ -100,29 +107,30 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
</Grid.Resources>
|
||||
<Grid>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="6*" />
|
||||
<ColumnDefinition Width="2*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="6*" />
|
||||
<ColumnDefinition Width="2*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="8*"/>
|
||||
<RowDefinition Height="2*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="8*"/>
|
||||
<RowDefinition Height="2*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Setting the row/col span of this shadow backdrop is a bit of a hack. In
|
||||
order to receive pointer events, an element needs to be _not_ transparent.
|
||||
However, we want to be able to eat all the clicks outside the immediate
|
||||
bounds of the command palette, and we don't want a semi-transparent overlay
|
||||
over all of the UI. Fortunately, if we make this _shadowBackdrop the size of
|
||||
the entire page, then it can be mostly transparent, and cause the root grid
|
||||
to receive clicks _anywhere_ in its bounds. -->
|
||||
<!-- Setting the row/col span of this shadow backdrop is a bit of a hack. In
|
||||
order to receive pointer events, an element needs to be _not_ transparent.
|
||||
However, we want to be able to eat all the clicks outside the immediate
|
||||
bounds of the command palette, and we don't want a semi-transparent overlay
|
||||
over all of the UI. Fortunately, if we make this _shadowBackdrop the size of
|
||||
the entire page, then it can be mostly transparent, and cause the root grid
|
||||
to receive clicks _anywhere_ in its bounds. -->
|
||||
|
||||
<Grid
|
||||
<Grid
|
||||
x:Name="_shadowBackdrop"
|
||||
Background="Transparent"
|
||||
Grid.Column="0"
|
||||
@@ -133,7 +141,7 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
VerticalAlignment="Stretch">
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
<Grid
|
||||
x:Name="_backdrop"
|
||||
Style="{ThemeResource CommandPaletteBackground}"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||
@@ -145,33 +153,32 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBox
|
||||
<TextBox
|
||||
Grid.Row="0"
|
||||
x:Uid="CommandPalette_SearchBox"
|
||||
x:Name="_searchBox"
|
||||
Margin="8"
|
||||
IsSpellCheckEnabled="False"
|
||||
TextChanged="_filterTextChanged"
|
||||
KeyDown="_keyDownHandler"
|
||||
PlaceholderText="{x:Bind SearchBoxText, Mode=OneWay}"
|
||||
Text="">
|
||||
</TextBox>
|
||||
</TextBox>
|
||||
|
||||
<TextBlock
|
||||
<TextBlock
|
||||
Padding="16"
|
||||
x:Name="_noMatchesText"
|
||||
x:Uid="CommandPalette_NoMatchesText"
|
||||
FontStyle="Italic"
|
||||
Visibility="Collapsed"
|
||||
Grid.Row="1">
|
||||
</TextBlock>
|
||||
Grid.Row="1"
|
||||
Text="{x:Bind NoMatchesText, Mode=OneWay}">
|
||||
</TextBlock>
|
||||
|
||||
<ListView
|
||||
<ListView
|
||||
Grid.Row="2"
|
||||
x:Name="_filteredActionsView"
|
||||
HorizontalAlignment="Stretch"
|
||||
@@ -181,36 +188,39 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
AllowDrop="False"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="_listItemClicked"
|
||||
PreviewKeyDown="_keyDownHandler"
|
||||
ItemsSource="{x:Bind FilteredActions}">
|
||||
|
||||
<ItemsControl.ItemTemplate >
|
||||
<DataTemplate x:DataType="local:Command">
|
||||
<ItemsControl.ItemTemplate >
|
||||
<DataTemplate x:DataType="local:Command">
|
||||
|
||||
<!-- This HorizontalContentAlignment="Stretch" is important
|
||||
<!-- This HorizontalContentAlignment="Stretch" is important
|
||||
to make sure it takes the entire width of the line -->
|
||||
<ListViewItem HorizontalContentAlignment="Stretch"
|
||||
<ListViewItem HorizontalContentAlignment="Stretch"
|
||||
AutomationProperties.Name="{x:Bind Name, Mode=OneWay}"
|
||||
AutomationProperties.AcceleratorKey="{x:Bind KeyChordText, Mode=OneWay}">
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" >
|
||||
<Grid HorizontalAlignment="Stretch" ColumnSpacing="8" >
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/> <!-- icon -->
|
||||
<ColumnDefinition Width="Auto"/> <!-- command label -->
|
||||
<ColumnDefinition Width="*"/> <!-- key chord -->
|
||||
<ColumnDefinition Width="16"/> <!-- gutter for scrollbar -->
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/> <!-- icon -->
|
||||
<ColumnDefinition Width="Auto"/> <!-- command label -->
|
||||
<ColumnDefinition Width="*"/> <!-- key chord -->
|
||||
<ColumnDefinition Width="16"/> <!-- gutter for scrollbar -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- TODO GH#6644: Add Icon to command palette entries, in column 0 -->
|
||||
<IconSourceElement
|
||||
Grid.Column="0"
|
||||
IconSource="{x:Bind IconSource, Mode=OneWay}"/>
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{x:Bind Name, Mode=OneWay}" />
|
||||
|
||||
<!-- The block for the key chord is only visible
|
||||
<!-- The block for the key chord is only visible
|
||||
when there's actual text set as the label. See
|
||||
CommandKeyChordVisibilityConverter for details. -->
|
||||
<Border
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
Visibility="{x:Bind KeyChordText,
|
||||
Mode=OneWay,
|
||||
@@ -220,19 +230,20 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<TextBlock
|
||||
<TextBlock
|
||||
Style="{ThemeResource KeyChordTextBlockStyle}"
|
||||
FontSize="12"
|
||||
Text="{x:Bind KeyChordText, Mode=OneWay}" />
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -529,6 +529,9 @@
|
||||
<data name="ToggleCommandPaletteCommandKey" xml:space="preserve">
|
||||
<value>Toggle command palette</value>
|
||||
</data>
|
||||
<data name="CommandPaletteControlName" xml:space="preserve">
|
||||
<value>Command Palette</value>
|
||||
</data>
|
||||
<data name="SetColorSchemeCommandKey" xml:space="preserve">
|
||||
<value>Set color scheme to {0}</value>
|
||||
<comment>{0} will be replaced with the name of a color scheme as defined by the user.</comment>
|
||||
@@ -562,6 +565,18 @@
|
||||
<value>Close tabs after index {0}</value>
|
||||
<comment>{0} will be replaced with a number</comment>
|
||||
</data>
|
||||
<data name="TabSwitcherControlName" xml:space="preserve">
|
||||
<value>Tab Switcher</value>
|
||||
</data>
|
||||
<data name="ToggleTabSwitcherCommandKey" xml:space="preserve">
|
||||
<value>Toggle tab switcher</value>
|
||||
</data>
|
||||
<data name="TabSwitcher_SearchBoxText" xml:space="preserve">
|
||||
<value>Type a tab name...</value>
|
||||
</data>
|
||||
<data name="TabSwitcher_NoMatchesText" xml:space="preserve">
|
||||
<value>No matching tab name</value>
|
||||
</data>
|
||||
<data name="CrimsonColorButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Crimson</value>
|
||||
</data>
|
||||
|
||||
@@ -220,6 +220,11 @@ namespace winrt::TerminalApp::implementation
|
||||
_CloseTabsAfterHandlers(*this, *eventArgs);
|
||||
break;
|
||||
}
|
||||
case ShortcutAction::ToggleTabSwitcher:
|
||||
{
|
||||
_ToggleTabSwitcherHandlers(*this, *eventArgs);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace winrt::TerminalApp::implementation
|
||||
TYPED_EVENT(ExecuteCommandline, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(CloseOtherTabs, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(CloseTabsAfter, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ToggleTabSwitcher, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
|
||||
@@ -44,7 +44,8 @@ namespace TerminalApp
|
||||
ExecuteCommandline,
|
||||
ToggleCommandPalette,
|
||||
CloseOtherTabs,
|
||||
CloseTabsAfter
|
||||
CloseTabsAfter,
|
||||
ToggleTabSwitcher
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ActionAndArgs {
|
||||
@@ -94,5 +95,6 @@ namespace TerminalApp
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> ExecuteCommandline;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> CloseOtherTabs;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> CloseTabsAfter;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> ToggleTabSwitcher;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ using namespace winrt::Windows::System;
|
||||
namespace winrt
|
||||
{
|
||||
namespace MUX = Microsoft::UI::Xaml;
|
||||
namespace WUX = Windows::UI::Xaml;
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
@@ -225,7 +226,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
IconPath(_lastIconPath);
|
||||
// The TabViewItem Icon needs MUX while the IconSourceElement in the CommandPalette needs WUX...
|
||||
IconSource(GetColoredIcon<winrt::WUX::Controls::IconSource>(_lastIconPath));
|
||||
_tabViewItem.IconSource(GetColoredIcon<winrt::MUX::Controls::IconSource>(_lastIconPath));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace winrt::TerminalApp::implementation
|
||||
DECLARE_EVENT(ColorCleared, _colorCleared, winrt::delegate<>);
|
||||
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Title, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, IconPath, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::Windows::UI::Xaml::Controls::IconSource, IconSource, _PropertyChangedHandlers, nullptr);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Pane> _rootPane{ nullptr };
|
||||
|
||||
@@ -6,6 +6,6 @@ namespace TerminalApp
|
||||
[default_interface] runtimeclass Tab : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
String Title { get; };
|
||||
String IconPath { get; };
|
||||
Windows.UI.Xaml.Controls.IconSource IconSource { get; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,9 +80,20 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
command.KeyChordText(KeyChordSerialization::ToString(keyChord));
|
||||
}
|
||||
|
||||
// Set the default IconSource to a BitmapIconSource with a null source
|
||||
// (instead of just nullptr) because there's a really weird crash when swapping
|
||||
// data bound IconSourceElements in a ListViewTemplate (i.e. CommandPalette).
|
||||
// Swapping between nullptr IconSources and non-null IconSources causes a crash
|
||||
// to occur, but swapping between IconSources with a null source and non-null IconSources
|
||||
// work perfectly fine :shrug:.
|
||||
winrt::Windows::UI::Xaml::Controls::BitmapIconSource icon;
|
||||
icon.UriSource(nullptr);
|
||||
command.IconSource(icon);
|
||||
|
||||
commandsCollection.Append(command);
|
||||
}
|
||||
CommandPalette().SetActions(commandsCollection);
|
||||
CommandPalette().SetCommands(commandsCollection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,6 +222,13 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
});
|
||||
|
||||
_tabs.VectorChanged([weakThis{ get_weak() }](auto&& s, auto&& e) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
page->CommandPalette().OnTabsChanged(s, e);
|
||||
}
|
||||
});
|
||||
|
||||
// Once the page is actually laid out on the screen, trigger all our
|
||||
// startup actions. Things like Panes need to know at least how big the
|
||||
// window will be, so they can subdivide that space.
|
||||
@@ -897,6 +915,7 @@ namespace winrt::TerminalApp::implementation
|
||||
_actionDispatch->ExecuteCommandline({ this, &TerminalPage::_HandleExecuteCommandline });
|
||||
_actionDispatch->CloseOtherTabs({ this, &TerminalPage::_HandleCloseOtherTabs });
|
||||
_actionDispatch->CloseTabsAfter({ this, &TerminalPage::_HandleCloseTabsAfter });
|
||||
_actionDispatch->ToggleTabSwitcher({ this, &TerminalPage::_HandleToggleTabSwitcher });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -230,6 +230,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void _HandleToggleCommandPalette(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
void _HandleCloseOtherTabs(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
void _HandleCloseTabsAfter(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
void _HandleToggleTabSwitcher(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
// Make sure to hook new actions up in _RegisterActionCallbacks!
|
||||
#pragma endregion
|
||||
|
||||
|
||||
@@ -270,3 +270,12 @@ JSON_ENUM_MAPPER(::winrt::TerminalApp::SettingsTarget)
|
||||
pair_type{ "allFiles", ValueType::AllFiles },
|
||||
};
|
||||
};
|
||||
|
||||
JSON_ENUM_MAPPER(::winrt::Windows::System::VirtualKey)
|
||||
{
|
||||
JSON_MAPPINGS(3) = {
|
||||
pair_type{ "ctrl", ValueType::Control },
|
||||
pair_type{ "alt", ValueType::Menu },
|
||||
pair_type{ "shift", ValueType::Shift },
|
||||
};
|
||||
};
|
||||
|
||||
@@ -114,7 +114,7 @@ private: \
|
||||
// private _setName() method, that the class can internally use to change the
|
||||
// value when it _knows_ it doesn't need to raise the PropertyChanged event
|
||||
// (like when the class is being initialized).
|
||||
#define OBSERVABLE_GETSET_PROPERTY(type, name, event) \
|
||||
#define OBSERVABLE_GETSET_PROPERTY(type, name, event, ...) \
|
||||
public: \
|
||||
type name() { return _##name; }; \
|
||||
void name(const type& value) \
|
||||
@@ -127,7 +127,7 @@ public:
|
||||
}; \
|
||||
\
|
||||
private: \
|
||||
const type _##name; \
|
||||
const type _##name{ __VA_ARGS__ }; \
|
||||
void _set##name(const type& value) \
|
||||
{ \
|
||||
const_cast<type&>(_##name) = value; \
|
||||
|
||||
Reference in New Issue
Block a user