Make fallback commands work again (#234)

Add support for fallback commands again. Couple important parts:

* Lists shouldn't contain items with blank `Title`s. That's spec'd that way, and allows fallback items to hide themselves if they don't want to be shown for a given search string
* The CommandProviderWrapper, as well as extension infrastructure needs to know to get the fallbacks out of a commandprovider too. 
* `TopLevelCommandWrapper` needs to know when its model changes, to update itself in the list

------
Co-authored-by: Mike Griese <zadjii@gmail.com>
This commit is contained in:
Mike Griese
2024-12-16 15:49:57 -06:00
committed by GitHub
parent 4048d76d87
commit b0bfeec888
8 changed files with 91 additions and 15 deletions

View File

@@ -133,7 +133,6 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel
this.Icon = model.Icon;
break;
// TODO! Icon
// TODO! MoreCommands array, which needs to also raise HasMoreCommands
}

View File

@@ -19,6 +19,8 @@ public sealed class CommandProviderWrapper
public ICommandItem[] TopLevelItems { get; private set; } = [];
public IFallbackCommandItem[] FallbackItems { get; private set; } = [];
public CommandProviderWrapper(ICommandProvider provider)
{
_commandProvider = provider;
@@ -56,10 +58,17 @@ public sealed class CommandProviderWrapper
var commands = await t.ConfigureAwait(false);
// On a BG thread here
var fallbacks = _commandProvider.FallbackCommands();
if (commands != null)
{
TopLevelItems = commands;
}
if (fallbacks != null)
{
FallbackItems = fallbacks;
}
}
/* This is a View/ExtensionHost piece

View File

@@ -59,7 +59,7 @@ public partial class MainListPage : DynamicListPage
}
return string.IsNullOrEmpty(SearchText)
? _commands.Select(tlc => tlc).ToArray()
? _commands.Select(tlc => tlc).Where(tlc => !string.IsNullOrEmpty(tlc.Title)).ToArray()
: _filteredItems?.ToArray() ?? [];
}
@@ -67,6 +67,11 @@ public partial class MainListPage : DynamicListPage
{
/* handle changes to the filter text here */
foreach (var command in _commands)
{
command.TryUpdateFallbackText(newSearch);
}
// Cleared out the filter text? easy. Reset _filteredItems, and bail out.
if (string.IsNullOrEmpty(newSearch))
{

View File

@@ -15,10 +15,7 @@ public partial class QuitCommandProvider : CommandProvider
{
private readonly QuitAction quitAction = new();
public override ICommandItem[] TopLevelCommands() =>
// HACK: fallback commands aren't wired up and we need to be able to exit
[new FallbackCommandItem(quitAction) { Subtitle = "Exit Command Palette" }];
public override ICommandItem[] TopLevelCommands() => [];
public override IFallbackCommandItem[] FallbackCommands() =>
[new FallbackCommandItem(quitAction) { Subtitle = "Exit Command Palette" }];

View File

@@ -40,7 +40,12 @@ public partial class TopLevelCommandManager(IServiceProvider _serviceProvider) :
await commandProvider.LoadTopLevelCommands();
foreach (var i in commandProvider.TopLevelItems)
{
TopLevelCommands.Add(new(new(i)));
TopLevelCommands.Add(new(new(i), false));
}
foreach (var i in commandProvider.FallbackItems)
{
TopLevelCommands.Add(new(new(i), true));
}
}

View File

@@ -17,9 +17,13 @@ public partial class TopLevelCommandWrapper : ListItem
{
public ExtensionObject<ICommandItem> Model { get; }
public TopLevelCommandWrapper(ExtensionObject<ICommandItem> commandItem)
private readonly bool _isFallback;
public TopLevelCommandWrapper(ExtensionObject<ICommandItem> commandItem, bool isFallback)
: base(commandItem.Unsafe?.Command ?? new NoOpCommand())
{
_isFallback = isFallback;
// TODO: In reality, we should do an async fetch when we're created
// from an extension object. Probably have an
// `static async Task<TopLevelCommandWrapper> FromExtension(ExtensionObject<ICommandItem>)`
@@ -38,10 +42,67 @@ public partial class TopLevelCommandWrapper : ListItem
Subtitle = model.Subtitle;
Icon = new(model.Icon.Icon);
MoreCommands = model.MoreCommands;
model.PropChanged += Model_PropChanged;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
}
private void Model_PropChanged(object sender, PropChangedEventArgs args)
{
try
{
var propertyName = args.PropertyName;
var model = Model.Unsafe;
if (model == null)
{
return; // throw?
}
switch (propertyName)
{
case nameof(Title):
this.Title = model.Title;
break;
case nameof(Subtitle):
this.Subtitle = model.Subtitle;
break;
case nameof(Icon):
var listIcon = model.Icon;
Icon = model.Icon;
break;
// TODO! MoreCommands array, which needs to also raise HasMoreCommands
}
}
catch
{
}
}
public void TryUpdateFallbackText(string newQuery)
{
if (!_isFallback)
{
return;
}
try
{
_ = Task.Run(() =>
{
var model = Model.Unsafe;
if (model is IFallbackCommandItem fallback)
{
fallback.FallbackHandler.UpdateQuery(newQuery);
}
});
}
catch (Exception)
{
}
}
}

View File

@@ -16,6 +16,11 @@ public class ListHelpers
return 1;
}
if (string.IsNullOrEmpty(listItem.Title))
{
return 0;
}
var nameMatch = StringMatcher.FuzzySearch(query, listItem.Title);
// var locNameMatch = StringMatcher.FuzzySearch(query, NameLocalized);

View File

@@ -4,9 +4,6 @@
using Microsoft.CmdPal.Ext.Shell.Commands;
using Microsoft.CmdPal.Ext.Shell.Helpers;
using Microsoft.CmdPal.Ext.Shell.Pages;
using Microsoft.CmdPal.Ext.Shell.Properties;
using Microsoft.CmdPal.Extensions;
using Microsoft.CmdPal.Extensions.Helpers;
namespace Microsoft.CmdPal.Ext.Shell;
@@ -20,17 +17,15 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem
{
_executeItem = (ExecuteItem)this.Command!;
Title = string.Empty;
_executeItem.Name = string.Empty;
Subtitle = Properties.Resources.generic_run_command;
Icon = new("\uE756");
// TODO: this is a bug in the current POC. I don't think Fallback items
// get icons set correctly.
_executeItem.Icon = Icon;
}
public override void UpdateQuery(string query)
{
_executeItem.Cmd = query;
_executeItem.Name = string.IsNullOrEmpty(query) ? string.Empty : Properties.Resources.generic_run_command;
Title = query;
}
}