Support multiple plugins per action keyword

Refactor non-global plugin storage to allow multiple plugins to share the same action keyword by using ConcurrentDictionary<string, List<PluginPair>>. Update all relevant methods to handle lists of plugins, ensure thread safety, and adjust the QueryBuilder logic accordingly. This change improves extensibility and flexibility in plugin management.
This commit is contained in:
Jack251970 2026-02-26 16:54:03 +08:00
parent f05d31a1c1
commit b8bc7fa82d
2 changed files with 54 additions and 17 deletions

View file

@ -29,7 +29,7 @@ namespace Flow.Launcher.Core.Plugin
private static readonly ConcurrentDictionary<string, PluginPair> _allInitializedPlugins = [];
private static readonly ConcurrentDictionary<string, PluginPair> _initFailedPlugins = [];
private static readonly ConcurrentDictionary<string, PluginPair> _globalPlugins = [];
private static readonly ConcurrentDictionary<string, PluginPair> _nonGlobalPlugins = [];
private static readonly ConcurrentDictionary<string, List<PluginPair>> _nonGlobalPlugins = [];
private static PluginsSettings Settings;
private static readonly ConcurrentBag<string> ModifiedPlugins = [];
@ -332,7 +332,19 @@ namespace Flow.Launcher.Core.Plugin
_globalPlugins.TryAdd(pair.Metadata.ID, pair);
break;
default:
_nonGlobalPlugins.TryAdd(actionKeyword, pair);
_nonGlobalPlugins.AddOrUpdate(actionKeyword,
_ => [pair],
(_, existing) =>
{
lock (existing)
{
if (!existing.Contains(pair))
{
existing.Add(pair);
}
}
return existing;
});
break;
}
}
@ -368,7 +380,7 @@ namespace Flow.Launcher.Core.Plugin
if (query is null)
return Array.Empty<PluginPair>();
if (!_nonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugin))
if (!_nonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugins))
{
if (dialogJump)
return [.. GetGlobalPlugins().Where(p => p.Plugin is IAsyncDialogJump && !PluginModified(p.Metadata.ID))];
@ -376,13 +388,11 @@ namespace Flow.Launcher.Core.Plugin
return [.. GetGlobalPlugins().Where(p => !PluginModified(p.Metadata.ID))];
}
if (dialogJump && plugin.Plugin is not IAsyncDialogJump)
return Array.Empty<PluginPair>();
var validPlugins = plugins.Where(p => !PluginModified(p.Metadata.ID));
if (dialogJump)
validPlugins = validPlugins.Where(p => p.Plugin is IAsyncDialogJump);
if (PluginModified(plugin.Metadata.ID))
return Array.Empty<PluginPair>();
return [plugin];
return [.. validPlugins];
}
public static ICollection<PluginPair> ValidPluginsForHomeQuery()
@ -576,7 +586,7 @@ namespace Flow.Launcher.Core.Plugin
return [.. _globalPlugins.Values];
}
public static Dictionary<string, PluginPair> GetNonGlobalPlugins()
public static Dictionary<string, List<PluginPair>> GetNonGlobalPlugins()
{
return _nonGlobalPlugins.ToDictionary();
}
@ -736,13 +746,27 @@ namespace Flow.Launcher.Core.Plugin
public static void AddActionKeyword(string id, string newActionKeyword)
{
var plugin = GetPluginForId(id);
if (plugin == null) return;
if (newActionKeyword == Query.GlobalPluginWildcardSign)
{
_globalPlugins.TryAdd(id, plugin);
}
else
{
_nonGlobalPlugins.AddOrUpdate(newActionKeyword, plugin, (key, oldValue) => plugin);
_nonGlobalPlugins.AddOrUpdate(newActionKeyword,
_ => [plugin],
(_, existing) =>
{
lock (existing)
{
if (!existing.Contains(plugin))
{
existing.Add(plugin);
}
}
return existing;
});
}
// Update action keywords and action keyword in plugin metadata
@ -764,6 +788,8 @@ namespace Flow.Launcher.Core.Plugin
public static void RemoveActionKeyword(string id, string oldActionkeyword)
{
var plugin = GetPluginForId(id);
if (plugin == null) return;
if (oldActionkeyword == Query.GlobalPluginWildcardSign
&& // Plugins may have multiple ActionKeywords that are global, eg. WebSearch
plugin.Metadata.ActionKeywords
@ -774,7 +800,13 @@ namespace Flow.Launcher.Core.Plugin
if (oldActionkeyword != Query.GlobalPluginWildcardSign)
{
_nonGlobalPlugins.TryRemove(oldActionkeyword, out _);
if (_nonGlobalPlugins.TryGetValue(oldActionkeyword, out var plugins))
{
lock (plugins)
{
plugins.RemoveAll(p => p.Metadata.ID == id);
}
}
}
// Update action keywords and action keyword in plugin metadata
@ -1032,10 +1064,13 @@ namespace Flow.Launcher.Core.Plugin
{
_globalPlugins.TryRemove(plugin.ID, out var _);
}
var keysToRemove = _nonGlobalPlugins.Where(p => p.Value.Metadata.ID == plugin.ID).Select(p => p.Key).ToList();
foreach (var key in keysToRemove)
var entriesToUpdate = _nonGlobalPlugins.ToList();
foreach (var entry in entriesToUpdate)
{
_nonGlobalPlugins.TryRemove(key, out var _);
lock (entry.Value)
{
entry.Value.RemoveAll(p => p.Metadata.ID == plugin.ID);
}
}
}

View file

@ -1,12 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Plugin
{
public static class QueryBuilder
{
public static Query Build(string originalQuery, string trimmedQuery, Dictionary<string, PluginPair> nonGlobalPlugins)
public static Query Build(string originalQuery, string trimmedQuery, Dictionary<string, List<PluginPair>> nonGlobalPlugins)
{
// home query
if (string.IsNullOrEmpty(trimmedQuery))
@ -34,7 +35,8 @@ namespace Flow.Launcher.Core.Plugin
string possibleActionKeyword = terms[0];
string[] searchTerms;
if (nonGlobalPlugins.TryGetValue(possibleActionKeyword, out var pluginPair) && !pluginPair.Metadata.Disabled)
if (nonGlobalPlugins.TryGetValue(possibleActionKeyword, out var pluginPairs)
&& pluginPairs.Any(plugin => !plugin.Metadata.Disabled))
{
// use non global plugin for query
actionKeyword = possibleActionKeyword;