Skip to content

Commit

Permalink
Add support for "Tasks" in the Suggestions UI (#15664)
Browse files Browse the repository at this point in the history
_targets #15027_

Adds a new suggestion source, `tasks`, that allows a user to open the
Suggestions UI with `sendInput` commands saved in their settings.
`source` becomes a flag setting, so it can be combined like so:

```json
        {
            "keys": "ctrl+shift+h", "command": { "action": "suggestions", "source": "commandHistory", "useCommandline":true },
        },
        {
            "keys": "ctrl+shift+y", "command": { "action": "suggestions", "source": "tasks", "useCommandline":false },
        },
        {
            "keys": "ctrl+shift+b", "command": { "action": "suggestions", "source": ["all"], "useCommandline":true },
        },
```

If a nested command has `sendInput` commands underneath it, this will
build a tree of commands that only include `sendInput`s as leaves (but
leave the rest of the nesting structure intact).


## References and Relevant Issues

Closes #1595

See also #13445 
As spec'd in #14864 

## Validation Steps Performed

Tested manually
  • Loading branch information
zadjii-msft authored Aug 15, 2023
1 parent 3afe7a8 commit 127df07
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 56 deletions.
54 changes: 54 additions & 0 deletions doc/cascadia/profiles.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,32 @@
}
]
},
"BuiltinSuggestionSource": {
"enum": [
"commandHistory",
"tasks",
"all"
],
"type": "string"
},
"SuggestionSource": {
"default": "all",
"description": "Either a single suggestion source, or an array of sources to concatenate. Built-in sources include `commandHistory`, `directoryHistory`, and `tasks`. The special value `all` indicates all suggestion sources should be included",
"$comment": "`tasks` and `local` are sources that would be added by the Tasks feature, as a follow-up",
"oneOf": [
{
"type": [ "string", "null", "BuiltinSuggestionSource" ]
},
{
"type": "array",
"items": { "type": "BuiltinSuggestionSource" }
},
{
"type": "array",
"items": { "type": "string" }
}
]
},
"AppearanceConfig": {
"properties": {
"colorScheme": {
Expand Down Expand Up @@ -398,6 +424,7 @@
"sendInput",
"setColorScheme",
"setTabColor",
"showSuggestions",
"splitPane",
"switchToTab",
"tabSearch",
Expand Down Expand Up @@ -1767,6 +1794,30 @@
}
]
},
"ShowSuggestionsAction": {
"description": "Arguments corresponding to a Open Suggestions Action",
"allOf": [
{
"$ref": "#/$defs/ShortcutAction"
},
{
"properties": {
"action": {
"type": "string",
"const": "showSuggestions"
},
"source": {
"$ref": "#/$defs/SuggestionSource",
"description": "Which suggestion sources to filter."
},
"useCommandline": {
"default": false,
"description": "When set to `true`, the current commandline the user has typed will pre-populate the filter of the Suggestions UI. This requires that the user has enabled shell integration in their shell's config. When set to false, the filter will start empty."
}
}
}
]
},
"ShowCloseButton": {
"enum": [
"always",
Expand Down Expand Up @@ -2037,6 +2088,9 @@
{
"$ref": "#/$defs/SearchWebAction"
},
{
"$ref": "#/$defs/ShowSuggestionsAction"
},
{
"type": "null"
}
Expand Down
63 changes: 50 additions & 13 deletions src/cascadia/TerminalApp/AppActionHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1264,25 +1264,62 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = args.ActionArgs().try_as<SuggestionsArgs>())
{
auto source = realArgs.Source();

switch (source)
{
case SuggestionsSource::CommandHistory:
const auto source = realArgs.Source();
std::vector<Command> commandsCollection;
Control::CommandHistoryContext context{ nullptr };
winrt::hstring currentCommandline = L"";

// If the user wanted to use the current commandline to filter results,
// OR they wanted command history (or some other source that
// requires context from the control)
// then get that here.
const bool shouldGetContext = realArgs.UseCommandline() ||
WI_IsFlagSet(source, SuggestionsSource::CommandHistory);
if (shouldGetContext)
{
if (const auto& control{ _GetActiveControl() })
{
const auto context = control.CommandHistory();
const auto& currentCmd{ realArgs.UseCommandline() ? context.CurrentCommandline() : L"" };
_OpenSuggestions(control,
Command::HistoryToCommands(context.History(), currentCmd, false),
SuggestionsMode::Palette,
currentCmd);
context = control.CommandHistory();
if (context)
{
currentCommandline = context.CurrentCommandline();
}
}
args.Handled(true);
}
break;

// Aggregate all the commands from the different sources that
// the user selected.

// Tasks are all the sendInput commands the user has saved in
// their settings file. Ask the ActionMap for those.
if (WI_IsFlagSet(source, SuggestionsSource::Tasks))
{
const auto tasks = _settings.GlobalSettings().ActionMap().FilterToSendInput(currentCommandline);
for (const auto& t : tasks)
{
commandsCollection.push_back(t);
}
}

// Command History comes from the commands in the buffer,
// assuming the user has enabled shell integration. Get those
// from the active control.
if (WI_IsFlagSet(source, SuggestionsSource::CommandHistory) &&
context != nullptr)
{
const auto recentCommands = Command::HistoryToCommands(context.History(), currentCommandline, false);
for (const auto& t : recentCommands)
{
commandsCollection.push_back(t);
}
}

// Open the palette with all these commands in it.
_OpenSuggestions(_GetActiveControl(),
winrt::single_threaded_vector<Command>(std::move(commandsCollection)),
SuggestionsMode::Palette,
currentCommandline);
args.Handled(true);
}
}
}
Expand Down
37 changes: 28 additions & 9 deletions src/cascadia/TerminalSettingsModel/ActionArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -709,23 +709,42 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation

winrt::hstring SuggestionsArgs::GenerateName() const
{
auto base{ RS_(L"SuggestionsCommandKey") };
switch (Source())
std::wstringstream ss;
ss << RS_(L"SuggestionsCommandKey").c_str();

if (UseCommandline())
{
case SuggestionsSource::CommandHistory:
base = RS_(L"SuggestionsCommandHistoryCommandKey");
ss << L", useCommandline:true";
}

if (UseCommandline())
// All of the source values will leave a trailing ", " that we need to chop later:
ss << L", source: ";
const auto source = Source();
if (source == SuggestionsSource::All)
{
return winrt::hstring{
fmt::format(L"{}, useCommandline:true", std::wstring_view(base))
};
ss << L"all, ";
}
else if (source == static_cast<SuggestionsSource>(0))
{
ss << L"none, ";
}
else
{
return base;
if (WI_IsFlagSet(source, SuggestionsSource::Tasks))
{
ss << L"tasks, ";
}

if (WI_IsFlagSet(source, SuggestionsSource::CommandHistory))
{
ss << L"commandHistory, ";
}
}
// Chop off the last ","
auto result = ss.str();
// use `resize`, to avoid duplicating the entire string. (substr doesn't create a view.)
result.resize(result.size() - 2);
return winrt::hstring{ result };
}

winrt::hstring FindMatchArgs::GenerateName() const
Expand Down
9 changes: 6 additions & 3 deletions src/cascadia/TerminalSettingsModel/ActionArgs.idl
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,14 @@ namespace Microsoft.Terminal.Settings.Model
ToCurrent,
ToMouse,
};

[flags]
enum SuggestionsSource
{
Tasks,
CommandHistory,
DirectoryHistory,
Tasks = 0x1,
CommandHistory = 0x2,
DirectoryHistory = 0x4,
All = 0xffffffff,
};

[default_interface] runtimeclass NewTerminalArgs {
Expand Down
68 changes: 68 additions & 0 deletions src/cascadia/TerminalSettingsModel/ActionMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -931,4 +931,72 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
return _ExpandedCommandsCache;
}

