Skip to content

Commit

Permalink
use suggestions UI instead
Browse files Browse the repository at this point in the history
  • Loading branch information
carlos-zamora committed Mar 26, 2024
1 parent c1e1eab commit 68ed03d
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 18 deletions.
12 changes: 11 additions & 1 deletion src/cascadia/TerminalApp/AppActionHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1308,7 +1308,7 @@ namespace winrt::TerminalApp::implementation
// requires context from the control)
// then get that here.
const bool shouldGetContext = realArgs.UseCommandline() ||
WI_IsFlagSet(source, SuggestionsSource::CommandHistory);
WI_IsAnyFlagSet(source, SuggestionsSource::CommandHistory | SuggestionsSource::WinGetCommandNotFound);
if (shouldGetContext)
{
if (const auto& control{ _GetActiveControl() })
Expand Down Expand Up @@ -1348,6 +1348,16 @@ namespace winrt::TerminalApp::implementation
}
}

if (WI_IsFlagSet(source, SuggestionsSource::WinGetCommandNotFound) &&
context != nullptr)
{
const auto recentCommands = Command::ToSendInputCommands(context.WinGetSuggestions());
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)),
Expand Down
36 changes: 26 additions & 10 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1672,7 +1672,7 @@ namespace winrt::TerminalApp::implementation

term.ShowWindowChanged({ get_weak(), &TerminalPage::_ShowWindowChangedHandler });

term.SearchMissingCommand({ get_weak(), &TerminalPage::_SearchMissingCommandHandler });
//term.SearchMissingCommand({ get_weak(), &TerminalPage::_SearchMissingCommandHandler });

// Don't even register for the event if the feature is compiled off.
if constexpr (Feature_ShellCompletions::IsEnabled())
Expand Down Expand Up @@ -2956,7 +2956,7 @@ namespace winrt::TerminalApp::implementation
ShowWindowChanged.raise(*this, args);
}

void TerminalPage::_SearchMissingCommandHandler(const IInspectable /*sender*/, const winrt::Microsoft::Terminal::Control::SearchMissingCommandEventArgs args)
void TerminalPage::_SearchMissingCommandHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::SearchMissingCommandEventArgs args)
{
#if 0
static constexpr CLSID CLSID_PackageManager = { 0xC53A4F16, 0x787E, 0x42A4, 0xB3, 0x04, 0x29, 0xEF, 0xFB, 0x4B, 0xF5, 0x97 }; //C53A4F16-787E-42A4-B304-29EFFB4BF597
Expand Down Expand Up @@ -3105,23 +3105,39 @@ namespace winrt::TerminalApp::implementation
//ShowCommandNotFoundInfoBar(suggestions, footer);
}
#elif defined(DEBUG) || defined(_DEBUG) || defined(DBG)
const bool tooManySuggestions = false;
const std::wstring searchOption = L"command";
//const bool tooManySuggestions = false;
//const std::wstring searchOption = L"command";
const std::wstring missingCmd = args.MissingCommand().data();
std::vector<std::wstring> pkgList = { L"pkg1", L"pkg2", L"pkg3" };
std::vector<std::wstring> suggestions;
std::vector<hstring> suggestions;
suggestions.reserve(pkgList.size());
for (auto pkg : pkgList)
{
suggestions.emplace_back(fmt::format(L"winget install --id {}", pkg));
}

std::wstring footer = tooManySuggestions ?
fmt::format(L"winget search --{} {}", searchOption, missingCmd) :
L"";
// This will come in on a background (not-UI, not output) thread.

// Parse the json string into a collection of actions
try
{
//auto commandsCollection = Command::ParsePowerShellMenuComplete(args.MenuJson(),
// args.ReplacementLength());

auto suggestionsWinRT = winrt::single_threaded_vector<hstring>(std::move(suggestions));
auto commandsCollection = Command::ToSendInputCommands(suggestionsWinRT);

// TODO CARLOS: no more info bar; replace!
//ShowCommandNotFoundInfoBar(suggestions, footer);
//auto weakThis{ get_weak() };
//Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis, commandsCollection, sender]() {
// // On the UI thread...
// if (const auto& page{ weakThis.get() })
// {
// // Open the Suggestions UI with the commands from the control
// page->_OpenSuggestions(sender.try_as<TermControl>(), commandsCollection, SuggestionsMode::Menu, L"");
// }
//});
}
CATCH_LOG();
#endif
}

