Skip to content

Commit

Permalink
Add support for opening the Suggestions UI with recent commands (#14943)
Browse files Browse the repository at this point in the history
This adds support for a new action, `showSuggestions`, as described in
#14864. This adds just one `source` currently, `recentCommands`. This
requires shell integration to be enabled in the shell to work properly.
When it is enabled, activating that action will invoke the suggestions
UI as a palette, populated with `sendInput` actions for each of the
user's recent commands.

* These don't persist across reboots. 
* These are per-control.

There's mild plans to remedy that in a follow-up, though that needs a
bit more design consideration.

Closes #14779
  • Loading branch information
zadjii-msft authored Aug 15, 2023
1 parent b556594 commit e5a430f
Show file tree
Hide file tree
Showing 18 changed files with 212 additions and 16 deletions.
28 changes: 28 additions & 0 deletions src/cascadia/TerminalApp/AppActionHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,34 @@ namespace winrt::TerminalApp::implementation
}
}

void TerminalPage::_HandleSuggestions(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (args)
{
if (const auto& realArgs = args.ActionArgs().try_as<SuggestionsArgs>())
{
auto source = realArgs.Source();

switch (source)
{
case SuggestionsSource::CommandHistory:
{
if (const auto& control{ _GetActiveControl() })
{
const auto context = control.CommandHistory();
_OpenSuggestions(control,
Command::HistoryToCommands(context.History(), context.CurrentCommandline(), false),
SuggestionsMode::Palette);
}
args.Handled(true);
}
break;
}
}
}
}

void TerminalPage::_HandleColorSelection(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
Expand Down
32 changes: 17 additions & 15 deletions src/cascadia/TerminalApp/SuggestionsControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,17 @@ namespace winrt::TerminalApp::implementation
}
});

// 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*/) {
// This does only fire once, when the size changes, which is the
// very first time it's opened. It does not fire for subsequent
// openings.

_sizeChangedRevoker.revoke();
// When we're in BottomUp mode, we need to adjust our own position
// so that our bottom is aligned with our origin. This will ensure
// that as the menu changes in size (as we filter results), the menu
// stays "attached" to the cursor.
if (Visibility() == Visibility::Visible && _direction == TerminalApp::SuggestionsDirection::BottomUp)
{
auto m = this->Margin();
m.Top = (_anchor.Y - ActualHeight());
this->Margin(m);
}
});

_filteredActionsView().SelectionChanged({ this, &SuggestionsControl::_selectedCommandChanged });
Expand Down Expand Up @@ -851,11 +851,13 @@ namespace winrt::TerminalApp::implementation
}
}
}
if (_mode == SuggestionsMode::Palette)
{
// We want to present the commands sorted
std::sort(actions.begin(), actions.end(), FilteredCommand::Compare);
}

// No sorting in palette mode, so results are still filtered, but in the
// original order. This feels more right for something like
// recentCommands.
//
// This is in contrast to the Command Palette, which always sorts its
// actions.

// Adjust the order of the results depending on if we're top-down or
// bottom up. This way, the "first" / "best" match is always closest to
Expand Down
36 changes: 36 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1951,6 +1951,42 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return hstring{ str };
}

// Get all of our recent commands. This will only really work if the user has enabled shell integration.
Control::CommandHistoryContext ControlCore::CommandHistory() const
{
auto terminalLock = _terminal->LockForWriting();
const auto& textBuffer = _terminal->GetTextBuffer();

std::vector<winrt::hstring> commands;
for (const auto& mark : _terminal->GetScrollMarks())
{
// The command text is between the `end` (which denotes the end of
// the prompt) and the `commandEnd`.
bool markHasCommand = mark.commandEnd.has_value() &&
mark.commandEnd != mark.end;
if (!markHasCommand)
{
continue;
}

// Get the text of the command
const auto line = mark.end.y;
const auto& row = textBuffer.GetRowByOffset(line);
const auto commandText = row.GetText(mark.end.x, mark.commandEnd->x);

// Trim off trailing spaces.
const auto strEnd = commandText.find_last_not_of(UNICODE_SPACE);
if (strEnd != std::string::npos)
{
const auto trimmed = commandText.substr(0, strEnd + 1);
commands.push_back(winrt::hstring{ trimmed });
}
}
auto context = winrt::make_self<CommandHistoryContext>(std::move(commands));

return *context;
}

Core::Scheme ControlCore::ColorScheme() const noexcept
{
Core::Scheme s;
Expand Down
12 changes: 12 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "ControlCore.g.h"
#include "SelectionColor.g.h"
#include "CommandHistoryContext.g.h"
#include "ControlSettings.h"
#include "../../audio/midi/MidiAudio.hpp"
#include "../../renderer/base/Renderer.hpp"
Expand Down Expand Up @@ -53,6 +54,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
til::property<til::color> Color;
til::property<bool> IsIndex16;
};
struct CommandHistoryContext : CommandHistoryContextT<CommandHistoryContext>
{
til::property<Windows::Foundation::Collections::IVector<winrt::hstring>> History;
til::property<winrt::hstring> CurrentCommandline;

CommandHistoryContext(std::vector<winrt::hstring>&& history)
{
History(winrt::single_threaded_vector<winrt::hstring>(std::move(history)));
}
};

struct ControlCore : ControlCoreT<ControlCore>
{
Expand Down Expand Up @@ -213,6 +224,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void SetReadOnlyMode(const bool readOnlyState);

hstring ReadEntireBuffer() const;
Control::CommandHistoryContext CommandHistory() const;

static bool IsVintageOpacityAvailable() noexcept;

Expand Down
7 changes: 7 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.idl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ namespace Microsoft.Terminal.Control
Boolean IsIndex16;
};

[default_interface] runtimeclass CommandHistoryContext
{
IVector<String> History { get; };
String CurrentCommandline { get; };
};

