/ src / modules / cmdpal / ext / Microsoft.CmdPal.Ext.Apps / AllAppsCommandProvider.cs
AllAppsCommandProvider.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using System;
  6  using System.Collections.Generic;
  7  using Microsoft.CmdPal.Ext.Apps.Helpers;
  8  using Microsoft.CmdPal.Ext.Apps.Programs;
  9  using Microsoft.CmdPal.Ext.Apps.Properties;
 10  using Microsoft.CmdPal.Ext.Apps.State;
 11  using Microsoft.CommandPalette.Extensions;
 12  using Microsoft.CommandPalette.Extensions.Toolkit;
 13  
 14  namespace Microsoft.CmdPal.Ext.Apps;
 15  
 16  public partial class AllAppsCommandProvider : CommandProvider
 17  {
 18      public const string WellKnownId = "AllApps";
 19  
 20      public static readonly AllAppsPage Page = new();
 21  
 22      private readonly AllAppsPage _page;
 23      private readonly CommandItem _listItem;
 24  
 25      public AllAppsCommandProvider()
 26          : this(Page)
 27      {
 28      }
 29  
 30      public AllAppsCommandProvider(AllAppsPage page)
 31      {
 32          _page = page ?? throw new ArgumentNullException(nameof(page));
 33          Id = WellKnownId;
 34          DisplayName = Resources.installed_apps;
 35          Icon = Icons.AllAppsIcon;
 36          Settings = AllAppsSettings.Instance.Settings;
 37  
 38          _listItem = new(_page)
 39          {
 40              MoreCommands = [new CommandContextItem(AllAppsSettings.Instance.Settings.SettingsPage)],
 41          };
 42  
 43          // Subscribe to pin state changes to refresh the command provider
 44          PinnedAppsManager.Instance.PinStateChanged += OnPinStateChanged;
 45      }
 46  
 47      public static int TopLevelResultLimit
 48      {
 49          get
 50          {
 51              var limitSetting = AllAppsSettings.Instance.SearchResultLimit;
 52  
 53              if (limitSetting is null)
 54              {
 55                  return 10;
 56              }
 57  
 58              var quantity = 10;
 59  
 60              if (int.TryParse(limitSetting, out var result))
 61              {
 62                  quantity = result < 0 ? quantity : result;
 63              }
 64  
 65              return quantity;
 66          }
 67      }
 68  
 69      public override ICommandItem[] TopLevelCommands() => [_listItem, .. _page.GetPinnedApps()];
 70  
 71      public ICommandItem? LookupAppByPackageFamilyName(string packageFamilyName, bool requireSingleMatch)
 72      {
 73          if (string.IsNullOrEmpty(packageFamilyName))
 74          {
 75              return null;
 76          }
 77  
 78          var items = _page.GetItems();
 79          List<ICommandItem> matches = [];
 80  
 81          foreach (var item in items)
 82          {
 83              if (item is AppListItem appItem && string.Equals(packageFamilyName, appItem.App.PackageFamilyName, StringComparison.OrdinalIgnoreCase))
 84              {
 85                  matches.Add(item);
 86                  if (!requireSingleMatch)
 87                  {
 88                      // Return early if we don't require uniqueness.
 89                      return item;
 90                  }
 91              }
 92          }
 93  
 94          return requireSingleMatch && matches.Count == 1 ? matches[0] : null;
 95      }
 96  
 97      public ICommandItem? LookupAppByProductCode(string productCode, bool requireSingleMatch)
 98      {
 99          if (string.IsNullOrEmpty(productCode))
100          {
101              return null;
102          }
103  
104          if (!UninstallRegistryAppLocator.TryGetInstallInfo(productCode, out _, out var candidates) || candidates.Count <= 0)
105          {
106              return null;
107          }
108  
109          var items = _page.GetItems();
110          List<ICommandItem> matches = [];
111  
112          foreach (var item in items)
113          {
114              if (item is not AppListItem appListItem || string.IsNullOrEmpty(appListItem.App.FullExecutablePath))
115              {
116                  continue;
117              }
118  
119              foreach (var candidate in candidates)
120              {
121                  if (string.Equals(appListItem.App.FullExecutablePath, candidate, StringComparison.OrdinalIgnoreCase))
122                  {
123                      matches.Add(item);
124                      if (!requireSingleMatch)
125                      {
126                          return item;
127                      }
128                  }
129              }
130          }
131  
132          return requireSingleMatch && matches.Count == 1 ? matches[0] : null;
133      }
134  
135      public ICommandItem? LookupAppByDisplayName(string displayName)
136      {
137          var items = _page.GetItems();
138  
139          var nameMatches = new List<ICommandItem>();
140          ICommandItem? bestAppMatch = null;
141          var bestLength = -1;
142  
143          foreach (var item in items)
144          {
145              if (item.Title is null)
146              {
147                  continue;
148              }
149  
150              // We're going to do this search in two directions:
151              // First, is this name a substring of any app...
152              if (item.Title.Contains(displayName))
153              {
154                  nameMatches.Add(item);
155              }
156  
157              // ... Then, does any app have this name as a substring ...
158              // Only get one of these - "Terminal Preview" contains both "Terminal" and "Terminal Preview", so just take the best one
159              if (displayName.Contains(item.Title))
160              {
161                  if (item.Title.Length > bestLength)
162                  {
163                      bestLength = item.Title.Length;
164                      bestAppMatch = item;
165                  }
166              }
167          }
168  
169          // ... Now, combine those two
170          List<ICommandItem> both = bestAppMatch is null ? nameMatches : [.. nameMatches, bestAppMatch];
171  
172          if (both.Count == 1)
173          {
174              return both[0];
175          }
176          else if (nameMatches.Count == 1 && bestAppMatch is not null)
177          {
178              if (nameMatches[0] == bestAppMatch)
179              {
180                  return nameMatches[0];
181              }
182          }
183  
184          return null;
185      }
186  
187      private void OnPinStateChanged(object? sender, System.EventArgs e)
188      {
189          RaiseItemsChanged(0);
190      }
191  }