Expand Down
167 changes: 165 additions & 2 deletions src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1585,9 +1585,168 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_midiAudio.PlayNote(reinterpret_cast<HWND>(_owningHwnd), noteNumber, velocity, std::chrono::duration_cast<std::chrono::milliseconds>(duration));
}

void ControlCore::_terminalSearchMissingCommand(std::wstring_view missingCommand)
void ControlCore::_terminalSearchMissingCommand(std::wstring_view /*missingCommand*/)
{
SearchMissingCommand.raise(*this, winrt::make<implementation::SearchMissingCommandEventArgs>(winrt::hstring{ missingCommand }));
#if 0
static constexpr CLSID CLSID_PackageManager = { 0xC53A4F16, 0x787E, 0x42A4, 0xB3, 0x04, 0x29, 0xEF, 0xFB, 0x4B, 0xF5, 0x97 }; //C53A4F16-787E-42A4-B304-29EFFB4BF597
static constexpr CLSID CLSID_FindPackagesOptions = { 0x572DED96, 0x9C60, 0x4526, { 0x8F, 0x92, 0xEE, 0x7D, 0x91, 0xD3, 0x8C, 0x1A } }; //572DED96-9C60-4526-8F92-EE7D91D38C1A
static constexpr CLSID CLSID_PackageMatchFilter = { 0xD02C9DAF, 0x99DC, 0x429C, { 0xB5, 0x03, 0x4E, 0x50, 0x4E, 0x4A, 0xB0, 0x00 } }; //D02C9DAF-99DC-429C-B503-4E504E4AB000

static constexpr unsigned int maxSuggestions = 5;
bool tooManySuggestions = false;

// TODO CARLOS: this is where we fail! "Class not registered" error
PackageManager pkgManager = winrt::create_instance<PackageManager>(CLSID_PackageManager, CLSCTX_ALL);
auto catalogRef = pkgManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog::OpenWindowsCatalog);
auto connectResult = catalogRef.Connect();
int retryCount = 0;
while (connectResult.Status() != ConnectResultStatus::Ok && retryCount < 3)
{
connectResult = catalogRef.Connect();
++retryCount;
}
if (connectResult.Status() != ConnectResultStatus::Ok)
{
return;
}
auto catalog = connectResult.PackageCatalog();

// Perform the query (search by command)
auto packageMatchFilter = winrt::create_instance<PackageMatchFilter>(CLSID_PackageMatchFilter, CLSCTX_ALL);
auto findPackagesOptions = winrt::create_instance<FindPackagesOptions>(CLSID_FindPackagesOptions, CLSCTX_ALL);

// Helper lambda to apply a filter to the query
auto applyPackageMatchFilter = [&packageMatchFilter, &findPackagesOptions](PackageMatchField field, PackageFieldMatchOption matchOption, hstring query) {
// Configure filter
packageMatchFilter.Field(field);
packageMatchFilter.Option(matchOption);
packageMatchFilter.Value(query);

// Apply filter
findPackagesOptions.ResultLimit(maxSuggestions + 1u);
findPackagesOptions.Filters().Clear();
findPackagesOptions.Filters().Append(packageMatchFilter);
};

// Helper lambda to retrieve the best matching package(s) from the query's result
auto tryGetBestMatchingPackage = [&tooManySuggestions](IVectorView<MatchResult> matches) {
std::vector<CatalogPackage> results;
results.reserve(std::min(matches.Size(), maxSuggestions));
if (matches.Size() == 1)
{
// One match --> return the package
results.emplace_back(matches.GetAt(0).CatalogPackage());
}
else if (matches.Size() > 1)
{
// Multiple matches --> display top 5 matches (prioritize best matches first)
std::queue<CatalogPackage> bestExactMatches, secondaryMatches, tertiaryMatches;
for (auto match : matches)
{
switch (match.MatchCriteria().Option())
{
case PackageFieldMatchOption::EqualsCaseInsensitive:
case PackageFieldMatchOption::Equals:
bestExactMatches.push(match.CatalogPackage());
break;
case PackageFieldMatchOption::StartsWithCaseInsensitive:
secondaryMatches.push(match.CatalogPackage());
break;
case PackageFieldMatchOption::ContainsCaseInsensitive:
tertiaryMatches.push(match.CatalogPackage());
break;
}
}

// Now return the top maxSuggestions
while (results.size() < maxSuggestions)
{
if (bestExactMatches.size() > 0)
{
results.emplace_back(bestExactMatches.front());
bestExactMatches.pop();
}
else if (secondaryMatches.size() > 0)
{
results.emplace_back(secondaryMatches.front());
secondaryMatches.pop();
}
else if (tertiaryMatches.size() > 0)
{
results.emplace_back(tertiaryMatches.front());
tertiaryMatches.pop();
}
else
{
break;
}
}
}
tooManySuggestions = matches.Size() > maxSuggestions;
return results;
};

