Swap the command palette modes for the prefix > (#7935)

VsCode uses `>` as its "prefix" for the equivalent of their "action
mode". This PR aligns the Terminal with their logic here. 

We have to be tricky - if we use the `>` in the actual input as the
indicator for action mode, we can't display any placeholder text in the
input to tell users to type a command. This wasn't an issue for the
commandline mode previously, because we'd stick the "prompt" in the "no
matches text" space. However, we can't do that for action mode. Instead,
we'll stick a floating text block over the input box, and when the
user's in action mode, we'll manually place a `>` into that space. When
the user backspaces the `>`, we'll remove it from that block, and switch
into commandline mode.

## Validation Steps Performed
Played with the cmdpal in lots of different modes, this finally feels
good

Closes #7736
This commit is contained in:
Mike Griese 2020-10-15 17:58:35 -05:00 committed by GitHub
parent 30e363e7ac
commit bd7cd5512d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 114 additions and 96 deletions

View file

@ -213,41 +213,27 @@ namespace winrt::TerminalApp::implementation
}
else if (key == VirtualKey::Escape)
{
// Action, TabSearch, TabSwitch Mode: Dismiss the palette if the
// text is empty, otherwise clear the search string.
if (_currentMode != CommandPaletteMode::CommandlineMode)
// Dismiss the palette if the text is empty, otherwise clear the
// search string.
if (_searchBox().Text().empty())
{
if (_searchBox().Text().empty())
{
_dismissPalette();
}
else
{
_searchBox().Text(L"");
}
_dismissPalette();
}
else if (_currentMode == CommandPaletteMode::CommandlineMode)
else
{
const auto currentInput = _getPostPrefixInput();
if (currentInput.empty())
{
// The user's only input "> " so far. We should just dismiss
// the palette. This is like dismissing the Action mode with
// empty input.
_dismissPalette();
}
else
{
// Clear out the current input. We'll leave a ">" in the
// input (to stay in commandline mode), and a leading space
// (if they currently had one).
const bool hasLeadingSpace = (_searchBox().Text().size()) - (currentInput.size()) > 1;
_searchBox().Text(hasLeadingSpace ? L"> " : L">");
_searchBox().Text(L"");
}
// This will conveniently move the cursor to the end of the
// text input for us.
_searchBox().Select(_searchBox().Text().size(), 0);
}
e.Handled(true);
}
else if (key == VirtualKey::Back)
{
// 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.
if (_searchBox().Text().empty() && _lastFilterTextWasEmpty && _currentMode == CommandPaletteMode::ActionMode)
{
_switchToMode(CommandPaletteMode::CommandlineMode);
}
e.Handled(true);
@ -480,18 +466,13 @@ namespace winrt::TerminalApp::implementation
}
}
}
// Method Description:
// - Get all the input text in _searchBox that follows the prefix character
// and any whitespace following that prefix character. This can be used in
// commandline mode to get all the useful input that the user input after
// the leading ">" prefix.
// - Note that this will behave unexpectedly in Action Mode.
// - Get all the input text in _searchBox that follows any leading spaces.
// Arguments:
// - <none>
// Return Value:
// - the string of input following the prefix character.
std::wstring CommandPalette::_getPostPrefixInput()
// - the string of input following any number of leading spaces
std::wstring CommandPalette::_getTrimmedInput()
{
const std::wstring input{ _searchBox().Text() };
if (input.empty())
@ -499,17 +480,15 @@ namespace winrt::TerminalApp::implementation
return input;
}
const auto rawCmdline{ input.substr(1) };
// Trim leading whitespace
const auto firstNonSpace = rawCmdline.find_first_not_of(L" ");
const auto firstNonSpace = input.find_first_not_of(L" ");
if (firstNonSpace == std::wstring::npos)
{
// All the following characters are whitespace.
return L"";
}
return rawCmdline.substr(firstNonSpace);
return input.substr(firstNonSpace);
}
// Method Description:
@ -520,12 +499,11 @@ namespace winrt::TerminalApp::implementation
// - <none>
void CommandPalette::_dispatchCommandline()
{
const auto input = _getPostPrefixInput();
if (input.empty())
auto cmdline{ _getTrimmedInput() };
if (cmdline.empty())
{
return;
}
winrt::hstring cmdline{ input };
// Build the ExecuteCommandline action from the values we've parsed on the commandline.
ExecuteCommandlineArgs args{ cmdline };
@ -574,32 +552,49 @@ namespace winrt::TerminalApp::implementation
void CommandPalette::_filterTextChanged(IInspectable const& /*sender*/,
Windows::UI::Xaml::RoutedEventArgs const& /*args*/)
{
if (_currentMode == CommandPaletteMode::CommandlineMode || _currentMode == CommandPaletteMode::ActionMode)
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();
_filteredActionsView().SelectedIndex(0);
_noMatchesText().Visibility(_filteredActions.Size() > 0 ? Visibility::Collapsed : Visibility::Visible);
if (_currentMode == CommandPaletteMode::TabSearchMode || _currentMode == CommandPaletteMode::ActionMode)
{
_noMatchesText().Visibility(_filteredActions.Size() > 0 ? Visibility::Collapsed : Visibility::Visible);
}
else
{
_noMatchesText().Visibility(Visibility::Collapsed);
}
}
void CommandPalette::_evaluatePrefix()
{
auto newMode = CommandPaletteMode::ActionMode;
// 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 = _searchBox().Text();
auto inputText = _getTrimmedInput();
if (inputText.size() > 0)
{
if (inputText[0] == L'>')
{
newMode = CommandPaletteMode::CommandlineMode;
newMode = CommandPaletteMode::ActionMode;
}
}
if (newMode != _currentMode)
{
//_switchToMode will remove the '>' character from the input.
_switchToMode(newMode);
}
}
@ -644,6 +639,8 @@ namespace winrt::TerminalApp::implementation
}
}
_searchBox().Text(L"");
_searchBox().Select(_searchBox().Text().size(), 0);
// Leaving this block of code outside the above if-statement
// guarantees that the correct text is shown for the mode
// whenever _switchToMode is called.
@ -652,20 +649,24 @@ namespace winrt::TerminalApp::implementation
case CommandPaletteMode::TabSearchMode:
case CommandPaletteMode::TabSwitchMode:
{
SearchBoxText(RS_(L"TabSwitcher_SearchBoxText"));
SearchBoxPlaceholderText(RS_(L"TabSwitcher_SearchBoxText"));
NoMatchesText(RS_(L"TabSwitcher_NoMatchesText"));
ControlName(RS_(L"TabSwitcherControlName"));
PrefixCharacter(L"");
break;
}
case CommandPaletteMode::CommandlineMode:
NoMatchesText(RS_(L"CmdPalCommandlinePrompt"));
SearchBoxPlaceholderText(RS_(L"CmdPalCommandlinePrompt"));
NoMatchesText(L"");
ControlName(RS_(L"CommandPaletteControlName"));
PrefixCharacter(L"");
break;
case CommandPaletteMode::ActionMode:
default:
SearchBoxText(RS_(L"CommandPalette_SearchBox/PlaceholderText"));
SearchBoxPlaceholderText(RS_(L"CommandPalette_SearchBox/PlaceholderText"));
NoMatchesText(RS_(L"CommandPalette_NoMatchesText/Text"));
ControlName(RS_(L"CommandPaletteControlName"));
PrefixCharacter(L">");
break;
}
}
@ -717,7 +718,7 @@ namespace winrt::TerminalApp::implementation
{
std::vector<Command> actions;
auto searchText = _searchBox().Text();
winrt::hstring searchText{ _getTrimmedInput() };
const bool addAll = searchText.empty();
auto commandsToFilter = _commandsToFilter();

View file

@ -39,7 +39,8 @@ namespace winrt::TerminalApp::implementation
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, SearchBoxPlaceholderText, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, PrefixCharacter, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, ControlName, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, ParentCommandName, _PropertyChangedHandlers);
@ -55,6 +56,8 @@ namespace winrt::TerminalApp::implementation
Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> _commandsToFilter();
bool _lastFilterTextWasEmpty{ true };
void _filterTextChanged(Windows::Foundation::IInspectable const& sender,
Windows::UI::Xaml::RoutedEventArgs const& args);
void _previewKeyDownHandler(Windows::Foundation::IInspectable const& sender,
@ -84,8 +87,8 @@ namespace winrt::TerminalApp::implementation
CommandPaletteMode _currentMode;
void _switchToMode(CommandPaletteMode mode);
std::wstring _getTrimmedInput();
void _evaluatePrefix();
std::wstring _getPostPrefixInput();
Microsoft::Terminal::TerminalControl::IKeyBindings _bindings;

View file

@ -11,7 +11,8 @@ namespace TerminalApp
CommandPalette();
String NoMatchesText { get; };
String SearchBoxText { get; };
String SearchBoxPlaceholderText { get; };
String PrefixCharacter { get; };
String ControlName { get; };
String ParentCommandName { get; };

View file

@ -8,8 +8,8 @@ the MIT License. See LICENSE in the project root for license information. -->
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
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)"
xmlns:SettingsModel="using:Microsoft.Terminal.Settings.Model"
xmlns:Windows10version1903="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 8)"
xmlns:SettingsModel="using:Microsoft.Terminal.Settings.Model"
TabNavigation="Cycle"
IsTabStop="True"
AllowFocusOnInteraction="True"
@ -135,27 +135,27 @@ the MIT License. See LICENSE in the project root for license information. -->
to receive clicks _anywhere_ in its bounds. -->
<Grid
x:Name="_shadowBackdrop"
Background="Transparent"
Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="3"
Grid.RowSpan="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
x:Name="_shadowBackdrop"
Background="Transparent"
Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="3"
Grid.RowSpan="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
</Grid>
<Grid
x:Name="_backdrop"
Style="{ThemeResource CommandPaletteBackground}"
CornerRadius="{ThemeResource ControlCornerRadius}"
PointerPressed="_backdropPointerPressed"
Margin="8"
Grid.Column="1"
Grid.Row="0"
Windows10version1903:Shadow="{StaticResource CommandPaletteShadow}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top">
x:Name="_backdrop"
Style="{ThemeResource CommandPaletteBackground}"
CornerRadius="{ThemeResource ControlCornerRadius}"
PointerPressed="_backdropPointerPressed"
Margin="8"
Grid.Column="1"
Grid.Row="0"
Windows10version1903:Shadow="{StaticResource CommandPaletteShadow}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@ -164,15 +164,28 @@ the MIT License. See LICENSE in the project root for license information. -->
</Grid.RowDefinitions>
<TextBox
Grid.Row="0"
x:Name="_searchBox"
Margin="8"
IsSpellCheckEnabled="False"
TextChanged="_filterTextChanged"
PlaceholderText="{x:Bind SearchBoxText, Mode=OneWay}"
Text="">
Grid.Row="0"
x:Name="_searchBox"
Margin="8"
Padding="18,8,8,8"
IsSpellCheckEnabled="False"
TextChanged="_filterTextChanged"
PlaceholderText="{x:Bind SearchBoxPlaceholderText, Mode=OneWay}"
Text="">
</TextBox>
<TextBlock
Grid.Row="0"
x:Name="_prefixCharacter"
Margin="16,16,0,-8"
FontSize="14"
Visibility="{x:Bind PrefixCharacter,
Mode=OneWay,
Converter={StaticResource ParentCommandVisibilityConverter}}"
Text="{x:Bind PrefixCharacter, Mode=OneWay}"
>
</TextBlock>
<TextBlock
Padding="16, 0, 16, 4"
x:Name="_parentCommandText"
@ -194,17 +207,17 @@ the MIT License. See LICENSE in the project root for license information. -->
</TextBlock>
<ListView
Grid.Row="2"
x:Name="_filteredActionsView"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SelectionMode="Single"
CanReorderItems="False"
AllowDrop="False"
IsItemClickEnabled="True"
ItemClick="_listItemClicked"
PreviewKeyDown="_keyDownHandler"
ItemsSource="{x:Bind FilteredActions}">
Grid.Row="2"
x:Name="_filteredActionsView"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SelectionMode="Single"
CanReorderItems="False"
AllowDrop="False"
IsItemClickEnabled="True"
ItemClick="_listItemClicked"
PreviewKeyDown="_keyDownHandler"
ItemsSource="{x:Bind FilteredActions}">
<ItemsControl.ItemTemplate >
<DataTemplate x:DataType="SettingsModel:Command">