[default_interface] runtimeclass ControlCore : ICoreState
{
ControlCore(IControlSettings settings,
Expand Down Expand Up @@ -136,6 +142,7 @@ namespace Microsoft.Terminal.Control
void EnablePainting();

String ReadEntireBuffer();
CommandHistoryContext CommandHistory();

void AdjustOpacity(Double Opacity, Boolean relative);
void WindowVisibilityChanged(Boolean showOrHide);
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3377,6 +3377,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
return _core.ReadEntireBuffer();
}
Control::CommandHistoryContext TermControl::CommandHistory() const
{
return _core.CommandHistory();
}

Core::Scheme TermControl::ColorScheme() const noexcept
{
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
static Windows::UI::Xaml::Thickness ParseThicknessFromPadding(const hstring padding);

hstring ReadEntireBuffer() const;
Control::CommandHistoryContext CommandHistory() const;

winrt::Microsoft::Terminal::Core::Scheme ColorScheme() const noexcept;
void ColorScheme(const winrt::Microsoft::Terminal::Core::Scheme& scheme) const noexcept;
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/TermControl.idl
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ namespace Microsoft.Terminal.Control
void SetReadOnly(Boolean readOnlyState);

String ReadEntireBuffer();
CommandHistoryContext CommandHistory();

void AdjustOpacity(Double Opacity, Boolean relative);

Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ static constexpr std::string_view SwitchToTabKey{ "switchToTab" };
static constexpr std::string_view TabSearchKey{ "tabSearch" };
static constexpr std::string_view ToggleAlwaysOnTopKey{ "toggleAlwaysOnTop" };
static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" };
static constexpr std::string_view SuggestionsKey{ "showSuggestions" };
static constexpr std::string_view ToggleFocusModeKey{ "toggleFocusMode" };
static constexpr std::string_view SetFocusModeKey{ "setFocusMode" };
static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" };
Expand Down Expand Up @@ -386,6 +387,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{ ShortcutAction::TabSearch, RS_(L"TabSearchCommandKey") },
{ ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") },
{ ShortcutAction::ToggleCommandPalette, MustGenerate },
{ ShortcutAction::Suggestions, MustGenerate },
{ ShortcutAction::ToggleFocusMode, RS_(L"ToggleFocusModeCommandKey") },
{ ShortcutAction::SetFocusMode, MustGenerate },
{ ShortcutAction::ToggleFullscreen, RS_(L"ToggleFullscreenCommandKey") },
Expand Down
11 changes: 11 additions & 0 deletions src/cascadia/TerminalSettingsModel/ActionArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "AddMarkArgs.g.cpp"
#include "FindMatchArgs.g.cpp"
#include "ToggleCommandPaletteArgs.g.cpp"
#include "SuggestionsArgs.g.cpp"
#include "NewWindowArgs.g.cpp"
#include "PrevTabArgs.g.cpp"
#include "NextTabArgs.g.cpp"
Expand Down Expand Up @@ -706,6 +707,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return RS_(L"ToggleCommandPaletteCommandKey");
}

winrt::hstring SuggestionsArgs::GenerateName() const
{
switch (Source())
{
case SuggestionsSource::CommandHistory:
return RS_(L"SuggestionsCommandHistoryCommandKey");
}
return RS_(L"SuggestionsCommandKey");
}

winrt::hstring FindMatchArgs::GenerateName() const
{
switch (Direction())
Expand Down
8 changes: 8 additions & 0 deletions src/cascadia/TerminalSettingsModel/ActionArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "AddMarkArgs.g.h"
#include "MoveTabArgs.g.h"
#include "ToggleCommandPaletteArgs.g.h"
#include "SuggestionsArgs.g.h"
#include "FindMatchArgs.g.h"
#include "NewWindowArgs.g.h"
#include "PrevTabArgs.g.h"
Expand Down Expand Up @@ -213,6 +214,10 @@ private: \
#define TOGGLE_COMMAND_PALETTE_ARGS(X) \
X(CommandPaletteLaunchMode, LaunchMode, "launchMode", false, CommandPaletteLaunchMode::Action)

////////////////////////////////////////////////////////////////////////////////
#define SUGGESTIONS_ARGS(X) \
X(SuggestionsSource, Source, "source", false, SuggestionsSource::Tasks)

////////////////////////////////////////////////////////////////////////////////
#define FIND_MATCH_ARGS(X) \
X(FindMatchDirection, Direction, "direction", args->Direction() == FindMatchDirection::None, FindMatchDirection::None)
Expand Down Expand Up @@ -709,6 +714,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation

ACTION_ARGS_STRUCT(ToggleCommandPaletteArgs, TOGGLE_COMMAND_PALETTE_ARGS);

ACTION_ARGS_STRUCT(SuggestionsArgs, SUGGESTIONS_ARGS);

ACTION_ARGS_STRUCT(FindMatchArgs, FIND_MATCH_ARGS);

ACTION_ARGS_STRUCT(PrevTabArgs, PREV_TAB_ARGS);
Expand Down Expand Up @@ -836,6 +843,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
BASIC_FACTORY(ClearBufferArgs);
BASIC_FACTORY(MultipleActionsArgs);
BASIC_FACTORY(AdjustOpacityArgs);
BASIC_FACTORY(SuggestionsArgs);
BASIC_FACTORY(SelectCommandArgs);
BASIC_FACTORY(SelectOutputArgs);
}
13 changes: 13 additions & 0 deletions src/cascadia/TerminalSettingsModel/ActionArgs.idl
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ namespace Microsoft.Terminal.Settings.Model
ToCurrent,
ToMouse,
};
enum SuggestionsSource
{
Tasks,
CommandHistory,
DirectoryHistory,
};

[default_interface] runtimeclass NewTerminalArgs {
NewTerminalArgs();
Expand Down Expand Up @@ -318,6 +324,13 @@ namespace Microsoft.Terminal.Settings.Model
CommandPaletteLaunchMode LaunchMode { get; };
};

[default_interface] runtimeclass SuggestionsArgs : IActionArgs
{
SuggestionsArgs();
SuggestionsArgs(SuggestionsSource source);
SuggestionsSource Source { get; };
};

[default_interface] runtimeclass FindMatchArgs : IActionArgs
{
FindMatchArgs(FindMatchDirection direction);
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalSettingsModel/AllShortcutActions.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
ON_ALL_ACTIONS(MarkMode) \
ON_ALL_ACTIONS(ToggleBlockSelection) \
ON_ALL_ACTIONS(SwitchSelectionEndpoint) \
ON_ALL_ACTIONS(Suggestions) \
ON_ALL_ACTIONS(ColorSelection) \
ON_ALL_ACTIONS(ShowContextMenu) \
ON_ALL_ACTIONS(ExpandSelectionToWord) \
Expand Down Expand Up @@ -150,6 +151,7 @@
ON_ALL_ACTIONS_WITH_ARGS(ClearBuffer) \
ON_ALL_ACTIONS_WITH_ARGS(MultipleActions) \
ON_ALL_ACTIONS_WITH_ARGS(AdjustOpacity) \
ON_ALL_ACTIONS_WITH_ARGS(Suggestions) \
ON_ALL_ACTIONS_WITH_ARGS(SelectCommand) \
ON_ALL_ACTIONS_WITH_ARGS(SelectOutput) \
ON_ALL_ACTIONS_WITH_ARGS(ColorSelection)
49 changes: 49 additions & 0 deletions src/cascadia/TerminalSettingsModel/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -730,4 +730,53 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation

return winrt::single_threaded_vector<Model::Command>(std::move(result));
}

// Method description:
// * Convert the list of recent commands into a list of sendInput actions to
// send those commands.
// * We'll give each command a "history" icon.
// * If directories is true, we'll prepend "cd " to each command, so that
// the command will be run as a directory change instead.
IVector<Model::Command> Command::HistoryToCommands(IVector<winrt::hstring> history,
winrt::hstring /*currentCommandline*/,
bool directories)
{
std::wstring cdText = directories ? L"cd " : L"";

This comment has been minimized.

Copy link
@DHowett

DHowett Aug 15, 2023

Member

This came up in the spec, but we need different types of cd for different shells and different operating systems.

auto result = std::vector<Model::Command>();

// Use this map to discard duplicates.
std::unordered_map<std::wstring_view, bool> foundCommands{};

This comment has been minimized.

Copy link
@DHowett

DHowett Aug 15, 2023

Member

this is an std::unordered_set 😉


auto backspaces = std::wstring(::base::saturated_cast<size_t>(0), L'\x7f');

// Iterate in reverse over the history, so that most recent commands are first
for (auto i = history.Size(); i > 0; i--)
{
std::wstring_view line{ history.GetAt(i - 1) };

if (line.empty())
{
continue;
}
if (foundCommands.contains(line))
{
continue;
}
auto args = winrt::make_self<SendInputArgs>(
winrt::hstring{ fmt::format(L"{}{}{}", cdText, backspaces, line) });

Model::ActionAndArgs actionAndArgs{ ShortcutAction::SendInput, *args };

auto command = winrt::make_self<Command>();
command->_ActionAndArgs = actionAndArgs;
command->_name = winrt::hstring{ line };
command->_iconPath = directories ?
L"\ue8da" : // OpenLocal (a folder with an arrow pointing up)
L"\ue81c"; // History icon
result.push_back(*command);
foundCommands[line] = true;
}

return winrt::single_threaded_vector<Model::Command>(std::move(result));
}
}
3 changes: 3 additions & 0 deletions src/cascadia/TerminalSettingsModel/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void IconPath(const hstring& val);

static Windows::Foundation::Collections::IVector<Model::Command> ParsePowerShellMenuComplete(winrt::hstring json, int32_t replaceLength);
static Windows::Foundation::Collections::IVector<Model::Command> HistoryToCommands(Windows::Foundation::Collections::IVector<winrt::hstring> history,
winrt::hstring currentCommandline,
bool directories);

WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None);
WINRT_PROPERTY(Model::ActionAndArgs, ActionAndArgs);
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalSettingsModel/Command.idl
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,7 @@ namespace Microsoft.Terminal.Settings.Model
Windows.Foundation.Collections.IMapView<String, Command> NestedCommands { get; };

static IVector<Command> ParsePowerShellMenuComplete(String json, Int32 replaceLength);
static IVector<Command> HistoryToCommands(IVector<String> commandHistory, String commandline, Boolean directories);

}
}
Loading

0 comments on commit e5a430f

Please sign in to comment.