// Search by command
auto missingCmd = args.MissingCommand();
std::wstring searchOption = L"command";
applyPackageMatchFilter(PackageMatchField::Command, PackageFieldMatchOption::StartsWithCaseInsensitive, missingCmd);
auto findPackagesResult = catalog.FindPackages(findPackagesOptions);
auto matches = findPackagesResult.Matches();
auto pkgList = tryGetBestMatchingPackage(matches);
if (pkgList.empty())
{
// No matches found --> search by name
applyPackageMatchFilter(PackageMatchField::Name, PackageFieldMatchOption::ContainsCaseInsensitive, missingCmd);

findPackagesResult = catalog.FindPackages(findPackagesOptions);
matches = findPackagesResult.Matches();
pkgList = tryGetBestMatchingPackage(matches);
searchOption = L"name";

if (pkgList.empty())
{
// No matches found --> search by moniker
applyPackageMatchFilter(PackageMatchField::Moniker, PackageFieldMatchOption::ContainsCaseInsensitive, missingCmd);

// Perform the query (search by name)
findPackagesResult = catalog.FindPackages(findPackagesOptions);
matches = findPackagesResult.Matches();
pkgList = tryGetBestMatchingPackage(matches);
searchOption = L"moniker";
}
}

// Display packages in UI
if (!pkgList.empty())
{
std::vector<std::wstring> suggestions;
suggestions.reserve(pkgList.size());
for (auto pkg : pkgList)
{
suggestions.emplace_back(fmt::format(L"winget install --id {}", pkg.Id()));
}

std::wstring footer = tooManySuggestions ?
fmt::format(L"winget search --{} {}", searchOption, missingCmd) :
L"";

// TODO CARLOS: no more info bar; replace!
//ShowCommandNotFoundInfoBar(suggestions, footer);
}
#elif defined(DEBUG) || defined(_DEBUG) || defined(DBG)
//const bool tooManySuggestions = false;
//const std::wstring searchOption = L"command";
//const std::wstring missingCmd = args.MissingCommand().data();
std::vector<std::wstring> pkgList = { L"pkg1", L"pkg2", L"pkg3" };
std::vector<hstring> suggestions;
suggestions.reserve(pkgList.size());
for (auto pkg : pkgList)
{
suggestions.emplace_back(fmt::format(L"winget install --id {}", pkg));
}

_cachedWinGetSuggestions = winrt::single_threaded_vector<hstring>(std::move(suggestions));
#endif
}

bool ControlCore::HasSelection() const
Expand Down Expand Up @@ -2127,6 +2286,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto context = winrt::make_self<CommandHistoryContext>(std::move(commands));
context->CurrentCommandline(winrt::hstring{ _terminal->CurrentCommand() });

// TODO CARLOS: should we delete this after a new command is run? Or delete it after a suggestion is used? Or just after the next winget suggestion (current impl)?
// No clue which we should do. Thoughts?
context->WinGetSuggestions(_cachedWinGetSuggestions);

return *context;
}

Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
til::property<Windows::Foundation::Collections::IVector<winrt::hstring>> History;
til::property<winrt::hstring> CurrentCommandline;
til::property<Windows::Foundation::Collections::IVector<winrt::hstring>> WinGetSuggestions;

CommandHistoryContext(std::vector<winrt::hstring>&& history)
{
Expand Down Expand Up @@ -347,6 +348,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
til::point _contextMenuBufferPosition{ 0, 0 };

Windows::Foundation::Collections::IVector<int32_t> _cachedSearchResultRows{ nullptr };
Windows::Foundation::Collections::IVector<hstring> _cachedWinGetSuggestions{ nullptr };

void _setupDispatcherAndCallbacks();

Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalControl/ControlCore.idl
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ namespace Microsoft.Terminal.Control
{
IVector<String> History { get; };
String CurrentCommandline { get; };
IVector<String> WinGetSuggestions { get; };
};

[default_interface] runtimeclass ControlCore : ICoreState
Expand Down Expand Up @@ -185,7 +186,6 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseTerminalRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> RestartTerminalRequested;
event Windows.Foundation.TypedEventHandler<Object, SearchMissingCommandEventArgs> SearchMissingCommand;

event Windows.Foundation.TypedEventHandler<Object, Object> Attached;

Expand Down
1 change: 0 additions & 1 deletion src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_revokers.CloseTerminalRequested = _core.CloseTerminalRequested(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleCloseTerminalRequested });
_revokers.CompletionsChanged = _core.CompletionsChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleCompletionsChanged });
_revokers.RestartTerminalRequested = _core.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleRestartTerminalRequested });
_revokers.SearchMissingCommand = _core.SearchMissingCommand(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleSearchMissingCommand });

_revokers.PasteFromClipboard = _interactivity.PasteFromClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubblePasteFromClipboard });

Expand Down
2 changes: 0 additions & 2 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
BUBBLED_FORWARDED_TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable);
BUBBLED_FORWARDED_TYPED_EVENT(CompletionsChanged, IInspectable, Control::CompletionsChangedEventArgs);
BUBBLED_FORWARDED_TYPED_EVENT(RestartTerminalRequested, IInspectable, IInspectable);
BUBBLED_FORWARDED_TYPED_EVENT(SearchMissingCommand, IInspectable, Control::SearchMissingCommandEventArgs);

BUBBLED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);

Expand Down Expand Up @@ -405,7 +404,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Control::ControlCore::CloseTerminalRequested_revoker CloseTerminalRequested;
Control::ControlCore::CompletionsChanged_revoker CompletionsChanged;
Control::ControlCore::RestartTerminalRequested_revoker RestartTerminalRequested;
Control::ControlCore::SearchMissingCommand_revoker SearchMissingCommand;

// These are set up in _InitializeTerminal
Control::ControlCore::RendererWarning_revoker RendererWarning;
Expand Down
1 change: 0 additions & 1 deletion src/cascadia/TerminalControl/TermControl.idl
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, KeySentEventArgs> KeySent;
event Windows.Foundation.TypedEventHandler<Object, CharSentEventArgs> CharSent;
event Windows.Foundation.TypedEventHandler<Object, StringSentEventArgs> StringSent;
event Windows.Foundation.TypedEventHandler<Object, SearchMissingCommandEventArgs> SearchMissingCommand;


Microsoft.UI.Xaml.Controls.CommandBarFlyout ContextMenu { get; };
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalSettingsModel/ActionArgs.idl
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ namespace Microsoft.Terminal.Settings.Model
Tasks = 0x1,
CommandHistory = 0x2,
DirectoryHistory = 0x4,
WinGetCommandNotFound = 0x8,
All = 0xffffffff,
};

Expand Down
23 changes: 23 additions & 0 deletions src/cascadia/TerminalSettingsModel/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -797,4 +797,27 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation

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

IVector<Model::Command> Command::ToSendInputCommands(IVector<hstring> commands)
{
if (!commands)
{
return single_threaded_vector<Model::Command>();
}

auto result = std::vector<Model::Command>();
for (const auto& command : commands)
{
auto args = winrt::make_self<SendInputArgs>(command);
Model::ActionAndArgs actionAndArgs{ ShortcutAction::SendInput, *args };

auto c = winrt::make_self<Command>();
c->_ActionAndArgs = actionAndArgs;
c->_name = command;
c->_iconPath = L"\ue74c"; // OEM icon
result.push_back(*c);
}

return winrt::single_threaded_vector<Model::Command>(std::move(result));
}
}
1 change: 1 addition & 0 deletions src/cascadia/TerminalSettingsModel/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static Windows::Foundation::Collections::IVector<Model::Command> HistoryToCommands(Windows::Foundation::Collections::IVector<winrt::hstring> history,
winrt::hstring currentCommandline,
bool directories);
static Windows::Foundation::Collections::IVector<Model::Command> ToSendInputCommands(Windows::Foundation::Collections::IVector<winrt::hstring> commands);

WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None);
WINRT_PROPERTY(Model::ActionAndArgs, ActionAndArgs);
Expand Down

0 comments on commit 68ed03d

Please sign in to comment.