This commit is contained in:
Jack Ye 2026-03-10 14:06:39 +08:00 committed by GitHub
commit 1f3b387977
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 115 additions and 29 deletions

View file

@ -30,7 +30,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 = [];
@ -333,7 +333,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;
}
}
@ -369,7 +381,7 @@ namespace Flow.Launcher.Core.Plugin
if (query is null)
return Array.Empty<PluginPair>();
if (!_nonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugin))
if (!TryGetNonGlobalPlugins(query.ActionKeyword, out var plugins))
{
if (dialogJump)
return [.. GetGlobalPlugins().Where(p => p.Plugin is IAsyncDialogJump && !PluginModified(p.Metadata.ID))];
@ -377,13 +389,25 @@ 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 => !p.Metadata.Disabled && !PluginModified(p.Metadata.ID));
if (dialogJump)
validPlugins = validPlugins.Where(p => p.Plugin is IAsyncDialogJump);
if (PluginModified(plugin.Metadata.ID))
return Array.Empty<PluginPair>();
return [.. validPlugins];
}
return [plugin];
private static bool TryGetNonGlobalPlugins(string actionKeyword, out List<PluginPair> plugins)
{
if (_nonGlobalPlugins.TryGetValue(actionKeyword, out var list))
{
lock (list)
{
plugins = [.. list];
}
return true;
}
plugins = [];
return false;
}
public static ICollection<PluginPair> ValidPluginsForHomeQuery()
@ -577,9 +601,17 @@ namespace Flow.Launcher.Core.Plugin
return [.. _globalPlugins.Values];
}
public static Dictionary<string, PluginPair> GetNonGlobalPlugins()
public static Dictionary<string, List<PluginPair>> GetNonGlobalPlugins()
{
return _nonGlobalPlugins.ToDictionary();
var nonGlobalPlugins = new Dictionary<string, List<PluginPair>>();
foreach (var kvp in _nonGlobalPlugins)
{
lock (kvp.Value)
{
nonGlobalPlugins.Add(kvp.Key, [.. kvp.Value]);
}
}
return nonGlobalPlugins;
}
public static List<PluginPair> GetTranslationPlugins()
@ -722,12 +754,12 @@ namespace Flow.Launcher.Core.Plugin
#region Plugin Action Keyword
[Obsolete("This method is only used for old Flow compatibility.")]
public static bool ActionKeywordRegistered(string actionKeyword)
{
// this method is only checking for action keywords (defined as not '*') registration
// hence the actionKeyword != Query.GlobalPluginWildcardSign logic
return actionKeyword != Query.GlobalPluginWildcardSign
&& _nonGlobalPlugins.ContainsKey(actionKeyword);
// Since now we support to assign one action keyword to multiple plugins,
// this check is unnecessary, so we will just return false here to ensure compatibility for old plugins.
return false;
}
/// <summary>
@ -737,17 +769,34 @@ 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
plugin.Metadata.ActionKeywords.Add(newActionKeyword);
if (!plugin.Metadata.ActionKeywords.Contains(newActionKeyword))
{
plugin.Metadata.ActionKeywords.Add(newActionKeyword);
}
if (plugin.Metadata.ActionKeywords.Count > 0)
{
plugin.Metadata.ActionKeyword = plugin.Metadata.ActionKeywords[0];
@ -765,6 +814,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
@ -775,11 +826,22 @@ 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);
if (plugins.Count == 0)
{
_nonGlobalPlugins.TryRemove(new KeyValuePair<string, List<PluginPair>>(oldActionkeyword, plugins));
}
}
}
}
// Update action keywords and action keyword in plugin metadata
plugin.Metadata.ActionKeywords.Remove(oldActionkeyword);
plugin.Metadata.ActionKeywords.RemoveAll(k => k == oldActionkeyword);
if (plugin.Metadata.ActionKeywords.Count > 0)
{
plugin.Metadata.ActionKeyword = plugin.Metadata.ActionKeywords[0];
@ -1063,10 +1125,18 @@ 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);
if (entry.Value.Count == 0)
{
_nonGlobalPlugins.TryRemove(new KeyValuePair<string, List<PluginPair>>(entry.Key, entry.Value));
}
}
}
}

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,7 @@ 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) && CheckPlugin(pluginPairs))
{
// use non global plugin for query
actionKeyword = possibleActionKeyword;
@ -59,5 +60,13 @@ namespace Flow.Launcher.Core.Plugin
IsHomeQuery = false
};
}
private static bool CheckPlugin(List<PluginPair> pluginPairs)
{
lock (pluginPairs)
{
return pluginPairs.Any(plugin => !plugin.Metadata.Disabled);
}
}
}
}

View file

@ -293,6 +293,11 @@ namespace Flow.Launcher.Plugin
/// </summary>
/// <param name="actionKeyword">The actionkeyword for checking</param>
/// <returns>True if the actionkeyword is already assigned, False otherwise</returns>
/// <remarks>
/// Flow now supports assigning one action keyword to multiple plugins.
/// This method is kept only for legacy Flow compatibility.
/// </remarks>
[Obsolete("Flow now supports assigning one action keyword to multiple plugins. This method always returns false for compatibility.")]
bool ActionKeywordAssigned(string actionKeyword);
/// <summary>

View file

@ -11,9 +11,9 @@ namespace Flow.Launcher.Test
[Test]
public void ExclusivePluginQueryTest()
{
var nonGlobalPlugins = new Dictionary<string, PluginPair>
var nonGlobalPlugins = new Dictionary<string, List<PluginPair>>
{
{">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List<string> {">"}}}}
{ ">", new List<PluginPair>(){ new() { Metadata = new PluginMetadata { ActionKeywords = [">"] } } } }
};
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", "> ping google.com -n 20 -6", nonGlobalPlugins);
@ -34,9 +34,9 @@ namespace Flow.Launcher.Test
[Test]
public void ExclusivePluginQueryIgnoreDisabledTest()
{
var nonGlobalPlugins = new Dictionary<string, PluginPair>
var nonGlobalPlugins = new Dictionary<string, List<PluginPair>>
{
{">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List<string> {">"}, Disabled = true}}}
{ ">", new List<PluginPair>(){ new() { Metadata = new PluginMetadata { ActionKeywords = [">"], Disabled = true } } } }
};
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", "> ping google.com -n 20 -6", nonGlobalPlugins);
@ -51,7 +51,7 @@ namespace Flow.Launcher.Test
[Test]
public void GenericPluginQueryTest()
{
Query q = QueryBuilder.Build("file.txt file2 file3", "file.txt file2 file3", new Dictionary<string, PluginPair>());
Query q = QueryBuilder.Build("file.txt file2 file3", "file.txt file2 file3", []);
ClassicAssert.AreEqual("file.txt file2 file3", q.Search);
ClassicAssert.AreEqual("", q.ActionKeyword);

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
@ -267,7 +267,9 @@ namespace Flow.Launcher
public void AddActionKeyword(string pluginId, string newActionKeyword) =>
PluginManager.AddActionKeyword(pluginId, newActionKeyword);
#pragma warning disable CS0618 // Type or member is obsolete
public bool ActionKeywordAssigned(string actionKeyword) => PluginManager.ActionKeywordRegistered(actionKeyword);
#pragma warning restore CS0618 // Type or member is obsolete
public void RemoveActionKeyword(string pluginId, string oldActionKeyword) =>
PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword);