diff --git a/build/rules/Microsoft.WindowsPackageManager.ComInterop.Additional.targets b/build/rules/Microsoft.WindowsPackageManager.ComInterop.Additional.targets new file mode 100644 index 0000000000..be2a691bb2 --- /dev/null +++ b/build/rules/Microsoft.WindowsPackageManager.ComInterop.Additional.targets @@ -0,0 +1,22 @@ + + + + + x86 + $(Platform) + + + + true + + + + + + CustomOutputGroupForPackaging + $(ProjectName) + Microsoft.Management.Deployment.winmd + + + + \ No newline at end of file diff --git a/dep/nuget/packages.config b/dep/nuget/packages.config index a02190c6bc..a641da7c51 100644 --- a/dep/nuget/packages.config +++ b/dep/nuget/packages.config @@ -10,6 +10,7 @@ + diff --git a/src/cascadia/TerminalApp/SuggestionsControl.cpp b/src/cascadia/TerminalApp/SuggestionsControl.cpp index 80206ea5dd..442b80243c 100644 --- a/src/cascadia/TerminalApp/SuggestionsControl.cpp +++ b/src/cascadia/TerminalApp/SuggestionsControl.cpp @@ -724,6 +724,20 @@ namespace winrt::TerminalApp::implementation // ever allow non-sendInput actions. DispatchCommandRequested.raise(*this, actionPaletteItem.Command()); + if (const auto& sendInputCmd = actionPaletteItem.Command().ActionAndArgs().Args().try_as()) + { + if (til::starts_with(sendInputCmd.Input(), L"winget")) + { + TraceLoggingWrite( + g_hTerminalAppProvider, + "QuickFixSuggestionUsed", + TraceLoggingDescription("Event emitted when a winget suggestion from is used"), + TraceLoggingValue("SuggestionsUI", "Source"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + } + } + TraceLoggingWrite( g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider "SuggestionsControlDispatchedAction", diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 8549c32cd4..99ccf110ff 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -25,6 +25,7 @@ true true + true @@ -177,6 +178,7 @@ SuggestionsControl.xaml + @@ -361,7 +363,7 @@ - + TaskPaneContent.xaml Code diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 002886fd3b..aae32e336c 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -26,6 +26,7 @@ #include "LaunchPositionRequest.g.cpp" using namespace winrt; +using namespace winrt::Microsoft::Management::Deployment; using namespace winrt::Microsoft::Terminal::Control; using namespace winrt::Microsoft::Terminal::Settings::Model; using namespace winrt::Microsoft::Terminal::TerminalConnection; @@ -3001,18 +3002,82 @@ namespace winrt::TerminalApp::implementation ShowWindowChanged.raise(*this, args); } - winrt::fire_and_forget TerminalPage::_SearchMissingCommandHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::SearchMissingCommandEventArgs args) + Windows::Foundation::IAsyncOperation> TerminalPage::_FindPackageAsync(hstring query) { - assert(!Dispatcher().HasThreadAccess()); + const PackageManager packageManager = WindowsPackageManagerFactory::CreatePackageManager(); + PackageCatalogReference catalogRef{ + packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog::OpenWindowsCatalog) + }; + catalogRef.PackageCatalogBackgroundUpdateInterval(std::chrono::hours(24)); + ConnectResult connectResult{ nullptr }; + for (int retries = 0;;) + { + connectResult = catalogRef.Connect(); + if (connectResult.Status() == ConnectResultStatus::Ok) + { + break; + } + + if (++retries == 3) + { + co_return nullptr; + } + } + + PackageCatalog catalog = connectResult.PackageCatalog(); + // clang-format off + static constexpr std::array searches{ { + { .Field = PackageMatchField::Command, .MatchOption = PackageFieldMatchOption::StartsWithCaseInsensitive }, + { .Field = PackageMatchField::Name, .MatchOption = PackageFieldMatchOption::ContainsCaseInsensitive }, + { .Field = PackageMatchField::Moniker, .MatchOption = PackageFieldMatchOption::ContainsCaseInsensitive } } }; + // clang-format on + + PackageMatchFilter filter = WindowsPackageManagerFactory::CreatePackageMatchFilter(); + filter.Value(query); + + FindPackagesOptions options = WindowsPackageManagerFactory::CreateFindPackagesOptions(); + options.Filters().Append(filter); + options.ResultLimit(20); + + IVectorView pkgList; + for (const auto& search : searches) + { + filter.Field(search.Field); + filter.Option(search.MatchOption); + + const auto result = co_await catalog.FindPackagesAsync(options); + pkgList = result.Matches(); + if (pkgList.Size() > 0) + { + break; + } + } + co_return pkgList; + } + + Windows::Foundation::IAsyncAction TerminalPage::_SearchMissingCommandHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::SearchMissingCommandEventArgs args) + { if (!Feature_QuickFix::IsEnabled()) { co_return; } + co_await winrt::resume_background(); + + // no packages were found, nothing to suggest + const auto pkgList = co_await _FindPackageAsync(args.MissingCommand()); + if (!pkgList || pkgList.Size() == 0) + { + co_return; + } std::vector suggestions; - suggestions.reserve(1); - suggestions.emplace_back(fmt::format(FMT_COMPILE(L"winget install {}"), args.MissingCommand())); + suggestions.reserve(pkgList.Size()); + for (const auto& pkg : pkgList) + { + // --id and --source ensure we don't collide with another package catalog + suggestions.emplace_back(fmt::format(FMT_COMPILE(L"winget install --id {} -s winget"), pkg.CatalogPackage().Id())); + } co_await wil::resume_foreground(Dispatcher()); @@ -5006,6 +5071,14 @@ namespace winrt::TerminalApp::implementation { ctrl.ClearQuickFix(); } + + TraceLoggingWrite( + g_hTerminalAppProvider, + "QuickFixSuggestionUsed", + TraceLoggingDescription("Event emitted when a winget suggestion from is used"), + TraceLoggingValue("QuickFixMenu", "Source"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } }; }; @@ -5027,6 +5100,7 @@ namespace winrt::TerminalApp::implementation item.Text(qf); item.Click(makeCallback(qf)); + ToolTipService::SetToolTip(item, box_value(qf)); menu.Items().Append(item); } } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 65bfa9bad7..449ffa1fff 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -13,6 +13,8 @@ #include "LaunchPositionRequest.g.h" #include "Toast.h" +#include "WindowsPackageManagerFactory.h" + #define DECLARE_ACTION_HANDLER(action) void _Handle##action(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); namespace TerminalAppLocalTests @@ -87,6 +89,12 @@ namespace winrt::TerminalApp::implementation til::property Position; }; + struct WinGetSearchParams + { + winrt::Microsoft::Management::Deployment::PackageMatchField Field; + winrt::Microsoft::Management::Deployment::PackageFieldMatchOption MatchOption; + }; + struct TerminalPage : TerminalPageT { public: @@ -530,7 +538,8 @@ namespace winrt::TerminalApp::implementation void _OpenSuggestions(const Microsoft::Terminal::Control::TermControl& sender, Windows::Foundation::Collections::IVector commandsCollection, winrt::TerminalApp::SuggestionsMode mode, winrt::hstring filterText); void _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args); - winrt::fire_and_forget _SearchMissingCommandHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::SearchMissingCommandEventArgs args); + Windows::Foundation::IAsyncAction _SearchMissingCommandHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::SearchMissingCommandEventArgs args); + Windows::Foundation::IAsyncOperation> _FindPackageAsync(hstring query); winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); diff --git a/src/cascadia/TerminalApp/WindowsPackageManagerFactory.h b/src/cascadia/TerminalApp/WindowsPackageManagerFactory.h new file mode 100644 index 0000000000..1032eb2076 --- /dev/null +++ b/src/cascadia/TerminalApp/WindowsPackageManagerFactory.h @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// Module Name: +// - WindowsPackageManagerFactory.h +// +// Abstract: +// - This factory is designed to create production-level instances of WinGet objects. +// Elevated sessions require manual activation of WinGet objects and are not currently supported, +// while non-elevated sessions can use the standard WinRT activation system. +// Author: +// - Carlos Zamora (carlos-zamora) 23-Jul-2024 + +#pragma once + +#include + +namespace winrt::TerminalApp::implementation +{ + struct WindowsPackageManagerFactory + { + public: + static winrt::Microsoft::Management::Deployment::PackageManager CreatePackageManager() + { + return winrt::create_instance(PackageManagerGuid, CLSCTX_ALL); + } + + static winrt::Microsoft::Management::Deployment::FindPackagesOptions CreateFindPackagesOptions() + { + return winrt::create_instance(FindPackageOptionsGuid, CLSCTX_ALL); + } + + static winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() + { + return winrt::create_instance(CreateCompositePackageCatalogOptionsGuid, CLSCTX_ALL); + } + + static winrt::Microsoft::Management::Deployment::InstallOptions CreateInstallOptions() + { + return winrt::create_instance(InstallOptionsGuid, CLSCTX_ALL); + } + + static winrt::Microsoft::Management::Deployment::UninstallOptions CreateUninstallOptions() + { + return winrt::create_instance(UninstallOptionsGuid, CLSCTX_ALL); + } + + static winrt::Microsoft::Management::Deployment::PackageMatchFilter CreatePackageMatchFilter() + { + return winrt::create_instance(PackageMatchFilterGuid, CLSCTX_ALL); + } + + private: + static constexpr winrt::guid PackageManagerGuid{ 0xC53A4F16, 0x787E, 0x42A4, { 0xB3, 0x04, 0x29, 0xEF, 0xFB, 0x4B, 0xF5, 0x97 } }; + static constexpr winrt::guid FindPackageOptionsGuid{ 0x572DED96, 0x9C60, 0x4526, { 0x8F, 0x92, 0xEE, 0x7D, 0x91, 0xD3, 0x8C, 0x1A } }; + static constexpr winrt::guid CreateCompositePackageCatalogOptionsGuid{ 0x526534B8, 0x7E46, 0x47C8, { 0x84, 0x16, 0xB1, 0x68, 0x5C, 0x32, 0x7D, 0x37 } }; + static constexpr winrt::guid InstallOptionsGuid{ 0x1095F097, 0xEB96, 0x453B, { 0xB4, 0xE6, 0x16, 0x13, 0x63, 0x7F, 0x3B, 0x14 } }; + static constexpr winrt::guid UninstallOptionsGuid{ 0xE1D9A11E, 0x9F85, 0x4D87, { 0x9C, 0x17, 0x2B, 0x93, 0x14, 0x3A, 0xDB, 0x8D } }; + static constexpr winrt::guid PackageMatchFilterGuid{ 0xD02C9DAF, 0x99DC, 0x429C, { 0xB5, 0x03, 0x4E, 0x50, 0x4E, 0x4A, 0xB0, 0x00 } }; + }; +} diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index d8d975f667..ac306b69c6 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -128,7 +128,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation auto pfnCompletionsChanged = [=](auto&& menuJson, auto&& replaceLength) { _terminalCompletionsChanged(menuJson, replaceLength); }; _terminal->CompletionsChangedCallback(pfnCompletionsChanged); - auto pfnSearchMissingCommand = [this](auto&& PH1) { _terminalSearchMissingCommand(std::forward(PH1)); }; + auto pfnSearchMissingCommand = [this](auto&& PH1, auto&& PH2) { _terminalSearchMissingCommand(std::forward(PH1), std::forward(PH2)); }; _terminal->SetSearchMissingCommandCallback(pfnSearchMissingCommand); auto pfnClearQuickFix = [this] { ClearQuickFix(); }; @@ -1629,9 +1629,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation _midiAudio.PlayNote(reinterpret_cast(_owningHwnd), noteNumber, velocity, std::chrono::duration_cast(duration)); } - void ControlCore::_terminalSearchMissingCommand(std::wstring_view missingCommand) + void ControlCore::_terminalSearchMissingCommand(std::wstring_view missingCommand, const til::CoordType& bufferRow) { - SearchMissingCommand.raise(*this, make(hstring{ missingCommand })); + SearchMissingCommand.raise(*this, make(hstring{ missingCommand }, bufferRow)); } void ControlCore::ClearQuickFix() diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 938d048243..32fa4c0253 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -389,7 +389,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _terminalPlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration); - void _terminalSearchMissingCommand(std::wstring_view missingCommand); + void _terminalSearchMissingCommand(std::wstring_view missingCommand, const til::CoordType& bufferRow); winrt::fire_and_forget _terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength); diff --git a/src/cascadia/TerminalControl/EventArgs.h b/src/cascadia/TerminalControl/EventArgs.h index 6770da9f62..0e98cefd67 100644 --- a/src/cascadia/TerminalControl/EventArgs.h +++ b/src/cascadia/TerminalControl/EventArgs.h @@ -216,10 +216,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation struct SearchMissingCommandEventArgs : public SearchMissingCommandEventArgsT { public: - SearchMissingCommandEventArgs(const winrt::hstring& missingCommand) : - MissingCommand(missingCommand) {} + SearchMissingCommandEventArgs(const winrt::hstring& missingCommand, const til::CoordType& bufferRow) : + MissingCommand(missingCommand), + BufferRow(bufferRow) {} til::property MissingCommand; + til::property BufferRow; }; } diff --git a/src/cascadia/TerminalControl/EventArgs.idl b/src/cascadia/TerminalControl/EventArgs.idl index 6cad9ccff7..c3255bc667 100644 --- a/src/cascadia/TerminalControl/EventArgs.idl +++ b/src/cascadia/TerminalControl/EventArgs.idl @@ -130,5 +130,6 @@ namespace Microsoft.Terminal.Control runtimeclass SearchMissingCommandEventArgs { String MissingCommand { get; }; + Int32 BufferRow { get; }; } } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 8c2a37e7d2..aa38206d10 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -4059,18 +4059,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto rd = get_self(_core)->GetRenderData(); rd->LockConsole(); const auto viewportBufferPosition = rd->GetViewport(); - const auto cursorBufferPosition = rd->GetCursorPosition(); rd->UnlockConsole(); - if (cursorBufferPosition.y < viewportBufferPosition.Top() || cursorBufferPosition.y > viewportBufferPosition.BottomInclusive()) + if (_quickFixBufferPos < viewportBufferPosition.Top() || _quickFixBufferPos > viewportBufferPosition.BottomInclusive()) { quickFixBtn.Visibility(Visibility::Collapsed); return; } // draw the button in the gutter - const auto& cursorPosInDips = CursorPositionInDips(); + const auto& quickFixBtnPosInDips = _toPosInDips({ 0, _quickFixBufferPos }); Controls::Canvas::SetLeft(quickFixBtn, -termPadding.Left); - Controls::Canvas::SetTop(quickFixBtn, cursorPosInDips.Y - termPadding.Top); + Controls::Canvas::SetTop(quickFixBtn, quickFixBtnPosInDips.y - termPadding.Top); quickFixBtn.Visibility(Visibility::Visible); if (auto automationPeer{ FrameworkElementAutomationPeer::FromElement(*this) }) @@ -4085,6 +4084,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void TermControl::_bubbleSearchMissingCommand(const IInspectable& /*sender*/, const Control::SearchMissingCommandEventArgs& args) { + _quickFixBufferPos = args.BufferRow(); SearchMissingCommand.raise(*this, args); } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index ab5b0a1ae7..c4fbdb40d9 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -278,6 +278,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool _initializedTerminal{ false }; bool _quickFixButtonCollapsible{ false }; bool _quickFixesAvailable{ false }; + til::CoordType _quickFixBufferPos{}; std::shared_ptr _playWarningBell; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index b7e343998d..8a82d4d91a 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -1233,7 +1233,7 @@ void Microsoft::Terminal::Core::Terminal::CompletionsChangedCallback(std::functi _pfnCompletionsChanged.swap(pfn); } -void Microsoft::Terminal::Core::Terminal::SetSearchMissingCommandCallback(std::function pfn) noexcept +void Microsoft::Terminal::Core::Terminal::SetSearchMissingCommandCallback(std::function pfn) noexcept { _pfnSearchMissingCommand.swap(pfn); } diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index cf8dea0a16..34d92213d8 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -230,7 +230,7 @@ public: void SetShowWindowCallback(std::function pfn) noexcept; void SetPlayMidiNoteCallback(std::function pfn) noexcept; void CompletionsChangedCallback(std::function pfn) noexcept; - void SetSearchMissingCommandCallback(std::function pfn) noexcept; + void SetSearchMissingCommandCallback(std::function pfn) noexcept; void SetClearQuickFixCallback(std::function pfn) noexcept; void SetSearchHighlights(const std::vector& highlights) noexcept; void SetSearchHighlightFocused(const size_t focusedIdx, til::CoordType searchScrollOffset); @@ -342,7 +342,7 @@ private: std::function _pfnShowWindowChanged; std::function _pfnPlayMidiNote; std::function _pfnCompletionsChanged; - std::function _pfnSearchMissingCommand; + std::function _pfnSearchMissingCommand; std::function _pfnClearQuickFix; RenderSettings _renderSettings; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 26daf24e5d..9114d7866e 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -338,7 +338,8 @@ void Terminal::SearchMissingCommand(const std::wstring_view command) { if (_pfnSearchMissingCommand) { - _pfnSearchMissingCommand(command); + const auto bufferRow = GetCursorPosition().y; + _pfnSearchMissingCommand(command, bufferRow); } } diff --git a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj index 68a52a67fd..21fea0cf5f 100644 --- a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj +++ b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj @@ -20,6 +20,7 @@ true true true + true diff --git a/src/common.nugetversions.props b/src/common.nugetversions.props index 7989c7ce0e..6e53f2cde2 100644 --- a/src/common.nugetversions.props +++ b/src/common.nugetversions.props @@ -39,4 +39,9 @@ + + + $(MSBuildThisFileDirectory)..\packages\Microsoft.WindowsPackageManager.ComInterop.1.8.1911\ + + diff --git a/src/common.nugetversions.targets b/src/common.nugetversions.targets index 375bfd2c39..837c575fd8 100644 --- a/src/common.nugetversions.targets +++ b/src/common.nugetversions.targets @@ -64,6 +64,8 @@ + +