diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index a8d0cb12d88..307ca975ba2 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -172,25 +172,59 @@ namespace winrt::TerminalApp::implementation } else if (key == VirtualKey::Enter) { - // Action Mode: Dispatch the action of the selected command. - - if (const auto selectedItem = _filteredActionsView().SelectedItem()) + // Action, TabSwitch or TabSearchMode Mode: Dispatch the action of the selected command. + if (_currentMode != CommandPaletteMode::CommandlineMode) { - _dispatchCommand(selectedItem.try_as()); + if (const auto selectedItem = _filteredActionsView().SelectedItem()) + { + _dispatchCommand(selectedItem.try_as()); + } + } + // Commandline Mode: Use the input to synthesize an ExecuteCommandline action + else if (_currentMode == CommandPaletteMode::CommandlineMode) + { + _dispatchCommandline(); } e.Handled(true); } else if (key == VirtualKey::Escape) { - // Action Mode: Dismiss the palette if the text is empty, otherwise clear the search string. - if (_searchBox().Text().empty()) + // Action, TabSearch, TabSwitch Mode: Dismiss the palette if the + // text is empty, otherwise clear the search string. + if (_currentMode != CommandPaletteMode::CommandlineMode) { - _dismissPalette(); + if (_searchBox().Text().empty()) + { + _dismissPalette(); + } + else + { + _searchBox().Text(L""); + } } - else + else if (_currentMode == CommandPaletteMode::CommandlineMode) { - _searchBox().Text(L""); + 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); + } } e.Handled(true); @@ -362,6 +396,8 @@ namespace winrt::TerminalApp::implementation case CommandPaletteMode::TabSearchMode: case CommandPaletteMode::TabSwitchMode: return _allTabActions; + case CommandPaletteMode::CommandlineMode: + return winrt::single_threaded_vector(); default: return _allCommands; } @@ -422,6 +458,72 @@ 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. + // Arguments: + // - + // Return Value: + // - the string of input following the prefix character. + std::wstring CommandPalette::_getPostPrefixInput() + { + 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" "); + if (firstNonSpace == std::wstring::npos) + { + // All the following characters are whitespace. + return L""; + } + + return rawCmdline.substr(firstNonSpace); + } + + // Method Description: + // - Dispatch the current search text as a ExecuteCommandline action. + // Arguments: + // - + // Return Value: + // - + void CommandPalette::_dispatchCommandline() + { + const auto input = _getPostPrefixInput(); + if (input.empty()) + { + return; + } + winrt::hstring cmdline{ input }; + + // Build the ExecuteCommandline action from the values we've parsed on the commandline. + auto executeActionAndArgs = winrt::make_self(); + executeActionAndArgs->Action(ShortcutAction::ExecuteCommandline); + auto args = winrt::make_self(); + args->Commandline(cmdline); + executeActionAndArgs->Args(*args); + + 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 (_dispatch.DoAction(*executeActionAndArgs)) + { + _close(); + } + } + // 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 @@ -452,12 +554,36 @@ namespace winrt::TerminalApp::implementation void CommandPalette::_filterTextChanged(IInspectable const& /*sender*/, Windows::UI::Xaml::RoutedEventArgs const& /*args*/) { + if (_currentMode == CommandPaletteMode::CommandlineMode || _currentMode == CommandPaletteMode::ActionMode) + { + _evaluatePrefix(); + } + _updateFilteredActions(); _filteredActionsView().SelectedIndex(0); _noMatchesText().Visibility(_filteredActions.Size() > 0 ? Visibility::Collapsed : Visibility::Visible); } + void CommandPalette::_evaluatePrefix() + { + auto newMode = CommandPaletteMode::ActionMode; + + auto inputText = _searchBox().Text(); + if (inputText.size() > 0) + { + if (inputText[0] == L'>') + { + newMode = CommandPaletteMode::CommandlineMode; + } + } + + if (newMode != _currentMode) + { + _switchToMode(newMode); + } + } + Collections::IObservableVector CommandPalette::FilteredActions() { return _filteredActions; @@ -511,6 +637,10 @@ namespace winrt::TerminalApp::implementation ControlName(RS_(L"TabSwitcherControlName")); break; } + case CommandPaletteMode::CommandlineMode: + NoMatchesText(RS_(L"CmdPalCommandlinePrompt")); + ControlName(RS_(L"CommandPaletteControlName")); + break; case CommandPaletteMode::ActionMode: default: SearchBoxText(RS_(L"CommandPalette_SearchBox/PlaceholderText")); @@ -666,6 +796,12 @@ namespace winrt::TerminalApp::implementation // - void CommandPalette::_updateFilteredActions() { + if (_currentMode == CommandPaletteMode::CommandlineMode) + { + _filteredActions.Clear(); + return; + } + auto actions = _collectFilteredActions(); // Make _filteredActions look identical to actions, using only Insert and Remove. diff --git a/src/cascadia/TerminalApp/CommandPalette.h b/src/cascadia/TerminalApp/CommandPalette.h index 97b0f0c8d25..65dd39ab552 100644 --- a/src/cascadia/TerminalApp/CommandPalette.h +++ b/src/cascadia/TerminalApp/CommandPalette.h @@ -12,7 +12,8 @@ namespace winrt::TerminalApp::implementation { ActionMode = 0, TabSearchMode, - TabSwitchMode + TabSwitchMode, + CommandlineMode }; struct CommandPalette : CommandPaletteT @@ -80,6 +81,9 @@ namespace winrt::TerminalApp::implementation CommandPaletteMode _currentMode; void _switchToMode(CommandPaletteMode mode); + void _evaluatePrefix(); + std::wstring _getPostPrefixInput(); + Microsoft::Terminal::TerminalControl::IKeyBindings _bindings; // Tab Switcher @@ -92,7 +96,7 @@ namespace winrt::TerminalApp::implementation winrt::Windows::UI::Xaml::Controls::ListView::SizeChanged_revoker _sizeChangedRevoker; void _dispatchCommand(const TerminalApp::Command& command); - + void _dispatchCommandline(); void _dismissPalette(); }; } diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index f7d58b6d826..f36568f8e8e 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -594,6 +594,10 @@ No matching tab name + + Enter a wt commandline to run + {Locked="wt"} + Crimson