@@ -1670,6 +1670,8 @@ namespace winrt::TerminalApp::implementation
1670
1670
1671
1671
term.ShowWindowChanged ({ get_weak (), &TerminalPage::_ShowWindowChangedHandler });
1672
1672
1673
+ term.SearchMissingCommand ({ get_weak (), &TerminalPage::_SearchMissingCommandHandler });
1674
+
1673
1675
// Don't even register for the event if the feature is compiled off.
1674
1676
if constexpr (Feature_ShellCompletions::IsEnabled ())
1675
1677
{
@@ -1688,6 +1690,12 @@ namespace winrt::TerminalApp::implementation
1688
1690
page->_PopulateContextMenu (weakTerm.get (), sender.try_as <MUX::Controls::CommandBarFlyout>(), true );
1689
1691
}
1690
1692
});
1693
+ term.QuickFixMenu ().Opening ([weak = get_weak (), weakTerm](auto && sender, auto && /* args*/ ) {
1694
+ if (const auto & page{ weak.get () })
1695
+ {
1696
+ page->_PopulateQuickFixMenu (weakTerm.get (), sender.try_as <Controls::MenuFlyout>());
1697
+ }
1698
+ });
1691
1699
}
1692
1700
1693
1701
// Method Description:
@@ -2889,6 +2897,180 @@ namespace winrt::TerminalApp::implementation
2889
2897
ShowWindowChanged.raise (*this , args);
2890
2898
}
2891
2899
2900
+ winrt::fire_and_forget TerminalPage::_SearchMissingCommandHandler (const IInspectable /* sender*/ , const Microsoft::Terminal::Control::SearchMissingCommandEventArgs args)
2901
+ {
2902
+ assert (!Dispatcher ().HasThreadAccess ());
2903
+
2904
+ #if 0
2905
+ static constexpr CLSID CLSID_PackageManager = { 0xC53A4F16, 0x787E, 0x42A4, 0xB3, 0x04, 0x29, 0xEF, 0xFB, 0x4B, 0xF5, 0x97 }; //C53A4F16-787E-42A4-B304-29EFFB4BF597
2906
+ static constexpr CLSID CLSID_FindPackagesOptions = { 0x572DED96, 0x9C60, 0x4526, { 0x8F, 0x92, 0xEE, 0x7D, 0x91, 0xD3, 0x8C, 0x1A } }; //572DED96-9C60-4526-8F92-EE7D91D38C1A
2907
+ static constexpr CLSID CLSID_PackageMatchFilter = { 0xD02C9DAF, 0x99DC, 0x429C, { 0xB5, 0x03, 0x4E, 0x50, 0x4E, 0x4A, 0xB0, 0x00 } }; //D02C9DAF-99DC-429C-B503-4E504E4AB000
2908
+
2909
+ static constexpr unsigned int maxSuggestions = 5;
2910
+ bool tooManySuggestions = false;
2911
+
2912
+ // TODO CARLOS: this is where we fail! "Class not registered" error
2913
+ PackageManager pkgManager = winrt::create_instance<PackageManager>(CLSID_PackageManager, CLSCTX_ALL);
2914
+ auto catalogRef = pkgManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog::OpenWindowsCatalog);
2915
+ auto connectResult = catalogRef.Connect();
2916
+ int retryCount = 0;
2917
+ while (connectResult.Status() != ConnectResultStatus::Ok && retryCount < 3)
2918
+ {
2919
+ connectResult = catalogRef.Connect();
2920
+ ++retryCount;
2921
+ }
2922
+ if (connectResult.Status() != ConnectResultStatus::Ok)
2923
+ {
2924
+ return;
2925
+ }
2926
+ auto catalog = connectResult.PackageCatalog();
2927
+
2928
+ // Perform the query (search by command)
2929
+ auto packageMatchFilter = winrt::create_instance<PackageMatchFilter>(CLSID_PackageMatchFilter, CLSCTX_ALL);
2930
+ auto findPackagesOptions = winrt::create_instance<FindPackagesOptions>(CLSID_FindPackagesOptions, CLSCTX_ALL);
2931
+
2932
+ // Helper lambda to apply a filter to the query
2933
+ auto applyPackageMatchFilter = [&packageMatchFilter, &findPackagesOptions](PackageMatchField field, PackageFieldMatchOption matchOption, hstring query) {
2934
+ // Configure filter
2935
+ packageMatchFilter.Field(field);
2936
+ packageMatchFilter.Option(matchOption);
2937
+ packageMatchFilter.Value(query);
2938
+
2939
+ // Apply filter
2940
+ findPackagesOptions.ResultLimit(maxSuggestions + 1u);
2941
+ findPackagesOptions.Filters().Clear();
2942
+ findPackagesOptions.Filters().Append(packageMatchFilter);
2943
+ };
2944
+
2945
+ // Helper lambda to retrieve the best matching package(s) from the query's result
2946
+ auto tryGetBestMatchingPackage = [&tooManySuggestions](IVectorView<MatchResult> matches) {
2947
+ std::vector<CatalogPackage> results;
2948
+ results.reserve(std::min(matches.Size(), maxSuggestions));
2949
+ if (matches.Size() == 1)
2950
+ {
2951
+ // One match --> return the package
2952
+ results.emplace_back(matches.GetAt(0).CatalogPackage());
2953
+ }
2954
+ else if (matches.Size() > 1)
2955
+ {
2956
+ // Multiple matches --> display top 5 matches (prioritize best matches first)
2957
+ std::queue<CatalogPackage> bestExactMatches, secondaryMatches, tertiaryMatches;
2958
+ for (auto match : matches)
2959
+ {
2960
+ switch (match.MatchCriteria().Option())
2961
+ {
2962
+ case PackageFieldMatchOption::EqualsCaseInsensitive:
2963
+ case PackageFieldMatchOption::Equals:
2964
+ bestExactMatches.push(match.CatalogPackage());
2965
+ break;
2966
+ case PackageFieldMatchOption::StartsWithCaseInsensitive:
2967
+ secondaryMatches.push(match.CatalogPackage());
2968
+ break;
2969
+ case PackageFieldMatchOption::ContainsCaseInsensitive:
2970
+ tertiaryMatches.push(match.CatalogPackage());
2971
+ break;
2972
+ }
2973
+ }
2974
+
2975
+ // Now return the top maxSuggestions
2976
+ while (results.size() < maxSuggestions)
2977
+ {
2978
+ if (bestExactMatches.size() > 0)
2979
+ {
2980
+ results.emplace_back(bestExactMatches.front());
2981
+ bestExactMatches.pop();
2982
+ }
2983
+ else if (secondaryMatches.size() > 0)
2984
+ {
2985
+ results.emplace_back(secondaryMatches.front());
2986
+ secondaryMatches.pop();
2987
+ }
2988
+ else if (tertiaryMatches.size() > 0)
2989
+ {
2990
+ results.emplace_back(tertiaryMatches.front());
2991
+ tertiaryMatches.pop();
2992
+ }
2993
+ else
2994
+ {
2995
+ break;
2996
+ }
2997
+ }
2998
+ }
2999
+ tooManySuggestions = matches.Size() > maxSuggestions;
3000
+ return results;
3001
+ };
3002
+
3003
+ // Search by command
3004
+ auto missingCmd = args.MissingCommand();
3005
+ std::wstring searchOption = L"command";
3006
+ applyPackageMatchFilter(PackageMatchField::Command, PackageFieldMatchOption::StartsWithCaseInsensitive, missingCmd);
3007
+ auto findPackagesResult = catalog.FindPackages(findPackagesOptions);
3008
+ auto matches = findPackagesResult.Matches();
3009
+ auto pkgList = tryGetBestMatchingPackage(matches);
3010
+ if (pkgList.empty())
3011
+ {
3012
+ // No matches found --> search by name
3013
+ applyPackageMatchFilter(PackageMatchField::Name, PackageFieldMatchOption::ContainsCaseInsensitive, missingCmd);
3014
+
3015
+ findPackagesResult = catalog.FindPackages(findPackagesOptions);
3016
+ matches = findPackagesResult.Matches();
3017
+ pkgList = tryGetBestMatchingPackage(matches);
3018
+ searchOption = L"name";
3019
+
3020
+ if (pkgList.empty())
3021
+ {
3022
+ // No matches found --> search by moniker
3023
+ applyPackageMatchFilter(PackageMatchField::Moniker, PackageFieldMatchOption::ContainsCaseInsensitive, missingCmd);
3024
+
3025
+ // Perform the query (search by name)
3026
+ findPackagesResult = catalog.FindPackages(findPackagesOptions);
3027
+ matches = findPackagesResult.Matches();
3028
+ pkgList = tryGetBestMatchingPackage(matches);
3029
+ searchOption = L"moniker";
3030
+ }
3031
+ }
3032
+
3033
+ // Display packages in UI
3034
+ if (!pkgList.empty())
3035
+ {
3036
+ std::vector<std::wstring> suggestions;
3037
+ suggestions.reserve(pkgList.size());
3038
+ for (auto pkg : pkgList)
3039
+ {
3040
+ suggestions.emplace_back(fmt::format(L"winget install --id {}", pkg.Id()));
3041
+ }
3042
+
3043
+ std::wstring footer = tooManySuggestions ?
3044
+ fmt::format(L"winget search --{} {}", searchOption, missingCmd) :
3045
+ L"";
3046
+
3047
+ // TODO CARLOS: no more info bar; replace!
3048
+ //ShowCommandNotFoundInfoBar(suggestions, footer);
3049
+ }
3050
+ #elif defined(DEBUG) || defined(_DEBUG) || defined(DBG)
3051
+ //const bool tooManySuggestions = false;
3052
+ //const std::wstring searchOption = L"command";
3053
+ //const std::wstring missingCmd = args.MissingCommand().data();
3054
+ std::vector<std::wstring> pkgList = { L"pkg1", L"pkg2", L"pkg3" };
3055
+ std::vector<hstring> suggestions;
3056
+ suggestions.reserve(pkgList.size());
3057
+ for (auto pkg : pkgList)
3058
+ {
3059
+ suggestions.emplace_back(fmt::format(L"winget install --id {}", pkg));
3060
+ }
3061
+
3062
+ co_await wil::resume_foreground(Dispatcher());
3063
+
3064
+ auto term = _GetActiveControl();
3065
+ if (!term)
3066
+ {
3067
+ co_return;
3068
+ }
3069
+ term.UpdateWinGetSuggestions(single_threaded_vector<hstring>(std::move(suggestions)));
3070
+ term.ShowQuickFixMenu();
3071
+ #endif
3072
+ }
3073
+
2892
3074
// Method Description:
2893
3075
// - Paste text from the Windows Clipboard to the focused terminal
2894
3076
void TerminalPage::_PasteText ()
@@ -4806,6 +4988,58 @@ namespace winrt::TerminalApp::implementation
4806
4988
makeItem (RS_ (L" TabClose" ), L" \xE711 " , ActionAndArgs{ ShortcutAction::CloseTab, CloseTabArgs{ _GetFocusedTabIndex ().value () } });
4807
4989
}
4808
4990
4991
+ void TerminalPage::_PopulateQuickFixMenu (const TermControl& control,
4992
+ const Controls::MenuFlyout& menu)
4993
+ {
4994
+ if (!control || !menu)
4995
+ {
4996
+ return ;
4997
+ }
4998
+
4999
+ // Helper lambda for dispatching an ActionAndArgs onto the
5000
+ // ShortcutActionDispatch. Used below to wire up each menu entry to the
5001
+ // respective action.
5002
+
5003
+ auto weak = get_weak ();
5004
+ auto makeCallback = [weak](const ActionAndArgs& actionAndArgs) {
5005
+ return [weak, actionAndArgs](auto &&, auto &&) {
5006
+ if (auto page{ weak.get () })
5007
+ {
5008
+ page->_actionDispatch ->DoAction (actionAndArgs);
5009
+ }
5010
+ };
5011
+ };
5012
+
5013
+ auto makeItem = [&menu, &makeCallback](const winrt::hstring& label,
5014
+ const winrt::hstring& icon,
5015
+ const winrt::hstring& suggestion) {
5016
+ MenuFlyoutItem item{};
5017
+
5018
+ if (!icon.empty ())
5019
+ {
5020
+ auto iconElement = UI::IconPathConverter::IconWUX (icon);
5021
+ Automation::AutomationProperties::SetAccessibilityView (iconElement, Automation::Peers::AccessibilityView::Raw);
5022
+ item.Icon (iconElement);
5023
+ }
5024
+
5025
+ item.Text (label);
5026
+ item.Click (makeCallback (ActionAndArgs{ ShortcutAction::SendInput, SendInputArgs{ hstring{ L" \u0003 " } + suggestion } }));
5027
+ menu.Items ().Append (item);
5028
+ };
5029
+
5030
+ // Wire up each item to the action that should be performed. By actually
5031
+ // connecting these to actions, we ensure the implementation is
5032
+ // consistent. This also leaves room for customizing this menu with
5033
+ // actions in the future.
5034
+
5035
+ menu.Items ().Clear ();
5036
+ const auto quickFixes = control.CommandHistory ().QuickFixes ();
5037
+ for (const auto & qf : quickFixes)
5038
+ {
5039
+ makeItem (qf, L" \ue74c " , qf);
5040
+ }
5041
+ }
5042
+
4809
5043
// Handler for our WindowProperties's PropertyChanged event. We'll use this
4810
5044
// to pop the "Identify Window" toast when the user renames our window.
4811
5045
winrt::fire_and_forget TerminalPage::_windowPropertyChanged (const IInspectable& /* sender*/ ,
0 commit comments