Skip to content

Commit

Permalink
Swap the command palette modes for the prefix > (#7935)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
zadjii-msft authored Oct 15, 2020
1 parent 30e363e commit bd7cd55
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 97 deletions.
113 changes: 57 additions & 56 deletions src/cascadia/TerminalApp/CommandPalette.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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">");

// This will conveniently move the cursor to the end of the
// text input for us.
_searchBox().Select(_searchBox().Text().size(), 0);
}
_searchBox().Text(L"");
}

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);
Expand Down Expand Up @@ -480,36 +466,29 @@ 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())
{
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:
Expand All @@ -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 };
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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.
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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();
Expand Down
7 changes: 5 additions & 2 deletions src/cascadia/TerminalApp/CommandPalette.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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,
Expand Down Expand Up @@ -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;

Expand Down
3 changes: 2 additions & 1 deletion src/cascadia/TerminalApp/CommandPalette.idl
Original file line number Diff line number Diff line change
Expand Up @@ -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; };

Expand Down
89 changes: 51 additions & 38 deletions src/cascadia/TerminalApp/CommandPalette.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"/>
Expand All @@ -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"
Expand All @@ -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">
Expand Down

0 comments on commit bd7cd55

Please sign in to comment.