IVector<Model::Command> _filterToSendInput(IMapView<hstring, Model::Command> nameMap,
winrt::hstring currentCommandline)
{
auto results = winrt::single_threaded_vector<Model::Command>();

const auto numBackspaces = currentCommandline.size();
// Helper to clone a sendInput command into a new Command, with the
// input trimmed to account for the currentCommandline
auto createInputAction = [&](const Model::Command& command) -> Model::Command {
winrt::com_ptr<implementation::Command> cmdImpl;
cmdImpl.copy_from(winrt::get_self<implementation::Command>(command));

const auto inArgs{ command.ActionAndArgs().Args().try_as<Model::SendInputArgs>() };

auto args = winrt::make_self<SendInputArgs>(
winrt::hstring{ fmt::format(FMT_COMPILE(L"{:\x7f^{}}{}"),
L"",
numBackspaces,
(std::wstring_view)(inArgs ? inArgs.Input() : L"")) });
Model::ActionAndArgs actionAndArgs{ ShortcutAction::SendInput, *args };

auto copy = cmdImpl->Copy();
copy->ActionAndArgs(actionAndArgs);

return *copy;
};

// iterate over all the commands in all our actions...
for (auto&& [name, command] : nameMap)
{
// If this is not a nested command, and it's a sendInput command...
if (!command.HasNestedCommands() &&
command.ActionAndArgs().Action() == ShortcutAction::SendInput)
{
// copy it into the results.
results.Append(createInputAction(command));
}
// If this is nested...
else if (command.HasNestedCommands())
{
// Look for any sendInput commands nested underneath us
auto innerResults = _filterToSendInput(command.NestedCommands(), currentCommandline);

if (innerResults.Size() > 0)
{
// This command did have at least one sendInput under it

// Create a new Command, which is a copy of this Command,
// which only has SendInputs in it
winrt::com_ptr<implementation::Command> cmdImpl;
cmdImpl.copy_from(winrt::get_self<implementation::Command>(command));
auto copy = cmdImpl->Copy();
copy->NestedCommands(innerResults.GetView());

results.Append(*copy);
}
}
}

return results;
}

IVector<Model::Command> ActionMap::FilterToSendInput(
winrt::hstring currentCommandline)
{
return _filterToSendInput(NameMap(), currentCommandline);
}
}
2 changes: 2 additions & 0 deletions src/cascadia/TerminalSettingsModel/ActionMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void ExpandCommands(const Windows::Foundation::Collections::IVectorView<Model::Profile>& profiles,
const Windows::Foundation::Collections::IMapView<winrt::hstring, Model::ColorScheme>& schemes);

winrt::Windows::Foundation::Collections::IVector<Model::Command> FilterToSendInput(winrt::hstring currentCommandline);

private:
std::optional<Model::Command> _GetActionByID(const InternalActionID actionID) const;
std::optional<Model::Command> _GetActionByKeyChordInternal(const Control::KeyChord& keys) const;
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalSettingsModel/ActionMap.idl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ namespace Microsoft.Terminal.Settings.Model
Windows.Foundation.Collections.IMapView<Microsoft.Terminal.Control.KeyChord, Command> GlobalHotkeys { get; };

IVector<Command> ExpandedCommands { get; };

IVector<Command> FilterToSendInput(String CurrentCommandline);
};

[default_interface] runtimeclass ActionMap : IActionMapView
Expand Down
13 changes: 12 additions & 1 deletion src/cascadia/TerminalSettingsModel/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return _subcommands ? _subcommands.GetView() : nullptr;
}

void Command::NestedCommands(const Windows::Foundation::Collections::IVectorView<Model::Command>& nested)
{
_subcommands = winrt::single_threaded_map<winrt::hstring, Model::Command>();

for (const auto& n : nested)
{
_subcommands.Insert(n.Name(), n);
}
}

// Function Description:
// - reports if the current command has nested commands
// - This CANNOT detect { "name": "foo", "commands": null }
Expand Down Expand Up @@ -752,7 +762,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// 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) };
const auto& element{ history.GetAt(i - 1) };
std::wstring_view line{ element };

if (line.empty())
{
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalSettingsModel/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
bool HasNestedCommands() const;
bool IsNestedCommand() const noexcept;
Windows::Foundation::Collections::IMapView<winrt::hstring, Model::Command> NestedCommands() const;
void NestedCommands(const Windows::Foundation::Collections::IVectorView<Model::Command>& nested);

bool HasName() const noexcept;
hstring Name() const noexcept;
Expand Down
Loading

0 comments on commit 127df07

Please sign in to comment.