Merge branch 'dev' into fix_loading_uwp
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
|
@ -53,6 +53,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Droplex" Version="1.2.0" />
|
||||
<PackageReference Include="FSharp.Core" Version="4.7.1" />
|
||||
<PackageReference Include="squirrel.windows" Version="1.5.2" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
|
|
@ -42,6 +43,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
public class JsonRPCQueryResponseModel : JsonRPCResponseModel
|
||||
{
|
||||
[JsonPropertyName("result")]
|
||||
public new List<JsonRPCResult> Result { get; set; }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Newtonsoft.Json;
|
||||
using Flow.Launcher.Infrastructure.Exception;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
|
@ -65,7 +65,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
List<Result> results = new List<Result>();
|
||||
|
||||
JsonRPCQueryResponseModel queryResponseModel = JsonConvert.DeserializeObject<JsonRPCQueryResponseModel>(output);
|
||||
JsonRPCQueryResponseModel queryResponseModel = JsonSerializer.Deserialize<JsonRPCQueryResponseModel>(output);
|
||||
if (queryResponseModel.Result == null) return null;
|
||||
|
||||
foreach (JsonRPCResult result in queryResponseModel.Result)
|
||||
|
|
@ -84,7 +84,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
else
|
||||
{
|
||||
string actionReponse = ExecuteCallback(result1.JsonRPCAction);
|
||||
JsonRPCRequestModel jsonRpcRequestModel = JsonConvert.DeserializeObject<JsonRPCRequestModel>(actionReponse);
|
||||
JsonRPCRequestModel jsonRpcRequestModel = JsonSerializer.Deserialize<JsonRPCRequestModel>(actionReponse);
|
||||
if (jsonRpcRequestModel != null
|
||||
&& !String.IsNullOrEmpty(jsonRpcRequestModel.Method)
|
||||
&& jsonRpcRequestModel.Method.StartsWith("Flow.Launcher."))
|
||||
|
|
@ -147,47 +147,42 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
try
|
||||
{
|
||||
using (var process = Process.Start(startInfo))
|
||||
using var process = Process.Start(startInfo);
|
||||
if (process == null)
|
||||
{
|
||||
if (process != null)
|
||||
Log.Error("|JsonRPCPlugin.Execute|Can't start new process");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
using var standardOutput = process.StandardOutput;
|
||||
var result = standardOutput.ReadToEnd();
|
||||
if (string.IsNullOrEmpty(result))
|
||||
{
|
||||
using (var standardError = process.StandardError)
|
||||
{
|
||||
using (var standardOutput = process.StandardOutput)
|
||||
var error = standardError.ReadToEnd();
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
var result = standardOutput.ReadToEnd();
|
||||
if (string.IsNullOrEmpty(result))
|
||||
{
|
||||
using (var standardError = process.StandardError)
|
||||
{
|
||||
var error = standardError.ReadToEnd();
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
Log.Error($"|JsonRPCPlugin.Execute|{error}");
|
||||
return string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("|JsonRPCPlugin.Execute|Empty standard output and standard error.");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (result.StartsWith("DEBUG:"))
|
||||
{
|
||||
MessageBox.Show(new Form { TopMost = true }, result.Substring(6));
|
||||
return string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
Log.Error($"|JsonRPCPlugin.Execute|{error}");
|
||||
return string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("|JsonRPCPlugin.Execute|Empty standard output and standard error.");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("|JsonRPCPlugin.Execute|Can't start new process");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
else if (result.StartsWith("DEBUG:"))
|
||||
{
|
||||
MessageBox.Show(new Form { TopMost = true }, result.Substring(6));
|
||||
return string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
dependencyResolver = new AssemblyDependencyResolver(assemblyFilePath);
|
||||
assemblyName = new AssemblyName(Path.GetFileNameWithoutExtension(assemblyFilePath));
|
||||
|
||||
referencedPluginPackageDependencyResolver =
|
||||
referencedPluginPackageDependencyResolver =
|
||||
new AssemblyDependencyResolver(Path.Combine(Constant.ProgramDirectory, "Flow.Launcher.Plugin.dll"));
|
||||
}
|
||||
|
||||
|
|
@ -38,15 +38,15 @@ namespace Flow.Launcher.Core.Plugin
|
|||
// that use Newtonsoft.Json
|
||||
if (assemblyPath == null || ExistsInReferencedPluginPackage(assemblyName))
|
||||
return null;
|
||||
|
||||
|
||||
return LoadFromAssemblyPath(assemblyPath);
|
||||
}
|
||||
|
||||
internal Type FromAssemblyGetTypeOfInterface(Assembly assembly, Type type)
|
||||
internal Type FromAssemblyGetTypeOfInterface(Assembly assembly, params Type[] types)
|
||||
{
|
||||
var allTypes = assembly.ExportedTypes;
|
||||
|
||||
return allTypes.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(type));
|
||||
return allTypes.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Intersect(types).Any());
|
||||
}
|
||||
|
||||
internal bool ExistsInReferencedPluginPackage(AssemblyName assemblyName)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Plugin;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
|
|
@ -61,7 +61,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
PluginMetadata metadata;
|
||||
try
|
||||
{
|
||||
metadata = JsonConvert.DeserializeObject<PluginMetadata>(File.ReadAllText(configPath));
|
||||
metadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(configPath));
|
||||
metadata.PluginDirectory = pluginDirectory;
|
||||
// for plugins which doesn't has ActionKeywords key
|
||||
metadata.ActionKeywords = metadata.ActionKeywords ?? new List<string> { metadata.ActionKeyword };
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Collections.Concurrent;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
|
|
@ -52,13 +53,14 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
}
|
||||
|
||||
public static void ReloadData()
|
||||
public static async Task ReloadData()
|
||||
{
|
||||
foreach(var plugin in AllPlugins)
|
||||
await Task.WhenAll(AllPlugins.Select(plugin => plugin.Plugin switch
|
||||
{
|
||||
var reloadablePlugin = plugin.Plugin as IReloadable;
|
||||
reloadablePlugin?.ReloadData();
|
||||
}
|
||||
IReloadable p => Task.Run(p.ReloadData),
|
||||
IAsyncReloadable p => p.ReloadDataAsync(),
|
||||
_ => Task.CompletedTask,
|
||||
}).ToArray());
|
||||
}
|
||||
|
||||
static PluginManager()
|
||||
|
|
@ -86,50 +88,62 @@ namespace Flow.Launcher.Core.Plugin
|
|||
/// Call initialize for all plugins
|
||||
/// </summary>
|
||||
/// <returns>return the list of failed to init plugins or null for none</returns>
|
||||
public static void InitializePlugins(IPublicAPI api)
|
||||
public static async Task InitializePlugins(IPublicAPI api)
|
||||
{
|
||||
API = api;
|
||||
var failedPlugins = new ConcurrentQueue<PluginPair>();
|
||||
Parallel.ForEach(AllPlugins, pair =>
|
||||
|
||||
var InitTasks = AllPlugins.Select(pair => Task.Run(async delegate
|
||||
{
|
||||
try
|
||||
{
|
||||
var milliseconds = Stopwatch.Debug($"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>", () =>
|
||||
var milliseconds = pair.Plugin switch
|
||||
{
|
||||
pair.Plugin.Init(new PluginInitContext
|
||||
{
|
||||
CurrentPluginMetadata = pair.Metadata,
|
||||
API = API
|
||||
});
|
||||
});
|
||||
IAsyncPlugin plugin
|
||||
=> await Stopwatch.DebugAsync($"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>",
|
||||
() => plugin.InitAsync(new PluginInitContext(pair.Metadata, API))),
|
||||
IPlugin plugin
|
||||
=> Stopwatch.Debug($"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>",
|
||||
() => plugin.Init(new PluginInitContext(pair.Metadata, API))),
|
||||
_ => throw new ArgumentException(),
|
||||
};
|
||||
pair.Metadata.InitTime += milliseconds;
|
||||
Log.Info($"|PluginManager.InitializePlugins|Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>");
|
||||
Log.Info(
|
||||
$"|PluginManager.InitializePlugins|Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(nameof(PluginManager), $"Fail to Init plugin: {pair.Metadata.Name}", e);
|
||||
pair.Metadata.Disabled = true;
|
||||
pair.Metadata.Disabled = true;
|
||||
failedPlugins.Enqueue(pair);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
await Task.WhenAll(InitTasks);
|
||||
|
||||
_contextMenuPlugins = GetPluginsForInterface<IContextMenu>();
|
||||
foreach (var plugin in AllPlugins)
|
||||
{
|
||||
if (IsGlobalPlugin(plugin.Metadata))
|
||||
GlobalPlugins.Add(plugin);
|
||||
|
||||
// Plugins may have multiple ActionKeywords, eg. WebSearch
|
||||
plugin.Metadata.ActionKeywords
|
||||
.Where(x => x != Query.GlobalPluginWildcardSign)
|
||||
.ToList()
|
||||
.ForEach(x => NonGlobalPlugins[x] = plugin);
|
||||
foreach (var actionKeyword in plugin.Metadata.ActionKeywords)
|
||||
{
|
||||
switch (actionKeyword)
|
||||
{
|
||||
case Query.GlobalPluginWildcardSign:
|
||||
GlobalPlugins.Add(plugin);
|
||||
break;
|
||||
default:
|
||||
NonGlobalPlugins[actionKeyword] = plugin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failedPlugins.Any())
|
||||
{
|
||||
var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name));
|
||||
API.ShowMsg($"Fail to Init Plugins", $"Plugins: {failed} - fail to load and would be disabled, please contact plugin creator for help", "", false);
|
||||
API.ShowMsg($"Fail to Init Plugins",
|
||||
$"Plugins: {failed} - fail to load and would be disabled, please contact plugin creator for help",
|
||||
"", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -146,24 +160,48 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
}
|
||||
|
||||
public static List<Result> QueryForPlugin(PluginPair pair, Query query)
|
||||
public static async Task<List<Result>> QueryForPlugin(PluginPair pair, Query query, CancellationToken token)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
try
|
||||
{
|
||||
var metadata = pair.Metadata;
|
||||
var milliseconds = Stopwatch.Debug($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", () =>
|
||||
|
||||
long milliseconds = -1L;
|
||||
|
||||
switch (pair.Plugin)
|
||||
{
|
||||
results = pair.Plugin.Query(query) ?? new List<Result>();
|
||||
UpdatePluginMetadata(results, metadata, query);
|
||||
});
|
||||
case IAsyncPlugin plugin:
|
||||
milliseconds = await Stopwatch.DebugAsync($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}",
|
||||
async () => results = await plugin.QueryAsync(query, token).ConfigureAwait(false));
|
||||
break;
|
||||
case IPlugin plugin:
|
||||
await Task.Run(() => milliseconds = Stopwatch.Debug($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}",
|
||||
() => results = plugin.Query(query)), token).ConfigureAwait(false);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (results == null)
|
||||
return results;
|
||||
UpdatePluginMetadata(results, metadata, query);
|
||||
|
||||
metadata.QueryCount += 1;
|
||||
metadata.AvgQueryTime = metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2;
|
||||
metadata.AvgQueryTime =
|
||||
metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2;
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// null will be fine since the results will only be added into queue if the token hasn't been cancelled
|
||||
return results = null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
|
@ -182,11 +220,6 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
}
|
||||
|
||||
private static bool IsGlobalPlugin(PluginMetadata metadata)
|
||||
{
|
||||
return metadata.ActionKeywords.Contains(Query.GlobalPluginWildcardSign);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// get specified plugin, return null if not found
|
||||
/// </summary>
|
||||
|
|
@ -222,16 +255,19 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{pluginPair.Metadata.Name}>", e);
|
||||
Log.Exception(
|
||||
$"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{pluginPair.Metadata.Name}>",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public static bool ActionKeywordRegistered(string actionKeyword)
|
||||
{
|
||||
return actionKeyword != Query.GlobalPluginWildcardSign
|
||||
&& NonGlobalPlugins.ContainsKey(actionKeyword);
|
||||
&& NonGlobalPlugins.ContainsKey(actionKeyword);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -249,6 +285,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
NonGlobalPlugins[newActionKeyword] = plugin;
|
||||
}
|
||||
|
||||
plugin.Metadata.ActionKeywords.Add(newActionKeyword);
|
||||
}
|
||||
|
||||
|
|
@ -262,16 +299,16 @@ namespace Flow.Launcher.Core.Plugin
|
|||
if (oldActionkeyword == Query.GlobalPluginWildcardSign
|
||||
&& // Plugins may have multiple ActionKeywords that are global, eg. WebSearch
|
||||
plugin.Metadata.ActionKeywords
|
||||
.Where(x => x == Query.GlobalPluginWildcardSign)
|
||||
.ToList()
|
||||
.Count == 1)
|
||||
.Where(x => x == Query.GlobalPluginWildcardSign)
|
||||
.ToList()
|
||||
.Count == 1)
|
||||
{
|
||||
GlobalPlugins.Remove(plugin);
|
||||
}
|
||||
|
||||
|
||||
if (oldActionkeyword != Query.GlobalPluginWildcardSign)
|
||||
NonGlobalPlugins.Remove(oldActionkeyword);
|
||||
|
||||
|
||||
|
||||
plugin.Metadata.ActionKeywords.Remove(oldActionkeyword);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Droplex;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
|
|
@ -22,7 +23,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
public static List<PluginPair> Plugins(List<PluginMetadata> metadatas, PluginsSettings settings)
|
||||
{
|
||||
var dotnetPlugins = DotNetPlugins(metadatas);
|
||||
var pythonPlugins = PythonPlugins(metadatas, settings.PythonDirectory);
|
||||
var pythonPlugins = PythonPlugins(metadatas, settings);
|
||||
var executablePlugins = ExecutablePlugins(metadatas);
|
||||
var plugins = dotnetPlugins.Concat(pythonPlugins).Concat(executablePlugins).ToList();
|
||||
return plugins;
|
||||
|
|
@ -37,56 +38,59 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
foreach (var metadata in metadatas)
|
||||
{
|
||||
var milliseconds = Stopwatch.Debug($"|PluginsLoader.DotNetPlugins|Constructor init cost for {metadata.Name}", () =>
|
||||
{
|
||||
|
||||
var milliseconds = Stopwatch.Debug(
|
||||
$"|PluginsLoader.DotNetPlugins|Constructor init cost for {metadata.Name}", () =>
|
||||
{
|
||||
#if DEBUG
|
||||
var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath);
|
||||
var assembly = assemblyLoader.LoadAssemblyAndDependencies();
|
||||
var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin));
|
||||
var plugin = (IPlugin)Activator.CreateInstance(type);
|
||||
#else
|
||||
Assembly assembly = null;
|
||||
IPlugin plugin = null;
|
||||
|
||||
try
|
||||
{
|
||||
var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath);
|
||||
assembly = assemblyLoader.LoadAssemblyAndDependencies();
|
||||
var assembly = assemblyLoader.LoadAssemblyAndDependencies();
|
||||
var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin),
|
||||
typeof(IAsyncPlugin));
|
||||
|
||||
var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin));
|
||||
var plugin = Activator.CreateInstance(type);
|
||||
#else
|
||||
Assembly assembly = null;
|
||||
object plugin = null;
|
||||
|
||||
plugin = (IPlugin)Activator.CreateInstance(type);
|
||||
}
|
||||
catch (Exception e) when (assembly == null)
|
||||
{
|
||||
Log.Exception($"|PluginsLoader.DotNetPlugins|Couldn't load assembly for the plugin: {metadata.Name}", e);
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
Log.Exception($"|PluginsLoader.DotNetPlugins|Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
|
||||
}
|
||||
catch (ReflectionTypeLoadException e)
|
||||
{
|
||||
Log.Exception($"|PluginsLoader.DotNetPlugins|The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|PluginsLoader.DotNetPlugins|The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
|
||||
}
|
||||
try
|
||||
{
|
||||
var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath);
|
||||
assembly = assemblyLoader.LoadAssemblyAndDependencies();
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
erroredPlugins.Add(metadata.Name);
|
||||
return;
|
||||
}
|
||||
var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin),
|
||||
typeof(IAsyncPlugin));
|
||||
|
||||
plugin = Activator.CreateInstance(type);
|
||||
}
|
||||
catch (Exception e) when (assembly == null)
|
||||
{
|
||||
Log.Exception($"|PluginsLoader.DotNetPlugins|Couldn't load assembly for the plugin: {metadata.Name}", e);
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
Log.Exception($"|PluginsLoader.DotNetPlugins|Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
|
||||
}
|
||||
catch (ReflectionTypeLoadException e)
|
||||
{
|
||||
Log.Exception($"|PluginsLoader.DotNetPlugins|The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|PluginsLoader.DotNetPlugins|The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
|
||||
}
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
erroredPlugins.Add(metadata.Name);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
plugins.Add(new PluginPair
|
||||
{
|
||||
Plugin = plugin,
|
||||
Metadata = metadata
|
||||
plugins.Add(new PluginPair
|
||||
{
|
||||
Plugin = plugin,
|
||||
Metadata = metadata
|
||||
});
|
||||
});
|
||||
});
|
||||
metadata.InitTime += milliseconds;
|
||||
}
|
||||
|
||||
|
|
@ -95,26 +99,29 @@ namespace Flow.Launcher.Core.Plugin
|
|||
var errorPluginString = String.Join(Environment.NewLine, erroredPlugins);
|
||||
|
||||
var errorMessage = "The following "
|
||||
+ (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ")
|
||||
+ "errored and cannot be loaded:";
|
||||
+ (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ")
|
||||
+ "errored and cannot be loaded:";
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
MessageBox.Show($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
|
||||
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
|
||||
$"Please refer to the logs for more information","",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
|
||||
$"Please refer to the logs for more information", "",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
});
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
public static IEnumerable<PluginPair> PythonPlugins(List<PluginMetadata> source, string pythonDirectory)
|
||||
public static IEnumerable<PluginPair> PythonPlugins(List<PluginMetadata> source, PluginsSettings settings)
|
||||
{
|
||||
// try to set Constant.PythonPath, either from
|
||||
if (!source.Any(o => o.Language.ToUpper() == AllowedLanguage.Python))
|
||||
return new List<PluginPair>();
|
||||
|
||||
// Try setting Constant.PythonPath first, either from
|
||||
// PATH or from the given pythonDirectory
|
||||
if (string.IsNullOrEmpty(pythonDirectory))
|
||||
if (string.IsNullOrEmpty(settings.PythonDirectory))
|
||||
{
|
||||
var paths = Environment.GetEnvironmentVariable(PATH);
|
||||
if (paths != null)
|
||||
|
|
@ -126,47 +133,103 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
if (pythonInPath)
|
||||
{
|
||||
Constant.PythonPath = PythonExecutable;
|
||||
Constant.PythonPath =
|
||||
Path.Combine(paths.Split(';').Where(p => p.ToLower().Contains(Python)).FirstOrDefault(), PythonExecutable);
|
||||
settings.PythonDirectory = FilesFolders.GetPreviousExistingDirectory(FilesFolders.LocationExists, Constant.PythonPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("|PluginsLoader.PythonPlugins|Python can't be found in PATH.");
|
||||
Log.Error("PluginsLoader","Failed to set Python path despite the environment variable PATH is found", "PythonPlugins");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("|PluginsLoader.PythonPlugins|PATH environment variable is not set.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var path = Path.Combine(pythonDirectory, PythonExecutable);
|
||||
var path = Path.Combine(settings.PythonDirectory, PythonExecutable);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Constant.PythonPath = path;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"|PluginsLoader.PythonPlugins|Can't find python executable in {path}");
|
||||
Log.Error("PluginsLoader",$"Tried to automatically set from Settings.PythonDirectory " +
|
||||
$"but can't find python executable in {path}", "PythonPlugins");
|
||||
}
|
||||
}
|
||||
|
||||
// if we have a path to the python executable,
|
||||
// load every python plugin pair.
|
||||
if (String.IsNullOrEmpty(Constant.PythonPath))
|
||||
if (string.IsNullOrEmpty(settings.PythonDirectory))
|
||||
{
|
||||
if (MessageBox.Show("Flow detected you have installed Python plugins, " +
|
||||
"would you like to install Python to run them? " +
|
||||
Environment.NewLine + Environment.NewLine +
|
||||
"Click no if it's already installed, " +
|
||||
"and you will be prompted to select the folder that contains the Python executable",
|
||||
string.Empty, MessageBoxButtons.YesNo) == DialogResult.No
|
||||
&& string.IsNullOrEmpty(settings.PythonDirectory))
|
||||
{
|
||||
var dlg = new FolderBrowserDialog
|
||||
{
|
||||
SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
|
||||
};
|
||||
|
||||
var result = dlg.ShowDialog();
|
||||
if (result == DialogResult.OK)
|
||||
{
|
||||
string pythonDirectory = dlg.SelectedPath;
|
||||
if (!string.IsNullOrEmpty(pythonDirectory))
|
||||
{
|
||||
var pythonPath = Path.Combine(pythonDirectory, PythonExecutable);
|
||||
if (File.Exists(pythonPath))
|
||||
{
|
||||
settings.PythonDirectory = pythonDirectory;
|
||||
Constant.PythonPath = pythonPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("Can't find python in given directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DroplexPackage.Drop(App.python3_9_1).Wait();
|
||||
|
||||
var installedPythonDirectory =
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python39");
|
||||
var pythonPath = Path.Combine(installedPythonDirectory, PythonExecutable);
|
||||
if (FilesFolders.FileExists(pythonPath))
|
||||
{
|
||||
settings.PythonDirectory = installedPythonDirectory;
|
||||
Constant.PythonPath = pythonPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("PluginsLoader",
|
||||
$"Failed to set Python path after Droplex install, {pythonPath} does not exist",
|
||||
"PythonPlugins");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(settings.PythonDirectory))
|
||||
{
|
||||
MessageBox.Show("Unable to set Python executable path, please try from Flow's settings (scroll down to the bottom).");
|
||||
Log.Error("PluginsLoader",
|
||||
$"Not able to successfully set Python path, the PythonDirectory variable is still an empty string.",
|
||||
"PythonPlugins");
|
||||
|
||||
return new List<PluginPair>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return source
|
||||
.Where(o => o.Language.ToUpper() == AllowedLanguage.Python)
|
||||
.Select(metadata => new PluginPair
|
||||
{
|
||||
Plugin = new PythonPlugin(Constant.PythonPath),
|
||||
Metadata = metadata
|
||||
});
|
||||
}
|
||||
|
||||
return source
|
||||
.Where(o => o.Language.ToUpper() == AllowedLanguage.Python)
|
||||
.Select(metadata => new PluginPair
|
||||
{
|
||||
Plugin = new PythonPlugin(Constant.PythonPath),
|
||||
Metadata = metadata
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static IEnumerable<PluginPair> ExecutablePlugins(IEnumerable<PluginMetadata> source)
|
||||
|
|
@ -179,6 +242,5 @@ namespace Flow.Launcher.Core.Plugin
|
|||
Metadata = metadata
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,14 @@ using System.Threading.Tasks;
|
|||
using System.Windows;
|
||||
using JetBrains.Annotations;
|
||||
using Squirrel;
|
||||
using Newtonsoft.Json;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Http;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using System.IO;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Flow.Launcher.Core
|
||||
{
|
||||
|
|
@ -29,101 +28,84 @@ namespace Flow.Launcher.Core
|
|||
GitHubRepository = gitHubRepository;
|
||||
}
|
||||
|
||||
public async Task UpdateApp(IPublicAPI api , bool silentUpdate = true)
|
||||
public async Task UpdateApp(IPublicAPI api, bool silentUpdate = true)
|
||||
{
|
||||
UpdateManager updateManager;
|
||||
UpdateInfo newUpdateInfo;
|
||||
|
||||
if (!silentUpdate)
|
||||
api.ShowMsg("Please wait...", "Checking for new update");
|
||||
|
||||
try
|
||||
{
|
||||
updateManager = await GitHubUpdateManager(GitHubRepository);
|
||||
}
|
||||
catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException)
|
||||
{
|
||||
Log.Exception($"|Updater.UpdateApp|Please check your connection and proxy settings to api.github.com.", e);
|
||||
return;
|
||||
}
|
||||
UpdateInfo newUpdateInfo;
|
||||
|
||||
try
|
||||
{
|
||||
// UpdateApp CheckForUpdate will return value only if the app is squirrel installed
|
||||
newUpdateInfo = await updateManager.CheckForUpdate().NonNull();
|
||||
}
|
||||
catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException)
|
||||
{
|
||||
Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to api.github.com.", e);
|
||||
updateManager.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString());
|
||||
var currentVersion = Version.Parse(Constant.Version);
|
||||
|
||||
Log.Info($"|Updater.UpdateApp|Future Release <{newUpdateInfo.FutureReleaseEntry.Formatted()}>");
|
||||
|
||||
if (newReleaseVersion <= currentVersion)
|
||||
{
|
||||
if (!silentUpdate)
|
||||
MessageBox.Show("You already have the latest Flow Launcher version");
|
||||
updateManager.Dispose();
|
||||
return;
|
||||
}
|
||||
api.ShowMsg(api.GetTranslation("pleaseWait"),
|
||||
api.GetTranslation("update_flowlauncher_update_check"));
|
||||
|
||||
if (!silentUpdate)
|
||||
api.ShowMsg("Update found", "Updating...");
|
||||
using var updateManager = await GitHubUpdateManager(GitHubRepository).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply);
|
||||
|
||||
// UpdateApp CheckForUpdate will return value only if the app is squirrel installed
|
||||
newUpdateInfo = await updateManager.CheckForUpdate().NonNull().ConfigureAwait(false);
|
||||
|
||||
var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString());
|
||||
var currentVersion = Version.Parse(Constant.Version);
|
||||
|
||||
Log.Info($"|Updater.UpdateApp|Future Release <{newUpdateInfo.FutureReleaseEntry.Formatted()}>");
|
||||
|
||||
if (newReleaseVersion <= currentVersion)
|
||||
{
|
||||
if (!silentUpdate)
|
||||
MessageBox.Show(api.GetTranslation("update_flowlauncher_already_on_latest"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!silentUpdate)
|
||||
api.ShowMsg(api.GetTranslation("update_flowlauncher_update_found"),
|
||||
api.GetTranslation("update_flowlauncher_updating"));
|
||||
|
||||
await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false);
|
||||
|
||||
await updateManager.ApplyReleases(newUpdateInfo).ConfigureAwait(false);
|
||||
|
||||
if (DataLocation.PortableDataLocationInUse())
|
||||
{
|
||||
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
|
||||
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination);
|
||||
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination))
|
||||
MessageBox.Show(string.Format(api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
|
||||
DataLocation.PortableDataPath,
|
||||
targetDestination));
|
||||
}
|
||||
else
|
||||
{
|
||||
await updateManager.CreateUninstallerRegistryEntry().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var newVersionTips = NewVersinoTips(newReleaseVersion.ToString());
|
||||
|
||||
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
|
||||
|
||||
if (MessageBox.Show(newVersionTips, api.GetTranslation("update_flowlauncher_new_update"), MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
||||
}
|
||||
}
|
||||
catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException)
|
||||
{
|
||||
Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
|
||||
updateManager.Dispose();
|
||||
api.ShowMsg(api.GetTranslation("update_flowlauncher_fail"),
|
||||
api.GetTranslation("update_flowlauncher_check_connection"));
|
||||
return;
|
||||
}
|
||||
|
||||
await updateManager.ApplyReleases(newUpdateInfo);
|
||||
|
||||
if (DataLocation.PortableDataLocationInUse())
|
||||
{
|
||||
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
|
||||
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination);
|
||||
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination))
|
||||
MessageBox.Show("Flow Launcher was not able to move your user profile data to the new update version. Please manually " +
|
||||
$"move your profile data folder from {DataLocation.PortableDataPath} to {targetDestination}");
|
||||
}
|
||||
else
|
||||
{
|
||||
await updateManager.CreateUninstallerRegistryEntry();
|
||||
}
|
||||
|
||||
var newVersionTips = NewVersinoTips(newReleaseVersion.ToString());
|
||||
|
||||
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
|
||||
|
||||
// always dispose UpdateManager
|
||||
updateManager.Dispose();
|
||||
|
||||
if (MessageBox.Show(newVersionTips, "New Update", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private class GithubRelease
|
||||
{
|
||||
[JsonProperty("prerelease")]
|
||||
[JsonPropertyName("prerelease")]
|
||||
public bool Prerelease { get; [UsedImplicitly] set; }
|
||||
|
||||
[JsonProperty("published_at")]
|
||||
[JsonPropertyName("published_at")]
|
||||
public DateTime PublishedAt { get; [UsedImplicitly] set; }
|
||||
|
||||
[JsonProperty("html_url")]
|
||||
[JsonPropertyName("html_url")]
|
||||
public string HtmlUrl { get; [UsedImplicitly] set; }
|
||||
}
|
||||
|
||||
|
|
@ -133,13 +115,13 @@ namespace Flow.Launcher.Core
|
|||
var uri = new Uri(repository);
|
||||
var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases";
|
||||
|
||||
var json = await Http.Get(api);
|
||||
var jsonStream = await Http.GetStreamAsync(api).ConfigureAwait(false);
|
||||
|
||||
var releases = JsonConvert.DeserializeObject<List<GithubRelease>>(json);
|
||||
var releases = await System.Text.Json.JsonSerializer.DeserializeAsync<List<GithubRelease>>(jsonStream).ConfigureAwait(false);
|
||||
var latest = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.PublishedAt).First();
|
||||
var latestUrl = latest.HtmlUrl.Replace("/tag/", "/download/");
|
||||
|
||||
var client = new WebClient { Proxy = Http.WebProxy() };
|
||||
var client = new WebClient { Proxy = Http.WebProxy };
|
||||
var downloader = new FileDownloader(client);
|
||||
|
||||
var manager = new UpdateManager(latestUrl, urlDownloader: downloader);
|
||||
|
|
|
|||
|
|
@ -30,10 +30,12 @@ namespace Flow.Launcher.Infrastructure
|
|||
|
||||
public static string PythonPath;
|
||||
|
||||
public static readonly string QueryTextBoxIconImagePath = $"{ProgramDirectory}\\Images\\mainsearch.png";
|
||||
public static readonly string QueryTextBoxIconImagePath = $"{ProgramDirectory}\\Images\\mainsearch.svg";
|
||||
|
||||
public const string DefaultTheme = "Darker";
|
||||
|
||||
public const string Themes = "Themes";
|
||||
|
||||
public const string Website = "https://flow-launcher.github.io";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NLog.Schema" Version="4.7.0-rc1" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="4.9.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure
|
||||
{
|
||||
public static class Helper
|
||||
{
|
||||
static Helper()
|
||||
{
|
||||
jsonFormattedSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy
|
||||
/// </summary>
|
||||
|
|
@ -65,13 +71,18 @@ namespace Flow.Launcher.Infrastructure
|
|||
}
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions jsonFormattedSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
public static string Formatted<T>(this T t)
|
||||
{
|
||||
var formatted = JsonConvert.SerializeObject(
|
||||
t,
|
||||
Formatting.Indented,
|
||||
new StringEnumConverter()
|
||||
);
|
||||
var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
|
||||
return formatted;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ using System.Threading.Tasks;
|
|||
using JetBrains.Annotations;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Http
|
||||
{
|
||||
|
|
@ -13,6 +16,8 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
{
|
||||
private const string UserAgent = @"Mozilla/5.0 (Trident/7.0; rv:11.0) like Gecko";
|
||||
|
||||
private static HttpClient client = new HttpClient();
|
||||
|
||||
static Http()
|
||||
{
|
||||
// need to be added so it would work on a win10 machine
|
||||
|
|
@ -20,58 +25,118 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls
|
||||
| SecurityProtocolType.Tls11
|
||||
| SecurityProtocolType.Tls12;
|
||||
|
||||
client.DefaultRequestHeaders.Add("User-Agent", UserAgent);
|
||||
HttpClient.DefaultProxy = WebProxy;
|
||||
}
|
||||
|
||||
public static HttpProxy Proxy { private get; set; }
|
||||
public static IWebProxy WebProxy()
|
||||
private static HttpProxy proxy;
|
||||
|
||||
public static HttpProxy Proxy
|
||||
{
|
||||
if (Proxy != null && Proxy.Enabled && !string.IsNullOrEmpty(Proxy.Server))
|
||||
private get { return proxy; }
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(Proxy.UserName) || string.IsNullOrEmpty(Proxy.Password))
|
||||
proxy = value;
|
||||
proxy.PropertyChanged += UpdateProxy;
|
||||
UpdateProxy(ProxyProperty.Enabled);
|
||||
}
|
||||
}
|
||||
|
||||
public static WebProxy WebProxy { get; } = new WebProxy();
|
||||
|
||||
/// <summary>
|
||||
/// Update the Address of the Proxy to modify the client Proxy
|
||||
/// </summary>
|
||||
public static void UpdateProxy(ProxyProperty property)
|
||||
{
|
||||
(WebProxy.Address, WebProxy.Credentials) = property switch
|
||||
{
|
||||
ProxyProperty.Enabled => Proxy.Enabled switch
|
||||
{
|
||||
var webProxy = new WebProxy(Proxy.Server, Proxy.Port);
|
||||
return webProxy;
|
||||
true => Proxy.UserName switch
|
||||
{
|
||||
var userName when !string.IsNullOrEmpty(userName) =>
|
||||
(new Uri($"http://{Proxy.Server}:{Proxy.Port}"), null),
|
||||
_ => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"),
|
||||
new NetworkCredential(Proxy.UserName, Proxy.Password))
|
||||
},
|
||||
false => (null, null)
|
||||
},
|
||||
ProxyProperty.Server => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), WebProxy.Credentials),
|
||||
ProxyProperty.Port => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), WebProxy.Credentials),
|
||||
ProxyProperty.UserName => (WebProxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)),
|
||||
ProxyProperty.Password => (WebProxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
await using var fileStream = new FileStream(filePath, FileMode.CreateNew);
|
||||
await response.Content.CopyToAsync(fileStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
var webProxy = new WebProxy(Proxy.Server, Proxy.Port)
|
||||
{
|
||||
Credentials = new NetworkCredential(Proxy.UserName, Proxy.Password)
|
||||
};
|
||||
return webProxy;
|
||||
throw new HttpRequestException($"Error code <{response.StatusCode}> returned from <{url}>");
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
Log.Exception("Infrastructure.Http", "Http Request Error", e, "DownloadAsync");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchrously get the result as string from url.
|
||||
/// When supposing the result larger than 83kb, try using GetStreamAsync to avoid reading as string
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns>The Http result as string. Null if cancellation requested</returns>
|
||||
public static Task<string> GetAsync([NotNull] string url, CancellationToken token = default)
|
||||
{
|
||||
Log.Debug($"|Http.Get|Url <{url}>");
|
||||
return GetAsync(new Uri(url.Replace("#", "%23")), token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns>The Http result as string. Null if cancellation requested</returns>
|
||||
public static async Task<string> GetAsync([NotNull] Uri url, CancellationToken token = default)
|
||||
{
|
||||
Log.Debug($"|Http.Get|Url <{url}>");
|
||||
using var response = await client.GetAsync(url, token);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
return content;
|
||||
}
|
||||
else
|
||||
{
|
||||
return WebRequest.GetSystemWebProxy();
|
||||
throw new HttpRequestException(
|
||||
$"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Download([NotNull] string url, [NotNull] string filePath)
|
||||
{
|
||||
var client = new WebClient { Proxy = WebProxy() };
|
||||
client.Headers.Add("user-agent", UserAgent);
|
||||
client.DownloadFile(url, filePath);
|
||||
}
|
||||
|
||||
public static async Task<string> Get([NotNull] string url, string encoding = "UTF-8")
|
||||
/// <summary>
|
||||
/// Asynchrously get the result as stream from url.
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Stream> GetStreamAsync([NotNull] string url, CancellationToken token = default)
|
||||
{
|
||||
Log.Debug($"|Http.Get|Url <{url}>");
|
||||
var request = WebRequest.CreateHttp(url);
|
||||
request.Method = "GET";
|
||||
request.Timeout = 1000;
|
||||
request.Proxy = WebProxy();
|
||||
request.UserAgent = UserAgent;
|
||||
var response = await request.GetResponseAsync() as HttpWebResponse;
|
||||
response = response.NonNull();
|
||||
var stream = response.GetResponseStream().NonNull();
|
||||
|
||||
using var reader = new StreamReader(stream, Encoding.GetEncoding(encoding));
|
||||
var content = await reader.ReadToEndAsync();
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
throw new HttpRequestException($"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>");
|
||||
|
||||
return content;
|
||||
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
|
||||
return await response.Content.ReadAsStreamAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,8 +73,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
var contains = Data.ContainsKey(key) && Data[key] != null;
|
||||
return contains;
|
||||
return Data.ContainsKey(key) && Data[key].imageSource != null;
|
||||
}
|
||||
|
||||
public int CacheSize()
|
||||
|
|
|
|||
|
|
@ -50,14 +50,18 @@ namespace Flow.Launcher.Infrastructure.Logger
|
|||
return valid;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public static void Exception(string className, string message, System.Exception exception, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
#if DEBUG
|
||||
throw exception;
|
||||
#else
|
||||
var classNameWithMethod = CheckClassAndMessageAndReturnFullClassWithMethod(className, message, methodName);
|
||||
|
||||
ExceptionInternal(classNameWithMethod, message, exception);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static string CheckClassAndMessageAndReturnFullClassWithMethod(string className, string message,
|
||||
|
|
@ -128,7 +132,7 @@ namespace Flow.Launcher.Infrastructure.Logger
|
|||
public static void Exception(string message, System.Exception e)
|
||||
{
|
||||
#if DEBUG
|
||||
throw e;
|
||||
throw e;
|
||||
#else
|
||||
if (FormatValid(message))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
|
|
@ -8,14 +9,109 @@ using ToolGood.Words.Pinyin;
|
|||
|
||||
namespace Flow.Launcher.Infrastructure
|
||||
{
|
||||
public class TranslationMapping
|
||||
{
|
||||
private bool constructed;
|
||||
|
||||
private List<int> originalIndexs = new List<int>();
|
||||
private List<int> translatedIndexs = new List<int>();
|
||||
private int translaedLength = 0;
|
||||
|
||||
public string key { get; private set; }
|
||||
|
||||
public void setKey(string key)
|
||||
{
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public void AddNewIndex(int originalIndex, int translatedIndex, int length)
|
||||
{
|
||||
if (constructed)
|
||||
throw new InvalidOperationException("Mapping shouldn't be changed after constructed");
|
||||
|
||||
originalIndexs.Add(originalIndex);
|
||||
translatedIndexs.Add(translatedIndex);
|
||||
translatedIndexs.Add(translatedIndex + length);
|
||||
translaedLength += length - 1;
|
||||
}
|
||||
|
||||
public int MapToOriginalIndex(int translatedIndex)
|
||||
{
|
||||
if (translatedIndex > translatedIndexs.Last())
|
||||
return translatedIndex - translaedLength - 1;
|
||||
|
||||
int lowerBound = 0;
|
||||
int upperBound = originalIndexs.Count - 1;
|
||||
|
||||
int count = 0;
|
||||
|
||||
// Corner case handle
|
||||
if (translatedIndex < translatedIndexs[0])
|
||||
return translatedIndex;
|
||||
if (translatedIndex > translatedIndexs.Last())
|
||||
{
|
||||
int indexDef = 0;
|
||||
for (int k = 0; k < originalIndexs.Count; k++)
|
||||
{
|
||||
indexDef += translatedIndexs[k * 2 + 1] - translatedIndexs[k * 2];
|
||||
}
|
||||
|
||||
return translatedIndex - indexDef - 1;
|
||||
}
|
||||
|
||||
// Binary Search with Range
|
||||
for (int i = originalIndexs.Count / 2;; count++)
|
||||
{
|
||||
if (translatedIndex < translatedIndexs[i * 2])
|
||||
{
|
||||
// move to lower middle
|
||||
upperBound = i;
|
||||
i = (i + lowerBound) / 2;
|
||||
}
|
||||
else if (translatedIndex > translatedIndexs[i * 2 + 1] - 1)
|
||||
{
|
||||
lowerBound = i;
|
||||
// move to upper middle
|
||||
// due to floor of integer division, move one up on corner case
|
||||
i = (i + upperBound + 1) / 2;
|
||||
}
|
||||
else
|
||||
return originalIndexs[i];
|
||||
|
||||
if (upperBound - lowerBound <= 1 &&
|
||||
translatedIndex > translatedIndexs[lowerBound * 2 + 1] &&
|
||||
translatedIndex < translatedIndexs[upperBound * 2])
|
||||
{
|
||||
int indexDef = 0;
|
||||
|
||||
for (int j = 0; j < upperBound; j++)
|
||||
{
|
||||
indexDef += translatedIndexs[j * 2 + 1] - translatedIndexs[j * 2];
|
||||
}
|
||||
|
||||
return translatedIndex - indexDef - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void endConstruct()
|
||||
{
|
||||
if (constructed)
|
||||
throw new InvalidOperationException("Mapping has already been constructed");
|
||||
constructed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IAlphabet
|
||||
{
|
||||
string Translate(string stringToTranslate);
|
||||
public (string translation, TranslationMapping map) Translate(string stringToTranslate);
|
||||
}
|
||||
|
||||
public class PinyinAlphabet : IAlphabet
|
||||
{
|
||||
private ConcurrentDictionary<string, string> _pinyinCache = new ConcurrentDictionary<string, string>();
|
||||
private ConcurrentDictionary<string, (string translation, TranslationMapping map)> _pinyinCache =
|
||||
new ConcurrentDictionary<string, (string translation, TranslationMapping map)>();
|
||||
|
||||
private Settings _settings;
|
||||
|
||||
public void Initialize([NotNull] Settings settings)
|
||||
|
|
@ -23,7 +119,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
public string Translate(string content)
|
||||
public (string translation, TranslationMapping map) Translate(string content)
|
||||
{
|
||||
if (_settings.ShouldUsePinyin)
|
||||
{
|
||||
|
|
@ -34,14 +130,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
var resultList = WordsHelper.GetPinyinList(content);
|
||||
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < resultList.Length; i++)
|
||||
{
|
||||
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
|
||||
resultBuilder.Append(resultList[i].First());
|
||||
}
|
||||
|
||||
resultBuilder.Append(' ');
|
||||
TranslationMapping map = new TranslationMapping();
|
||||
|
||||
bool pre = false;
|
||||
|
||||
|
|
@ -49,6 +138,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
{
|
||||
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
|
||||
{
|
||||
map.AddNewIndex(i, resultBuilder.Length, resultList[i].Length + 1);
|
||||
resultBuilder.Append(' ');
|
||||
resultBuilder.Append(resultList[i]);
|
||||
pre = true;
|
||||
|
|
@ -60,15 +150,21 @@ namespace Flow.Launcher.Infrastructure
|
|||
pre = false;
|
||||
resultBuilder.Append(' ');
|
||||
}
|
||||
|
||||
resultBuilder.Append(resultList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return _pinyinCache[content] = resultBuilder.ToString();
|
||||
map.endConstruct();
|
||||
|
||||
var key = resultBuilder.ToString();
|
||||
map.setKey(key);
|
||||
|
||||
return _pinyinCache[content] = (key, map);
|
||||
}
|
||||
else
|
||||
{
|
||||
return content;
|
||||
return (content, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -78,7 +174,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
}
|
||||
else
|
||||
{
|
||||
return content;
|
||||
return (content, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure
|
||||
|
|
@ -22,7 +23,22 @@ namespace Flow.Launcher.Infrastructure
|
|||
Log.Debug(info);
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This stopwatch will appear only in Debug mode
|
||||
/// </summary>
|
||||
public static async Task<long> DebugAsync(string message, Func<Task> action)
|
||||
{
|
||||
var stopWatch = new System.Diagnostics.Stopwatch();
|
||||
stopWatch.Start();
|
||||
await action();
|
||||
stopWatch.Stop();
|
||||
var milliseconds = stopWatch.ElapsedMilliseconds;
|
||||
string info = $"{message} <{milliseconds}ms>";
|
||||
Log.Debug(info);
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
public static long Normal(string message, Action action)
|
||||
{
|
||||
var stopWatch = new System.Diagnostics.Stopwatch();
|
||||
|
|
@ -34,6 +50,20 @@ namespace Flow.Launcher.Infrastructure
|
|||
Log.Info(info);
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
public static async Task<long> NormalAsync(string message, Func<Task> action)
|
||||
{
|
||||
var stopWatch = new System.Diagnostics.Stopwatch();
|
||||
stopWatch.Start();
|
||||
await action();
|
||||
stopWatch.Stop();
|
||||
var milliseconds = stopWatch.ElapsedMilliseconds;
|
||||
string info = $"{message} <{milliseconds}ms>";
|
||||
Log.Info(info);
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void StartCount(string name, Action action)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Storage
|
||||
|
|
@ -9,10 +9,10 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
/// <summary>
|
||||
/// Serialize object using json format.
|
||||
/// </summary>
|
||||
public class JsonStrorage<T>
|
||||
public class JsonStrorage<T> where T : new()
|
||||
{
|
||||
private readonly JsonSerializerSettings _serializerSettings;
|
||||
private T _data;
|
||||
private readonly JsonSerializerOptions _serializerSettings;
|
||||
protected T _data;
|
||||
// need a new directory name
|
||||
public const string DirectoryName = "Settings";
|
||||
public const string FileSuffix = ".json";
|
||||
|
|
@ -24,10 +24,9 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
{
|
||||
// use property initialization instead of DefaultValueAttribute
|
||||
// easier and flexible for default value of object
|
||||
_serializerSettings = new JsonSerializerSettings
|
||||
_serializerSettings = new JsonSerializerOptions
|
||||
{
|
||||
ObjectCreationHandling = ObjectCreationHandling.Replace,
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
IgnoreNullValues = false
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +55,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
{
|
||||
try
|
||||
{
|
||||
_data = JsonConvert.DeserializeObject<T>(searlized, _serializerSettings);
|
||||
_data = JsonSerializer.Deserialize<T>(searlized, _serializerSettings);
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
|
|
@ -77,7 +76,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
BackupOriginFile();
|
||||
}
|
||||
|
||||
_data = JsonConvert.DeserializeObject<T>("{}", _serializerSettings);
|
||||
_data = new T();
|
||||
Save();
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +93,8 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
|
||||
public void Save()
|
||||
{
|
||||
string serialized = JsonConvert.SerializeObject(_data, Formatting.Indented);
|
||||
string serialized = JsonSerializer.Serialize(_data, new JsonSerializerOptions() { WriteIndented = true });
|
||||
|
||||
File.WriteAllText(FilePath, serialized);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,5 +15,10 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
|
||||
FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}");
|
||||
}
|
||||
|
||||
public PluginJsonStorage(T data) : this()
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using static Flow.Launcher.Infrastructure.StringMatcher;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure
|
||||
{
|
||||
|
|
@ -32,7 +31,20 @@ namespace Flow.Launcher.Infrastructure
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current method:
|
||||
/// Current method has two parts, Acronym Match and Fuzzy Search:
|
||||
///
|
||||
/// Acronym Match:
|
||||
/// Charater listed below will be considered as acronym
|
||||
/// 1. Character on index 0
|
||||
/// 2. Character appears after a space
|
||||
/// 3. Character that is UpperCase
|
||||
/// 4. Character that is number
|
||||
///
|
||||
/// Acronym Match will succeed when all query characters match with acronyms in stringToCompare.
|
||||
/// If any of the characters in the query isn't matched with stringToCompare, Acronym Match will fail.
|
||||
/// Score will be calculated based the percentage of all query characters matched with total acronyms in stringToCompare.
|
||||
///
|
||||
/// Fuzzy Search:
|
||||
/// Character matching + substring matching;
|
||||
/// 1. Query search string is split into substrings, separator is whitespace.
|
||||
/// 2. Check each query substring's characters against full compare string,
|
||||
|
|
@ -44,20 +56,21 @@ namespace Flow.Launcher.Infrastructure
|
|||
/// </summary>
|
||||
public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption opt)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) return new MatchResult (false, UserSettingSearchPrecision);
|
||||
|
||||
query = query.Trim();
|
||||
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query))
|
||||
return new MatchResult(false, UserSettingSearchPrecision);
|
||||
|
||||
if (_alphabet != null)
|
||||
{
|
||||
query = _alphabet.Translate(query);
|
||||
stringToCompare = _alphabet.Translate(stringToCompare);
|
||||
}
|
||||
query = query.Trim();
|
||||
TranslationMapping translationMapping;
|
||||
(stringToCompare, translationMapping) = _alphabet?.Translate(stringToCompare) ?? (stringToCompare, null);
|
||||
|
||||
var currentAcronymQueryIndex = 0;
|
||||
var acronymMatchData = new List<int>();
|
||||
int acronymsTotalCount = 0;
|
||||
int acronymsMatched = 0;
|
||||
|
||||
var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare;
|
||||
|
||||
var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query;
|
||||
|
||||
|
||||
var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
int currentQuerySubstringIndex = 0;
|
||||
var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
|
||||
|
|
@ -75,17 +88,44 @@ namespace Flow.Launcher.Infrastructure
|
|||
|
||||
for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++)
|
||||
{
|
||||
// If acronyms matching successfully finished, this gets the remaining not matched acronyms for score calculation
|
||||
if (currentAcronymQueryIndex >= query.Length && acronymsMatched == query.Length)
|
||||
{
|
||||
if (IsAcronymCount(stringToCompare, compareStringIndex))
|
||||
acronymsTotalCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentAcronymQueryIndex >= query.Length ||
|
||||
currentAcronymQueryIndex >= query.Length && allQuerySubstringsMatched)
|
||||
break;
|
||||
|
||||
// To maintain a list of indices which correspond to spaces in the string to compare
|
||||
// To populate the list only for the first query substring
|
||||
if (fullStringToCompareWithoutCase[compareStringIndex].Equals(' ') && currentQuerySubstringIndex == 0)
|
||||
{
|
||||
if (fullStringToCompareWithoutCase[compareStringIndex] == ' ' && currentQuerySubstringIndex == 0)
|
||||
spaceIndices.Add(compareStringIndex);
|
||||
|
||||
// Acronym Match
|
||||
if (IsAcronym(stringToCompare, compareStringIndex))
|
||||
{
|
||||
if (fullStringToCompareWithoutCase[compareStringIndex] ==
|
||||
queryWithoutCase[currentAcronymQueryIndex])
|
||||
{
|
||||
acronymMatchData.Add(compareStringIndex);
|
||||
acronymsMatched++;
|
||||
|
||||
currentAcronymQueryIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex])
|
||||
if (IsAcronymCount(stringToCompare, compareStringIndex))
|
||||
acronymsTotalCount++;
|
||||
|
||||
if (allQuerySubstringsMatched || fullStringToCompareWithoutCase[compareStringIndex] !=
|
||||
currentQuerySubstring[currentQuerySubstringCharacterIndex])
|
||||
{
|
||||
matchFoundInPreviousLoop = false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -107,14 +147,16 @@ namespace Flow.Launcher.Infrastructure
|
|||
// in order to do so we need to verify all previous chars are part of the pattern
|
||||
var startIndexToVerify = compareStringIndex - currentQuerySubstringCharacterIndex;
|
||||
|
||||
if (AllPreviousCharsMatched(startIndexToVerify, currentQuerySubstringCharacterIndex, fullStringToCompareWithoutCase, currentQuerySubstring))
|
||||
if (AllPreviousCharsMatched(startIndexToVerify, currentQuerySubstringCharacterIndex,
|
||||
fullStringToCompareWithoutCase, currentQuerySubstring))
|
||||
{
|
||||
matchFoundInPreviousLoop = true;
|
||||
|
||||
// if it's the beginning character of the first query substring that is matched then we need to update start index
|
||||
firstMatchIndex = currentQuerySubstringIndex == 0 ? startIndexToVerify : firstMatchIndex;
|
||||
|
||||
indexList = GetUpdatedIndexList(startIndexToVerify, currentQuerySubstringCharacterIndex, firstMatchIndexInWord, indexList);
|
||||
indexList = GetUpdatedIndexList(startIndexToVerify, currentQuerySubstringCharacterIndex,
|
||||
firstMatchIndexInWord, indexList);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,49 +169,96 @@ namespace Flow.Launcher.Infrastructure
|
|||
if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length)
|
||||
{
|
||||
// if any of the substrings was not matched then consider as all are not matched
|
||||
allSubstringsContainedInCompareString = matchFoundInPreviousLoop && allSubstringsContainedInCompareString;
|
||||
allSubstringsContainedInCompareString =
|
||||
matchFoundInPreviousLoop && allSubstringsContainedInCompareString;
|
||||
|
||||
currentQuerySubstringIndex++;
|
||||
|
||||
allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length);
|
||||
allQuerySubstringsMatched =
|
||||
AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length);
|
||||
|
||||
if (allQuerySubstringsMatched)
|
||||
break;
|
||||
continue;
|
||||
|
||||
// otherwise move to the next query substring
|
||||
currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
|
||||
currentQuerySubstringCharacterIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// return acronym match if all query char matched
|
||||
if (acronymsMatched > 0 && acronymsMatched == query.Length)
|
||||
{
|
||||
int acronymScore = acronymsMatched * 100 / acronymsTotalCount;
|
||||
|
||||
if (acronymScore >= (int)UserSettingSearchPrecision)
|
||||
{
|
||||
acronymMatchData = acronymMatchData.Select(x => translationMapping?.MapToOriginalIndex(x) ?? x).Distinct().ToList();
|
||||
return new MatchResult(true, UserSettingSearchPrecision, acronymMatchData, acronymScore);
|
||||
}
|
||||
}
|
||||
|
||||
// proceed to calculate score if every char or substring without whitespaces matched
|
||||
if (allQuerySubstringsMatched)
|
||||
{
|
||||
var nearestSpaceIndex = CalculateClosestSpaceIndex(spaceIndices, firstMatchIndex);
|
||||
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
|
||||
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1,
|
||||
lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
|
||||
|
||||
return new MatchResult(true, UserSettingSearchPrecision, indexList, score);
|
||||
var resultList = indexList.Select(x => translationMapping?.MapToOriginalIndex(x) ?? x).Distinct().ToList();
|
||||
return new MatchResult(true, UserSettingSearchPrecision, resultList, score);
|
||||
}
|
||||
|
||||
return new MatchResult(false, UserSettingSearchPrecision);
|
||||
}
|
||||
|
||||
private bool IsAcronym(string stringToCompare, int compareStringIndex)
|
||||
{
|
||||
if (IsAcronymChar(stringToCompare, compareStringIndex) || IsAcronymNumber(stringToCompare, compareStringIndex))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// When counting acronyms, treat a set of numbers as one acronym ie. Visual 2019 as 2 acronyms instead of 5
|
||||
private bool IsAcronymCount(string stringToCompare, int compareStringIndex)
|
||||
{
|
||||
if (IsAcronymChar(stringToCompare, compareStringIndex))
|
||||
return true;
|
||||
|
||||
if (IsAcronymNumber(stringToCompare, compareStringIndex))
|
||||
return compareStringIndex == 0 || char.IsWhiteSpace(stringToCompare[compareStringIndex - 1]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsAcronymChar(string stringToCompare, int compareStringIndex)
|
||||
=> char.IsUpper(stringToCompare[compareStringIndex]) ||
|
||||
compareStringIndex == 0 || // 0 index means char is the start of the compare string, which is an acronym
|
||||
char.IsWhiteSpace(stringToCompare[compareStringIndex - 1]);
|
||||
|
||||
private bool IsAcronymNumber(string stringToCompare, int compareStringIndex)
|
||||
=> stringToCompare[compareStringIndex] >= 0 && stringToCompare[compareStringIndex] <= 9;
|
||||
|
||||
// To get the index of the closest space which preceeds the first matching index
|
||||
private int CalculateClosestSpaceIndex(List<int> spaceIndices, int firstMatchIndex)
|
||||
{
|
||||
if (spaceIndices.Count == 0)
|
||||
var closestSpaceIndex = -1;
|
||||
|
||||
// spaceIndices should be ordered asc
|
||||
foreach (var index in spaceIndices)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
int? ind = spaceIndices.OrderBy(item => (firstMatchIndex - item)).Where(item => firstMatchIndex > item).FirstOrDefault();
|
||||
int closestSpaceIndex = ind ?? -1;
|
||||
return closestSpaceIndex;
|
||||
if (index < firstMatchIndex)
|
||||
closestSpaceIndex = index;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return closestSpaceIndex;
|
||||
}
|
||||
|
||||
private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex,
|
||||
string fullStringToCompareWithoutCase, string currentQuerySubstring)
|
||||
string fullStringToCompareWithoutCase, string currentQuerySubstring)
|
||||
{
|
||||
var allMatch = true;
|
||||
for (int indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++)
|
||||
|
|
@ -183,8 +272,9 @@ namespace Flow.Launcher.Infrastructure
|
|||
|
||||
return allMatch;
|
||||
}
|
||||
|
||||
private static List<int> GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, int firstMatchIndexInWord, List<int> indexList)
|
||||
|
||||
private static List<int> GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex,
|
||||
int firstMatchIndexInWord, List<int> indexList)
|
||||
{
|
||||
var updatedList = new List<int>();
|
||||
|
||||
|
|
@ -202,10 +292,12 @@ namespace Flow.Launcher.Infrastructure
|
|||
|
||||
private static bool AllQuerySubstringsMatched(int currentQuerySubstringIndex, int querySubstringsLength)
|
||||
{
|
||||
// Acronym won't utilize the substring to match
|
||||
return currentQuerySubstringIndex >= querySubstringsLength;
|
||||
}
|
||||
|
||||
private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen, bool allSubstringsContainedInCompareString)
|
||||
private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen,
|
||||
bool allSubstringsContainedInCompareString)
|
||||
{
|
||||
// A match found near the beginning of a string is scored more than a match found near the end
|
||||
// A match is scored more if the characters in the patterns are closer to each other,
|
||||
|
|
@ -239,74 +331,6 @@ namespace Flow.Launcher.Infrastructure
|
|||
|
||||
return score;
|
||||
}
|
||||
|
||||
public enum SearchPrecisionScore
|
||||
{
|
||||
Regular = 50,
|
||||
Low = 20,
|
||||
None = 0
|
||||
}
|
||||
}
|
||||
|
||||
public class MatchResult
|
||||
{
|
||||
public MatchResult(bool success, SearchPrecisionScore searchPrecision)
|
||||
{
|
||||
Success = success;
|
||||
SearchPrecision = searchPrecision;
|
||||
}
|
||||
|
||||
public MatchResult(bool success, SearchPrecisionScore searchPrecision, List<int> matchData, int rawScore)
|
||||
{
|
||||
Success = success;
|
||||
SearchPrecision = searchPrecision;
|
||||
MatchData = matchData;
|
||||
RawScore = rawScore;
|
||||
}
|
||||
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The final score of the match result with search precision filters applied.
|
||||
/// </summary>
|
||||
public int Score { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The raw calculated search score without any search precision filtering applied.
|
||||
/// </summary>
|
||||
private int _rawScore;
|
||||
|
||||
public int RawScore
|
||||
{
|
||||
get { return _rawScore; }
|
||||
set
|
||||
{
|
||||
_rawScore = value;
|
||||
Score = ScoreAfterSearchPrecisionFilter(_rawScore);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matched data to highlight.
|
||||
/// </summary>
|
||||
public List<int> MatchData { get; set; }
|
||||
|
||||
public SearchPrecisionScore SearchPrecision { get; set; }
|
||||
|
||||
public bool IsSearchPrecisionScoreMet()
|
||||
{
|
||||
return IsSearchPrecisionScoreMet(_rawScore);
|
||||
}
|
||||
|
||||
private bool IsSearchPrecisionScoreMet(int rawScore)
|
||||
{
|
||||
return rawScore >= (int)SearchPrecision;
|
||||
}
|
||||
|
||||
private int ScoreAfterSearchPrecisionFilter(int rawScore)
|
||||
{
|
||||
return IsSearchPrecisionScoreMet(rawScore) ? rawScore : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public class MatchOption
|
||||
|
|
|
|||
|
|
@ -1,11 +1,80 @@
|
|||
namespace Flow.Launcher.Infrastructure.UserSettings
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.UserSettings
|
||||
{
|
||||
public enum ProxyProperty
|
||||
{
|
||||
Enabled,
|
||||
Server,
|
||||
Port,
|
||||
UserName,
|
||||
Password
|
||||
}
|
||||
|
||||
public class HttpProxy
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
public string Server { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string Password { get; set; }
|
||||
private bool _enabled = false;
|
||||
private string _server;
|
||||
private int _port;
|
||||
private string _userName;
|
||||
private string _password;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
_enabled = value;
|
||||
OnPropertyChanged(ProxyProperty.Enabled);
|
||||
}
|
||||
}
|
||||
|
||||
public string Server
|
||||
{
|
||||
get => _server;
|
||||
set
|
||||
{
|
||||
_server = value;
|
||||
OnPropertyChanged(ProxyProperty.Server);
|
||||
}
|
||||
}
|
||||
|
||||
public int Port
|
||||
{
|
||||
get => _port;
|
||||
set
|
||||
{
|
||||
_port = value;
|
||||
OnPropertyChanged(ProxyProperty.Port);
|
||||
}
|
||||
}
|
||||
|
||||
public string UserName
|
||||
{
|
||||
get => _userName;
|
||||
set
|
||||
{
|
||||
_userName = value;
|
||||
OnPropertyChanged(ProxyProperty.UserName);
|
||||
}
|
||||
}
|
||||
|
||||
public string Password
|
||||
{
|
||||
get => _password;
|
||||
set
|
||||
{
|
||||
_password = value;
|
||||
OnPropertyChanged(ProxyProperty.Password);
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void ProxyPropertyChangedHandler(ProxyProperty property);
|
||||
public event ProxyPropertyChangedHandler PropertyChanged;
|
||||
|
||||
private void OnPropertyChanged(ProxyProperty property)
|
||||
{
|
||||
PropertyChanged?.Invoke(property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
metadata.ActionKeyword = settings.ActionKeywords[0];
|
||||
}
|
||||
metadata.Disabled = settings.Disabled;
|
||||
metadata.Priority = settings.Priority;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -40,7 +41,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
Name = metadata.Name,
|
||||
Version = metadata.Version,
|
||||
ActionKeywords = metadata.ActionKeywords,
|
||||
Disabled = metadata.Disabled
|
||||
Disabled = metadata.Disabled,
|
||||
Priority = metadata.Priority
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -52,6 +54,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
public string Name { get; set; }
|
||||
public string Version { get; set; }
|
||||
public List<string> ActionKeywords { get; set; } // a reference of the action keywords from plugin manager
|
||||
public int Priority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used only to save the state of the plugin in settings
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Drawing;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System.Text.Json.Serialization;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.UserSettings
|
||||
{
|
||||
|
|
@ -16,7 +16,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
public bool ShowOpenResultHotkey { get; set; } = true;
|
||||
public string Language
|
||||
{
|
||||
get => language; set {
|
||||
get => language; set
|
||||
{
|
||||
language = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
|
|
@ -38,7 +39,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
/// </summary>
|
||||
public bool ShouldUsePinyin { get; set; } = false;
|
||||
|
||||
internal StringMatcher.SearchPrecisionScore QuerySearchPrecision { get; private set; } = StringMatcher.SearchPrecisionScore.Regular;
|
||||
internal SearchPrecisionScore QuerySearchPrecision { get; private set; } = SearchPrecisionScore.Regular;
|
||||
|
||||
[JsonIgnore]
|
||||
public string QuerySearchPrecisionString
|
||||
|
|
@ -48,8 +49,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
{
|
||||
try
|
||||
{
|
||||
var precisionScore = (StringMatcher.SearchPrecisionScore)Enum
|
||||
.Parse(typeof(StringMatcher.SearchPrecisionScore), value);
|
||||
var precisionScore = (SearchPrecisionScore)Enum
|
||||
.Parse(typeof(SearchPrecisionScore), value);
|
||||
|
||||
QuerySearchPrecision = precisionScore;
|
||||
StringMatcher.Instance.UserSettingSearchPrecision = precisionScore;
|
||||
|
|
@ -58,8 +59,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
{
|
||||
Logger.Log.Exception(nameof(Settings), "Failed to load QuerySearchPrecisionString value from Settings file", e);
|
||||
|
||||
QuerySearchPrecision = StringMatcher.SearchPrecisionScore.Regular;
|
||||
StringMatcher.Instance.UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular;
|
||||
QuerySearchPrecision = SearchPrecisionScore.Regular;
|
||||
StringMatcher.Instance.UserSettingSearchPrecision = SearchPrecisionScore.Regular;
|
||||
|
||||
throw;
|
||||
}
|
||||
|
|
@ -73,9 +74,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
public int MaxResultsToShow { get; set; } = 5;
|
||||
public int ActivateTimes { get; set; }
|
||||
|
||||
// Order defaults to 0 or -1, so 1 will let this property appear last
|
||||
[JsonProperty(Order = 1)]
|
||||
public PluginsSettings PluginSettings { get; set; } = new PluginsSettings();
|
||||
|
||||
public ObservableCollection<CustomPluginHotkey> CustomPluginHotkeys { get; set; } = new ObservableCollection<CustomPluginHotkey>();
|
||||
|
||||
public bool DontPromptUpdateMsg { get; set; }
|
||||
|
|
@ -100,8 +99,12 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
|
||||
public HttpProxy Proxy { get; set; } = new HttpProxy();
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public LastQueryMode LastQueryMode { get; set; } = LastQueryMode.Selected;
|
||||
|
||||
|
||||
// This needs to be loaded last by staying at the bottom
|
||||
public PluginsSettings PluginSettings { get; set; } = new PluginsSettings();
|
||||
}
|
||||
|
||||
public enum LastQueryMode
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
|
@ -14,10 +14,10 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.3.0</Version>
|
||||
<PackageVersion>1.3.0</PackageVersion>
|
||||
<AssemblyVersion>1.3.0</AssemblyVersion>
|
||||
<FileVersion>1.3.0</FileVersion>
|
||||
<Version>1.4.0</Version>
|
||||
<PackageVersion>1.4.0</PackageVersion>
|
||||
<AssemblyVersion>1.4.0</AssemblyVersion>
|
||||
<FileVersion>1.4.0</FileVersion>
|
||||
<PackageId>Flow.Launcher.Plugin</PackageId>
|
||||
<Authors>Flow-Launcher</Authors>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
|
@ -62,7 +62,6 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
|||
31
Flow.Launcher.Plugin/IAsyncPlugin.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Asynchronous Plugin Model for Flow Launcher
|
||||
/// </summary>
|
||||
public interface IAsyncPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Asynchronous Querying
|
||||
/// </summary>
|
||||
/// <para>
|
||||
/// If the Querying or Init method requires high IO transmission
|
||||
/// or performing CPU intense jobs (performing better with cancellation), please use this IAsyncPlugin interface
|
||||
/// </para>
|
||||
/// <param name="query">Query to search</param>
|
||||
/// <param name="token">Cancel when querying job is obsolete</param>
|
||||
/// <returns></returns>
|
||||
Task<List<Result>> QueryAsync(Query query, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize plugin asynchrously (will still wait finish to continue)
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
Task InitAsync(PluginInitContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,30 @@
|
|||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Synchronous Plugin Model for Flow Launcher
|
||||
/// <para>
|
||||
/// If the Querying or Init method requires high IO transmission
|
||||
/// or performaing CPU intense jobs (performing better with cancellation), please try the IAsyncPlugin interface
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Querying when user's search changes
|
||||
/// <para>
|
||||
/// This method will be called within a Task.Run,
|
||||
/// so please avoid synchrously wait for long.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="query">Query to search</param>
|
||||
/// <returns></returns>
|
||||
List<Result> Query(Query query);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize plugin
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
void Init(PluginInitContext context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
using System;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using JetBrains.Annotations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
|
|
@ -34,7 +40,7 @@ namespace Flow.Launcher.Plugin
|
|||
/// Plugin's in memory data with new content
|
||||
/// added by user.
|
||||
/// </summary>
|
||||
void ReloadAllPluginData();
|
||||
Task ReloadAllPluginData();
|
||||
|
||||
/// <summary>
|
||||
/// Check for new Flow Launcher update
|
||||
|
|
@ -82,5 +88,87 @@ namespace Flow.Launcher.Plugin
|
|||
/// if you want to hook something like Ctrl+R, you should use this event
|
||||
/// </summary>
|
||||
event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Fuzzy Search the string with the given query. This is the core search mechanism Flow uses
|
||||
/// </summary>
|
||||
/// <param name="query">Query string</param>
|
||||
/// <param name="stringToCompare">The string that will be compared against the query</param>
|
||||
/// <returns>Match results</returns>
|
||||
MatchResult FuzzySearch(string query, string stringToCompare);
|
||||
|
||||
/// <summary>
|
||||
/// Http download the spefic url and return as string
|
||||
/// </summary>
|
||||
/// <param name="url">URL to call Http Get</param>
|
||||
/// <param name="token">Cancellation Token</param>
|
||||
/// <returns>Task to get string result</returns>
|
||||
Task<string> HttpGetStringAsync(string url, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Http download the spefic url and return as stream
|
||||
/// </summary>
|
||||
/// <param name="url">URL to call Http Get</param>
|
||||
/// <param name="token">Cancellation Token</param>
|
||||
/// <returns>Task to get stream result</returns>
|
||||
Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Download the specific url to a cretain file path
|
||||
/// </summary>
|
||||
/// <param name="url">URL to download file</param>
|
||||
/// <param name="token">place to store file</param>
|
||||
/// <returns>Task showing the progress</returns>
|
||||
Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Add ActionKeyword for specific plugin
|
||||
/// </summary>
|
||||
/// <param name="pluginId">ID for plugin that needs to add action keyword</param>
|
||||
/// <param name="newActionKeyword">The actionkeyword that is supposed to be added</param>
|
||||
void AddActionKeyword(string pluginId, string newActionKeyword);
|
||||
|
||||
/// <summary>
|
||||
/// Remove ActionKeyword for specific plugin
|
||||
/// </summary>
|
||||
/// <param name="pluginId">ID for plugin that needs to remove action keyword</param>
|
||||
/// <param name="newActionKeyword">The actionkeyword that is supposed to be removed</param>
|
||||
void RemoveActionKeyword(string pluginId, string oldActionKeyword);
|
||||
|
||||
/// <summary>
|
||||
/// Log debug message
|
||||
/// Message will only be logged in Debug mode
|
||||
/// </summary>
|
||||
void LogDebug(string className, string message, [CallerMemberName] string methodName = "");
|
||||
|
||||
/// <summary>
|
||||
/// Log info message
|
||||
/// </summary>
|
||||
void LogInfo(string className, string message, [CallerMemberName] string methodName = "");
|
||||
|
||||
/// <summary>
|
||||
/// Log warning message
|
||||
/// </summary>
|
||||
void LogWarn(string className, string message, [CallerMemberName] string methodName = "");
|
||||
|
||||
/// <summary>
|
||||
/// Log an Exception. Will throw if in debug mode so developer will be aware,
|
||||
/// otherwise logs the eror message. This is the primary logging method used for Flow
|
||||
/// </summary>
|
||||
void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "");
|
||||
|
||||
/// <summary>
|
||||
/// Load JsonStorage for current plugin. This is the method used to load settings from json in Flow
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type for deserialization</typeparam>
|
||||
/// <returns></returns>
|
||||
T LoadJsonStorage<T>() where T : new();
|
||||
|
||||
/// <summary>
|
||||
/// Save JsonStorage for current plugin. This is the method used to save settings to json in Flow
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type for Serialization</typeparam>
|
||||
/// <returns></returns>
|
||||
void SaveJsonStorage<T>(T setting) where T : new();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface is to indicate and allow plugins to asyncronously reload their
|
||||
/// in memory data cache or other mediums when user makes a new change
|
||||
/// that is not immediately captured. For example, for BrowserBookmark and Program
|
||||
/// plugin does not automatically detect when a user added a new bookmark or program,
|
||||
/// so this interface's function is exposed to allow user manually do the reloading after
|
||||
/// those new additions.
|
||||
///
|
||||
/// The command that allows user to manual reload is exposed via Plugin.Sys, and
|
||||
/// it will call the plugins that have implemented this interface.
|
||||
/// </summary>
|
||||
public interface IAsyncReloadable
|
||||
{
|
||||
Task ReloadDataAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface is to indicate and allow plugins to reload their
|
||||
/// This interface is to indicate and allow plugins to synchronously reload their
|
||||
/// in memory data cache or other mediums when user makes a new change
|
||||
/// that is not immediately captured. For example, for BrowserBookmark and Program
|
||||
/// plugin does not automatically detect when a user added a new bookmark or program,
|
||||
|
|
@ -10,6 +10,10 @@
|
|||
///
|
||||
/// The command that allows user to manual reload is exposed via Plugin.Sys, and
|
||||
/// it will call the plugins that have implemented this interface.
|
||||
///
|
||||
/// <para>
|
||||
/// If requiring reloading data asynchronously, please use the IAsyncReloadable interface
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface IReloadable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Flow.Launcher.Infrastructure.Storage
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Save plugin settings/cache,
|
||||
|
|
@ -4,6 +4,16 @@ namespace Flow.Launcher.Plugin
|
|||
{
|
||||
public class PluginInitContext
|
||||
{
|
||||
public PluginInitContext()
|
||||
{
|
||||
}
|
||||
|
||||
public PluginInitContext(PluginMetadata currentPluginMetadata, IPublicAPI api)
|
||||
{
|
||||
CurrentPluginMetadata = currentPluginMetadata;
|
||||
API = api;
|
||||
}
|
||||
|
||||
public PluginMetadata CurrentPluginMetadata { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptOut)]
|
||||
public class PluginMetadata : BaseModel
|
||||
{
|
||||
private string _pluginDirectory;
|
||||
|
|
@ -37,12 +36,15 @@ namespace Flow.Launcher.Plugin
|
|||
public List<string> ActionKeywords { get; set; }
|
||||
|
||||
public string IcoPath { get; set;}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public int Priority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Init time include both plugin load time and init time
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
{
|
||||
public class PluginPair
|
||||
{
|
||||
public IPlugin Plugin { get; internal set; }
|
||||
public object Plugin { get; internal set; }
|
||||
public PluginMetadata Metadata { get; internal set; }
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
|
||||
public static void OpenPath(string fileOrFolderPath)
|
||||
{
|
||||
var psi = new ProcessStartInfo { FileName = FileExplorerProgramName, UseShellExecute = true, Arguments = fileOrFolderPath };
|
||||
var psi = new ProcessStartInfo { FileName = FileExplorerProgramName, UseShellExecute = true, Arguments = '"' + fileOrFolderPath + '"' };
|
||||
try
|
||||
{
|
||||
if (LocationExists(fileOrFolderPath) || FileExists(fileOrFolderPath))
|
||||
|
|
@ -146,31 +146,23 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
/// This checks whether a given string is a directory path or network location string.
|
||||
/// It does not check if location actually exists.
|
||||
///</summary>
|
||||
public static bool IsLocationPathString(string querySearchString)
|
||||
public static bool IsLocationPathString(this string querySearchString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(querySearchString))
|
||||
if (string.IsNullOrEmpty(querySearchString) || querySearchString.Length < 3)
|
||||
return false;
|
||||
|
||||
// // shared folder location, and not \\\location\
|
||||
if (querySearchString.Length >= 3
|
||||
&& querySearchString.StartsWith(@"\\")
|
||||
&& char.IsLetter(querySearchString[2]))
|
||||
if (querySearchString.StartsWith(@"\\")
|
||||
&& querySearchString[2] != '\\')
|
||||
return true;
|
||||
|
||||
// c:\
|
||||
if (querySearchString.Length == 3
|
||||
&& char.IsLetter(querySearchString[0])
|
||||
if (char.IsLetter(querySearchString[0])
|
||||
&& querySearchString[1] == ':'
|
||||
&& querySearchString[2] == '\\')
|
||||
return true;
|
||||
|
||||
// c:\\
|
||||
if (querySearchString.Length >= 4
|
||||
&& char.IsLetter(querySearchString[0])
|
||||
&& querySearchString[1] == ':'
|
||||
&& querySearchString[2] == '\\'
|
||||
&& char.IsLetter(querySearchString[3]))
|
||||
return true;
|
||||
{
|
||||
return querySearchString.Length == 3 || querySearchString[3] != '\\';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
72
Flow.Launcher.Plugin/SharedModels/MatchResult.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Flow.Launcher.Plugin.SharedModels
|
||||
{
|
||||
public class MatchResult
|
||||
{
|
||||
public MatchResult(bool success, SearchPrecisionScore searchPrecision)
|
||||
{
|
||||
Success = success;
|
||||
SearchPrecision = searchPrecision;
|
||||
}
|
||||
|
||||
public MatchResult(bool success, SearchPrecisionScore searchPrecision, List<int> matchData, int rawScore)
|
||||
{
|
||||
Success = success;
|
||||
SearchPrecision = searchPrecision;
|
||||
MatchData = matchData;
|
||||
RawScore = rawScore;
|
||||
}
|
||||
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The final score of the match result with search precision filters applied.
|
||||
/// </summary>
|
||||
public int Score { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The raw calculated search score without any search precision filtering applied.
|
||||
/// </summary>
|
||||
private int _rawScore;
|
||||
|
||||
public int RawScore
|
||||
{
|
||||
get { return _rawScore; }
|
||||
set
|
||||
{
|
||||
_rawScore = value;
|
||||
Score = ScoreAfterSearchPrecisionFilter(_rawScore);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matched data to highlight.
|
||||
/// </summary>
|
||||
public List<int> MatchData { get; set; }
|
||||
|
||||
public SearchPrecisionScore SearchPrecision { get; set; }
|
||||
|
||||
public bool IsSearchPrecisionScoreMet()
|
||||
{
|
||||
return IsSearchPrecisionScoreMet(_rawScore);
|
||||
}
|
||||
|
||||
private bool IsSearchPrecisionScoreMet(int rawScore)
|
||||
{
|
||||
return rawScore >= (int)SearchPrecision;
|
||||
}
|
||||
|
||||
private int ScoreAfterSearchPrecisionFilter(int rawScore)
|
||||
{
|
||||
return IsSearchPrecisionScoreMet(rawScore) ? rawScore : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public enum SearchPrecisionScore
|
||||
{
|
||||
Regular = 50,
|
||||
Low = 20,
|
||||
None = 0
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||
using NUnit.Framework;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
|
||||
namespace Flow.Launcher.Test
|
||||
{
|
||||
|
|
@ -37,8 +38,8 @@ namespace Flow.Launcher.Test
|
|||
{
|
||||
var listToReturn = new List<int>();
|
||||
|
||||
Enum.GetValues(typeof(StringMatcher.SearchPrecisionScore))
|
||||
.Cast<StringMatcher.SearchPrecisionScore>()
|
||||
Enum.GetValues(typeof(SearchPrecisionScore))
|
||||
.Cast<SearchPrecisionScore>()
|
||||
.ToList()
|
||||
.ForEach(x => listToReturn.Add((int)x));
|
||||
|
||||
|
|
@ -92,7 +93,8 @@ namespace Flow.Launcher.Test
|
|||
[TestCase("cand")]
|
||||
[TestCase("cpywa")]
|
||||
[TestCase("ccs")]
|
||||
public void GivenQueryString_WhenAppliedPrecisionFiltering_ThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm)
|
||||
public void GivenQueryString_WhenAppliedPrecisionFiltering_ThenShouldReturnGreaterThanPrecisionScoreResults(
|
||||
string searchTerm)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
var matcher = new StringMatcher();
|
||||
|
|
@ -108,9 +110,9 @@ namespace Flow.Launcher.Test
|
|||
foreach (var precisionScore in GetPrecisionScores())
|
||||
{
|
||||
var filteredResult = results.Where(result => result.Score >= precisionScore)
|
||||
.Select(result => result)
|
||||
.OrderByDescending(x => x.Score)
|
||||
.ToList();
|
||||
.Select(result => result)
|
||||
.OrderByDescending(x => x.Score)
|
||||
.ToList();
|
||||
|
||||
Debug.WriteLine("");
|
||||
Debug.WriteLine("###############################################");
|
||||
|
|
@ -119,6 +121,7 @@ namespace Flow.Launcher.Test
|
|||
{
|
||||
Debug.WriteLine("SCORE: " + item.Score.ToString() + ", FoundString: " + item.Title);
|
||||
}
|
||||
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine("");
|
||||
|
||||
|
|
@ -128,37 +131,47 @@ namespace Flow.Launcher.Test
|
|||
|
||||
[TestCase(Chrome, Chrome, 157)]
|
||||
[TestCase(Chrome, LastIsChrome, 147)]
|
||||
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 25)]
|
||||
[TestCase("chro", HelpCureHopeRaiseOnMindEntityChrome, 50)]
|
||||
[TestCase("chr", HelpCureHopeRaiseOnMindEntityChrome, 30)]
|
||||
[TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 21)]
|
||||
[TestCase(Chrome, CandyCrushSagaFromKing, 0)]
|
||||
[TestCase("sql", MicrosoftSqlServerManagementStudio, 110)]
|
||||
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, 121)]//double spacing intended
|
||||
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, 121)] //double spacing intended
|
||||
public void WhenGivenQueryString_ThenShouldReturn_TheDesiredScoring(
|
||||
string queryString, string compareString, int expectedScore)
|
||||
{
|
||||
// When, Given
|
||||
var matcher = new StringMatcher();
|
||||
var matcher = new StringMatcher {UserSettingSearchPrecision = SearchPrecisionScore.Regular};
|
||||
var rawScore = matcher.FuzzyMatch(queryString, compareString).RawScore;
|
||||
|
||||
// Should
|
||||
Assert.AreEqual(expectedScore, rawScore,
|
||||
Assert.AreEqual(expectedScore, rawScore,
|
||||
$"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}");
|
||||
}
|
||||
|
||||
[TestCase("goo", "Google Chrome", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("chr", "Google Chrome", StringMatcher.SearchPrecisionScore.Low, true)]
|
||||
[TestCase("chr", "Chrome", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("chr", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("chr", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Low, true)]
|
||||
[TestCase("chr", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("chr", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.None, true)]
|
||||
[TestCase("ccs", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Low, true)]
|
||||
[TestCase("cand", "Candy Crush Saga from King",StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("cand", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("goo", "Google Chrome", SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("chr", "Google Chrome", SearchPrecisionScore.Low, true)]
|
||||
[TestCase("chr", "Chrome", SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("chr", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("chr", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Low, true)]
|
||||
[TestCase("chr", "Candy Crush Saga from King", SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("chr", "Candy Crush Saga from King", SearchPrecisionScore.None, true)]
|
||||
[TestCase("ccs", "Candy Crush Saga from King", SearchPrecisionScore.Low, true)]
|
||||
[TestCase("cand", "Candy Crush Saga from King", SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("cand", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("vsc", VisualStudioCode, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("vs", VisualStudioCode, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("vc", VisualStudioCode, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("vts", VisualStudioCode, SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("vcs", VisualStudioCode, SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("wt", "Windows Terminal From Microsoft Store", SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("vsp", "Visual Studio 2019 Preview", SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("vsp", "2019 Visual Studio Preview", SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("2019p", "Visual Studio 2019 Preview", SearchPrecisionScore.Regular, true)]
|
||||
public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual(
|
||||
string queryString,
|
||||
string compareString,
|
||||
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
|
||||
SearchPrecisionScore expectedPrecisionScore,
|
||||
bool expectedPrecisionResult)
|
||||
{
|
||||
// When
|
||||
|
|
@ -170,48 +183,50 @@ namespace Flow.Launcher.Test
|
|||
Debug.WriteLine("");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
|
||||
Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})");
|
||||
Debug.WriteLine(
|
||||
$"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int) expectedPrecisionScore})");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine("");
|
||||
|
||||
// Should
|
||||
Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
|
||||
$"Query:{queryString}{Environment.NewLine} " +
|
||||
$"Compare:{compareString}{Environment.NewLine}" +
|
||||
$"Query: {queryString}{Environment.NewLine} " +
|
||||
$"Compare: {compareString}{Environment.NewLine}" +
|
||||
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
|
||||
$"Precision Score: {(int)expectedPrecisionScore}");
|
||||
}
|
||||
|
||||
[TestCase("exce", "OverLeaf-Latex: An online LaTeX editor", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("term", "Windows Terminal (Preview)", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql s managa", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("sql' s manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("sql s manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql serv", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("servez", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("sql servz", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("sql serv man", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql studio", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("mic", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("chr", "Shutdown", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("mssms", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("a test", "This is a test", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("test", "This is a test", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("cod", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("code", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("codes", "Visual Studio Codes", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("exce", "OverLeaf-Latex: An online LaTeX editor", SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("term", "Windows Terminal (Preview)", SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql s managa", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("sql' s manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("sql s manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql serv", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("servez", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("sql servz", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("sql serv man", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql studio", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("mic", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("mssms", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("msms", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("chr", "Shutdown", SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).", SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("a test", "This is a test", SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("test", "This is a test", SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("cod", VisualStudioCode, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("code", VisualStudioCode, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("codes", "Visual Studio Codes", SearchPrecisionScore.Regular, true)]
|
||||
public void WhenGivenQuery_ShouldReturnResults_ContainingAllQuerySubstrings(
|
||||
string queryString,
|
||||
string compareString,
|
||||
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
|
||||
SearchPrecisionScore expectedPrecisionScore,
|
||||
bool expectedPrecisionResult)
|
||||
{
|
||||
// When
|
||||
var matcher = new StringMatcher { UserSettingSearchPrecision = expectedPrecisionScore };
|
||||
var matcher = new StringMatcher {UserSettingSearchPrecision = expectedPrecisionScore};
|
||||
|
||||
// Given
|
||||
var matchResult = matcher.FuzzyMatch(queryString, compareString);
|
||||
|
|
@ -219,7 +234,8 @@ namespace Flow.Launcher.Test
|
|||
Debug.WriteLine("");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
|
||||
Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})");
|
||||
Debug.WriteLine(
|
||||
$"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int) expectedPrecisionScore})");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine("");
|
||||
|
||||
|
|
@ -238,7 +254,7 @@ namespace Flow.Launcher.Test
|
|||
string queryString, string compareString1, string compareString2)
|
||||
{
|
||||
// When
|
||||
var matcher = new StringMatcher { UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular };
|
||||
var matcher = new StringMatcher {UserSettingSearchPrecision = SearchPrecisionScore.Regular};
|
||||
|
||||
// Given
|
||||
var compareString1Result = matcher.FuzzyMatch(queryString, compareString1);
|
||||
|
|
@ -247,8 +263,10 @@ namespace Flow.Launcher.Test
|
|||
Debug.WriteLine("");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine($"QueryString: \"{queryString}\"{Environment.NewLine}");
|
||||
Debug.WriteLine($"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}");
|
||||
Debug.WriteLine($"CompareString2: \"{compareString2}\", Score: {compareString2Result.Score}{Environment.NewLine}");
|
||||
Debug.WriteLine(
|
||||
$"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}");
|
||||
Debug.WriteLine(
|
||||
$"CompareString2: \"{compareString2}\", Score: {compareString2Result.Score}{Environment.NewLine}");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine("");
|
||||
|
||||
|
|
@ -256,13 +274,13 @@ namespace Flow.Launcher.Test
|
|||
Assert.True(compareString1Result.Score > compareString2Result.Score,
|
||||
$"Query: \"{queryString}\"{Environment.NewLine} " +
|
||||
$"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" +
|
||||
$"Should be greater than{ Environment.NewLine}" +
|
||||
$"Should be greater than{Environment.NewLine}" +
|
||||
$"CompareString2: \"{compareString2}\", Score: {compareString1Result.Score}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
[TestCase("vim", "Vim", "ignoreDescription", "ignore.exe", "Vim Diff", "ignoreDescription", "ignore.exe")]
|
||||
public void WhenMultipleResults_ExactMatchingResult_ShouldHaveGreatestScore(
|
||||
string queryString, string firstName, string firstDescription, string firstExecutableName,
|
||||
string queryString, string firstName, string firstDescription, string firstExecutableName,
|
||||
string secondName, string secondDescription, string secondExecutableName)
|
||||
{
|
||||
// Act
|
||||
|
|
@ -275,15 +293,39 @@ namespace Flow.Launcher.Test
|
|||
var secondDescriptionMatch = matcher.FuzzyMatch(queryString, secondDescription).RawScore;
|
||||
var secondExecutableNameMatch = matcher.FuzzyMatch(queryString, secondExecutableName).RawScore;
|
||||
|
||||
var firstScore = new[] { firstNameMatch, firstDescriptionMatch, firstExecutableNameMatch }.Max();
|
||||
var secondScore = new[] { secondNameMatch, secondDescriptionMatch, secondExecutableNameMatch }.Max();
|
||||
var firstScore = new[] {firstNameMatch, firstDescriptionMatch, firstExecutableNameMatch}.Max();
|
||||
var secondScore = new[] {secondNameMatch, secondDescriptionMatch, secondExecutableNameMatch}.Max();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(firstScore > secondScore,
|
||||
$"Query: \"{queryString}\"{Environment.NewLine} " +
|
||||
$"Name of first: \"{firstName}\", Final Score: {firstScore}{Environment.NewLine}" +
|
||||
$"Should be greater than{ Environment.NewLine}" +
|
||||
$"Should be greater than{Environment.NewLine}" +
|
||||
$"Name of second: \"{secondName}\", Final Score: {secondScore}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
[TestCase("vsc", "Visual Studio Code", 100)]
|
||||
[TestCase("jbr", "JetBrain Rider", 100)]
|
||||
[TestCase("jr", "JetBrain Rider", 66)]
|
||||
[TestCase("vs", "Visual Studio", 100)]
|
||||
[TestCase("vs", "Visual Studio Preview", 66)]
|
||||
[TestCase("vsp", "Visual Studio Preview", 100)]
|
||||
[TestCase("pc", "postman canary", 100)]
|
||||
[TestCase("psc", "Postman super canary", 100)]
|
||||
[TestCase("psc", "Postman super Canary", 100)]
|
||||
[TestCase("vsp", "Visual Studio", 0)]
|
||||
[TestCase("vps", "Visual Studio", 0)]
|
||||
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 75)]
|
||||
public void WhenGivenAnAcronymQuery_ShouldReturnAcronymScore(string queryString, string compareString,
|
||||
int desiredScore)
|
||||
{
|
||||
var matcher = new StringMatcher();
|
||||
var score = matcher.FuzzyMatch(queryString, compareString).Score;
|
||||
Assert.IsTrue(score == desiredScore,
|
||||
$@"Query: ""{queryString}""
|
||||
CompareString: ""{compareString}""
|
||||
Score: {score}
|
||||
Desired Score: {desiredScore}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ using Flow.Launcher.Plugin.SharedCommands;
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flow.Launcher.Test.Plugins
|
||||
{
|
||||
|
|
@ -17,15 +19,15 @@ namespace Flow.Launcher.Test.Plugins
|
|||
[TestFixture]
|
||||
public class ExplorerTest
|
||||
{
|
||||
private List<Result> MethodWindowsIndexSearchReturnsZeroResults(Query dummyQuery, string dummyString)
|
||||
private async Task<List<Result>> MethodWindowsIndexSearchReturnsZeroResultsAsync(Query dummyQuery, string dummyString, CancellationToken dummyToken)
|
||||
{
|
||||
return new List<Result>();
|
||||
}
|
||||
|
||||
private List<Result> MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString)
|
||||
private List<Result> MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString, CancellationToken token)
|
||||
{
|
||||
return new List<Result>
|
||||
{
|
||||
return new List<Result>
|
||||
{
|
||||
new Result
|
||||
{
|
||||
Title="Result 1"
|
||||
|
|
@ -58,16 +60,16 @@ namespace Flow.Launcher.Test.Plugins
|
|||
$"Actual: {result}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
[TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\'")]
|
||||
[TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\'")]
|
||||
[TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\' ORDER BY System.FileName")]
|
||||
[TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\' ORDER BY System.FileName")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString)
|
||||
{
|
||||
// Given
|
||||
var queryConstructor = new QueryConstructor(new Settings());
|
||||
|
||||
|
||||
//When
|
||||
var queryString = queryConstructor.QueryForTopLevelDirectorySearch(folderPath);
|
||||
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(queryString == expectedString,
|
||||
$"Expected string: {expectedString}{Environment.NewLine} " +
|
||||
|
|
@ -77,7 +79,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
[TestCase("C:\\SomeFolder\\flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " +
|
||||
"FROM SystemIndex WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
|
||||
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033))" +
|
||||
" AND directory='file:C:\\SomeFolder'")]
|
||||
" AND directory='file:C:\\SomeFolder' ORDER BY System.FileName")]
|
||||
public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString(
|
||||
string userSearchString, string expectedString)
|
||||
{
|
||||
|
|
@ -112,13 +114,10 @@ namespace Flow.Launcher.Test.Plugins
|
|||
}
|
||||
|
||||
[TestCase("scope='file:'")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString)
|
||||
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString)
|
||||
{
|
||||
// Given
|
||||
var queryConstructor = new QueryConstructor(new Settings());
|
||||
|
||||
//When
|
||||
var resultString = queryConstructor.QueryWhereRestrictionsForAllFilesAndFoldersSearch();
|
||||
var resultString = QueryConstructor.QueryWhereRestrictionsForAllFilesAndFoldersSearch;
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(resultString == expectedString,
|
||||
|
|
@ -128,9 +127,9 @@ namespace Flow.Launcher.Test.Plugins
|
|||
|
||||
[TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" " +
|
||||
"FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
|
||||
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:'")]
|
||||
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY System.FileName")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString(
|
||||
string userSearchString, string expectedString)
|
||||
string userSearchString, string expectedString)
|
||||
{
|
||||
// Given
|
||||
var queryConstructor = new QueryConstructor(new Settings());
|
||||
|
|
@ -145,18 +144,19 @@ namespace Flow.Launcher.Test.Plugins
|
|||
}
|
||||
|
||||
[TestCase]
|
||||
public void GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldContinueDirectoryInfoClassSearch()
|
||||
public async Task GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldContinueDirectoryInfoClassSearch()
|
||||
{
|
||||
// Given
|
||||
var searchManager = new SearchManager(new Settings(), new PluginInitContext());
|
||||
|
||||
|
||||
// When
|
||||
var results = searchManager.TopLevelDirectorySearchBehaviour(
|
||||
MethodWindowsIndexSearchReturnsZeroResults,
|
||||
MethodDirectoryInfoClassSearchReturnsTwoResults,
|
||||
false,
|
||||
var results = await searchManager.TopLevelDirectorySearchBehaviourAsync(
|
||||
MethodWindowsIndexSearchReturnsZeroResultsAsync,
|
||||
MethodDirectoryInfoClassSearchReturnsTwoResults,
|
||||
false,
|
||||
new Query(),
|
||||
"string not used");
|
||||
"string not used",
|
||||
default);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(results.Count == 2,
|
||||
|
|
@ -165,18 +165,19 @@ namespace Flow.Launcher.Test.Plugins
|
|||
}
|
||||
|
||||
[TestCase]
|
||||
public void GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearch()
|
||||
public async Task GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearch()
|
||||
{
|
||||
// Given
|
||||
var searchManager = new SearchManager(new Settings(), new PluginInitContext());
|
||||
|
||||
// When
|
||||
var results = searchManager.TopLevelDirectorySearchBehaviour(
|
||||
MethodWindowsIndexSearchReturnsZeroResults,
|
||||
var results = await searchManager.TopLevelDirectorySearchBehaviourAsync(
|
||||
MethodWindowsIndexSearchReturnsZeroResultsAsync,
|
||||
MethodDirectoryInfoClassSearchReturnsTwoResults,
|
||||
true,
|
||||
new Query(),
|
||||
"string not used");
|
||||
"string not used",
|
||||
default);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(results.Count == 0,
|
||||
|
|
@ -201,7 +202,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
}
|
||||
|
||||
[TestCase("some words", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " +
|
||||
"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:'")]
|
||||
"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY System.FileName")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString(
|
||||
string userSearchString, string expectedString)
|
||||
{
|
||||
|
|
@ -223,7 +224,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
var query = new Query { ActionKeyword = "doc:", Search = "search term" };
|
||||
|
||||
var searchManager = new SearchManager(new Settings(), new PluginInitContext());
|
||||
|
||||
|
||||
// When
|
||||
var result = searchManager.IsFileContentSearch(query.ActionKeyword);
|
||||
|
||||
|
|
@ -239,6 +240,9 @@ namespace Flow.Launcher.Test.Plugins
|
|||
[TestCase(@"cc:\", false)]
|
||||
[TestCase(@"\\\SomeNetworkLocation\", false)]
|
||||
[TestCase("RandomFile", false)]
|
||||
[TestCase(@"c:\>*", true)]
|
||||
[TestCase(@"c:\>", true)]
|
||||
[TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)]
|
||||
public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult)
|
||||
{
|
||||
// When, Given
|
||||
|
|
@ -250,7 +254,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
$"Actual check result is {result} {Environment.NewLine}");
|
||||
|
||||
}
|
||||
|
||||
|
||||
[TestCase(@"C:\SomeFolder\SomeApp", true, @"C:\SomeFolder\")]
|
||||
[TestCase(@"C:\SomeFolder\SomeApp\SomeFile", true, @"C:\SomeFolder\SomeApp\")]
|
||||
[TestCase(@"C:\NonExistentFolder\SomeApp", false, "")]
|
||||
|
|
@ -291,10 +295,10 @@ namespace Flow.Launcher.Test.Plugins
|
|||
}
|
||||
|
||||
[TestCase("c:\\SomeFolder\\>", "scope='file:c:\\SomeFolder'")]
|
||||
[TestCase("c:\\SomeFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' " +
|
||||
"OR CONTAINS(System.FileName,'\"SomeName*\"',1033)) AND " +
|
||||
"scope='file:c:\\SomeFolder'")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString(string path, string expectedString)
|
||||
[TestCase("c:\\SomeFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' "
|
||||
+ "OR CONTAINS(System.FileName,'\"SomeName*\"',1033)) AND "
|
||||
+ "scope='file:c:\\SomeFolder'")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString(string path, string expectedString)
|
||||
{
|
||||
// Given
|
||||
var queryConstructor = new QueryConstructor(new Settings());
|
||||
|
|
@ -308,16 +312,14 @@ namespace Flow.Launcher.Test.Plugins
|
|||
$"Actual string was: {resultString}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
[TestCase("c:\\somefolder\\>somefile","*somefile*")]
|
||||
[TestCase("c:\\somefolder\\>somefile", "*somefile*")]
|
||||
[TestCase("c:\\somefolder\\somefile", "somefile*")]
|
||||
[TestCase("c:\\somefolder\\", "*")]
|
||||
public void GivenDirectoryInfoSearch_WhenSearchPatternHotKeyIsSearchAll_ThenSearchCriteriaShouldUseCriteriaString(string path, string expectedString)
|
||||
{
|
||||
// Given
|
||||
var criteriaConstructor = new DirectoryInfoSearch(new PluginInitContext());
|
||||
|
||||
//When
|
||||
var resultString = criteriaConstructor.ConstructSearchCriteria(path);
|
||||
var resultString = DirectoryInfoSearch.ConstructSearchCriteria(path);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(resultString == expectedString,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher", "Flow.Launc
|
|||
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}
|
||||
{59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E}
|
||||
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}
|
||||
{F35190AA-4758-4D9E-A193-E3BDF6AD3567} = {F35190AA-4758-4D9E-A193-E3BDF6AD3567}
|
||||
{9B130CC5-14FB-41FF-B310-0A95B6894C37} = {9B130CC5-14FB-41FF-B310-0A95B6894C37}
|
||||
{FDED22C8-B637-42E8-824A-63B5B6E05A3A} = {FDED22C8-B637-42E8-824A-63B5B6E05A3A}
|
||||
{A3DCCBCA-ACC1-421D-B16E-210896234C26} = {A3DCCBCA-ACC1-421D-B16E-210896234C26}
|
||||
|
|
@ -44,8 +43,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Sys",
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Url", "Plugins\Flow.Launcher.Plugin.Url\Flow.Launcher.Plugin.Url.csproj", "{A3DCCBCA-ACC1-421D-B16E-210896234C26}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Color", "Plugins\Flow.Launcher.Plugin.Color\Flow.Launcher.Plugin.Color.csproj", "{F35190AA-4758-4D9E-A193-E3BDF6AD3567}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FFD651C7-0546-441F-BC8C-D4EE8FD01EA7}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitattributes = .gitattributes
|
||||
|
|
@ -214,18 +211,6 @@ Global
|
|||
{A3DCCBCA-ACC1-421D-B16E-210896234C26}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A3DCCBCA-ACC1-421D-B16E-210896234C26}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A3DCCBCA-ACC1-421D-B16E-210896234C26}.Release|x86.Build.0 = Release|Any CPU
|
||||
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|x64.Build.0 = Release|Any CPU
|
||||
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F35190AA-4758-4D9E-A193-E3BDF6AD3567}.Release|x86.Build.0 = Release|Any CPU
|
||||
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -309,7 +294,6 @@ Global
|
|||
{FDED22C8-B637-42E8-824A-63B5B6E05A3A} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
|
||||
{0B9DE348-9361-4940-ADB6-F5953BFFCCEC} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
|
||||
{A3DCCBCA-ACC1-421D-B16E-210896234C26} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
|
||||
{F35190AA-4758-4D9E-A193-E3BDF6AD3567} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
|
||||
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
|
||||
{9B130CC5-14FB-41FF-B310-0A95B6894C37} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
|
||||
{59BD9891-3837-438A-958D-ADC7F91F6F7E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
ShutdownMode="OnMainWindowClose"
|
||||
Startup="OnStartup">
|
||||
Startup="OnStartupAsync">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ namespace Flow.Launcher
|
|||
}
|
||||
}
|
||||
|
||||
private void OnStartup(object sender, StartupEventArgs e)
|
||||
private async void OnStartupAsync(object sender, StartupEventArgs e)
|
||||
{
|
||||
Stopwatch.Normal("|App.OnStartup|Startup cost", () =>
|
||||
await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
|
||||
{
|
||||
_portable.PreStartCleanUpAfterPortabilityUpdate();
|
||||
|
||||
|
|
@ -61,6 +61,8 @@ namespace Flow.Launcher
|
|||
_settingsVM = new SettingWindowViewModel(_updater, _portable);
|
||||
_settings = _settingsVM.Settings;
|
||||
|
||||
Http.Proxy = _settings.Proxy;
|
||||
|
||||
_alphabet.Initialize(_settings);
|
||||
_stringMatcher = new StringMatcher(_alphabet);
|
||||
StringMatcher.Instance = _stringMatcher;
|
||||
|
|
@ -68,9 +70,10 @@ namespace Flow.Launcher
|
|||
|
||||
PluginManager.LoadPlugins(_settings.PluginSettings);
|
||||
_mainVM = new MainViewModel(_settings);
|
||||
var window = new MainWindow(_settings, _mainVM);
|
||||
API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet);
|
||||
PluginManager.InitializePlugins(API);
|
||||
await PluginManager.InitializePlugins(API);
|
||||
var window = new MainWindow(_settings, _mainVM);
|
||||
|
||||
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
|
||||
|
||||
Current.MainWindow = window;
|
||||
|
|
@ -84,8 +87,6 @@ namespace Flow.Launcher
|
|||
ThemeManager.Instance.Settings = _settings;
|
||||
ThemeManager.Instance.ChangeTheme(_settings.Theme);
|
||||
|
||||
Http.Proxy = _settings.Proxy;
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
RegisterExitEvents();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
Icon="Images\app.png"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Title="Custom Plugin Hotkey" Height="200" Width="674.766">
|
||||
Title="{DynamicResource customeQueryHotkeyTitle}" Height="200" Width="674.766">
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Key="Escape" Command="Close"/>
|
||||
</Window.InputBindings>
|
||||
|
|
|
|||
|
|
@ -60,6 +60,15 @@
|
|||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\*.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\*.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\*.ico">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -78,7 +87,8 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="2.5.13" />
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.3.1" />
|
||||
<PackageReference Include="SharpVectors" Version="1.7.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -87,116 +97,7 @@
|
|||
<ProjectReference Include="..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Images\app.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<Resource Include="Images\app.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<None Update="Images\app_error.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\Browser.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\calculator.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\cancel.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\close.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\cmd.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\color.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\copy.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\down.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\EXE.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\file.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\find.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\folder.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\history.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\image.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\Link.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\lock.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\logoff.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\mainsearch.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\New Message.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\ok.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\open.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\plugin.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\recyclebin.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\restart.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\search.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\settings.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\shutdown.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\sleep.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\up.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\update.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Images\warning.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
<Exec Command="taskkill /f /fi "IMAGENAME eq Flow.Launcher.exe"" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="powershell.exe -NoProfile -ExecutionPolicy Bypass -File $(SolutionDir)Scripts\post_build.ps1 $(ConfigurationName) $(SolutionDir) $(TargetPath)" />
|
||||
</Target>
|
||||
</Project>
|
||||
BIN
Flow.Launcher/Images/app.ico
Normal file
|
After Width: | Height: | Size: 114 KiB |
10
Flow.Launcher/Images/mainsearch.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="1200px" height="1200px" viewBox="0 0 12000 12000" preserveAspectRatio="xMidYMid meet">
|
||||
<g id="layer101" fill="#000000" stroke="none">
|
||||
</g>
|
||||
<g id="layer102" fill="#555555" stroke="none">
|
||||
<path d="M10354 10962 c-28 -11 -75 -35 -105 -55 -33 -21 -773 -754 -1879 -1861 -1004 -1004 -1829 -1826 -1834 -1826 -4 0 -38 22 -75 48 -248 179 -578 351 -869 453 -398 139 -790 198 -1232 185 -748 -20 -1407 -259 -2020 -732 -209 -161 -508 -475 -676 -709 -270 -377 -476 -847 -567 -1295 -53 -261 -67 -406 -67 -700 0 -340 26 -556 105 -861 128 -499 376 -976 715 -1374 86 -101 312 -324 410 -406 521 -434 1162 -709 1830 -784 181 -20 577 -20 758 0 657 75 1252 323 1782 744 144 114 451 426 556 566 176 233 281 404 393 635 223 465 332 947 332 1470 0 394 -50 705 -174 1082 -53 160 -62 182 -135 343 -85 186 -212 407 -332 575 -28 39 -50 73 -50 78 0 4 826 833 1835 1842 1386 1386 1843 1849 1869 1894 21 34 42 90 52 134 14 64 15 85 4 146 -28 163 -140 311 -290 383 -69 34 -83 37 -180 40 -85 3 -115 0 -156 -15z m-5669 -3912 c529 -49 1009 -241 1415 -566 109 -88 296 -275 384 -384 667 -833 762 -1990 237 -2910 -352 -619 -923 -1053 -1621 -1234 -394 -101 -878 -101 -1270 1 -382 99 -690 253 -992 496 -501 402 -828 974 -930 1627 -31 194 -31 576 0 770 40 255 120 520 225 740 326 682 944 1192 1677 1383 157 41 275 61 480 80 81 8 293 6 395 -3z"/>
|
||||
</g>
|
||||
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -17,6 +17,7 @@
|
|||
<!--Setting General-->
|
||||
<system:String x:Key="flowlauncher_settings">Flow Launcher Settings</system:String>
|
||||
<system:String x:Key="general">General</system:String>
|
||||
<system:String x:Key="portableMode">Portable Mode</system:String>
|
||||
<system:String x:Key="startFlowLauncherOnSystemStartup">Start Flow Launcher on system startup</system:String>
|
||||
<system:String x:Key="hideFlowLauncherWhenLoseFocus">Hide Flow Launcher when focus is lost</system:String>
|
||||
<system:String x:Key="dontPromptUpdateMsg">Do not show new version notifications</system:String>
|
||||
|
|
@ -39,10 +40,13 @@
|
|||
<!--Setting Plugin-->
|
||||
<system:String x:Key="plugin">Plugin</system:String>
|
||||
<system:String x:Key="browserMorePlugins">Find more plugins</system:String>
|
||||
<system:String x:Key="enable">Enable</system:String>
|
||||
<system:String x:Key="disable">Disable</system:String>
|
||||
<system:String x:Key="actionKeywords">Action keyword:</system:String>
|
||||
<system:String x:Key="currentActionKeywords">Current action keyword:</system:String>
|
||||
<system:String x:Key="newActionKeyword">New action keyword:</system:String>
|
||||
<system:String x:Key="currentPriority">Current Priority:</system:String>
|
||||
<system:String x:Key="newPriority">New Priority:</system:String>
|
||||
<system:String x:Key="pluginDirectory">Plugin Directory</system:String>
|
||||
<system:String x:Key="author">Author</system:String>
|
||||
<system:String x:Key="plugin_init_time">Init time:</system:String>
|
||||
|
|
@ -72,7 +76,7 @@
|
|||
<system:String x:Key="deleteCustomHotkeyWarning">Are you sure you want to delete {0} plugin hotkey?</system:String>
|
||||
<system:String x:Key="queryWindowShadowEffect">Query window shadow effect</system:String>
|
||||
<system:String x:Key="shadowEffectCPUUsage">Shadow effect has a substantial usage of GPU.</system:String>
|
||||
<system:String x:Key="shadowEffectPerformance">Not recommended if you computer performance is limited.</system:String>
|
||||
<system:String x:Key="shadowEffectPerformance">Not recommended if your computer performance is limited.</system:String>
|
||||
|
||||
<!--Setting Proxy-->
|
||||
<system:String x:Key="proxy">HTTP Proxy</system:String>
|
||||
|
|
@ -104,6 +108,10 @@
|
|||
</system:String>
|
||||
<system:String x:Key="releaseNotes">Release Notes</system:String>
|
||||
|
||||
<!--Priority Setting Dialog-->
|
||||
<system:String x:Key="priority_tips">Greater the number, the higher the result will be ranked. Try setting it as 5. If you want the results to be lower than any other plugin's, provide a negative number</system:String>
|
||||
<system:String x:Key="invalidPriority">Please provide an valid integer for Priority!</system:String>
|
||||
|
||||
<!--Action Keyword Setting Dialog-->
|
||||
<system:String x:Key="oldActionKeywords">Old Action Keyword</system:String>
|
||||
<system:String x:Key="newActionKeywords">New Action Keyword</system:String>
|
||||
|
|
@ -113,9 +121,11 @@
|
|||
<system:String x:Key="newActionKeywordsCannotBeEmpty">New Action Keyword can't be empty</system:String>
|
||||
<system:String x:Key="newActionKeywordsHasBeenAssigned">This new Action Keyword is already assigned to another plugin, please choose a different one</system:String>
|
||||
<system:String x:Key="success">Success</system:String>
|
||||
<system:String x:Key="completedSuccessfully">Completed successfully</system:String>
|
||||
<system:String x:Key="actionkeyword_tips">Use * if you don't want to specify an action keyword</system:String>
|
||||
|
||||
<!--Custom Query Hotkey Dialog-->
|
||||
<system:String x:Key="customeQueryHotkeyTitle">Custom Plugin Hotkey</system:String>
|
||||
<system:String x:Key="preview">Preview</system:String>
|
||||
<system:String x:Key="hotkeyIsNotUnavailable">Hotkey is unavailable, please select a new hotkey</system:String>
|
||||
<system:String x:Key="invalidPluginHotkey">Invalid plugin hotkey</system:String>
|
||||
|
|
@ -140,11 +150,23 @@
|
|||
<system:String x:Key="reportWindow_report_failed">Failed to send report</system:String>
|
||||
<system:String x:Key="reportWindow_flowlauncher_got_an_error">Flow Launcher got an error</system:String>
|
||||
|
||||
<!--General Notice-->
|
||||
<system:String x:Key="pleaseWait">Please wait...</system:String>
|
||||
|
||||
<!--update-->
|
||||
<system:String x:Key="update_flowlauncher_update_check">Checking for new update</system:String>
|
||||
<system:String x:Key="update_flowlauncher_already_on_latest">You already have the latest Flow Launcher version</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_found">Update found</system:String>
|
||||
<system:String x:Key="update_flowlauncher_updating">Updating...</system:String>
|
||||
<system:String x:Key="update_flowlauncher_fail_moving_portable_user_profile_data">Flow Launcher was not able to move your user profile data to the new update version.
|
||||
Please manually move your profile data folder from {0} to {1}</system:String>
|
||||
<system:String x:Key="update_flowlauncher_new_update">New Update</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_new_version_available">New Flow Launcher release {0} is now available</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_error">An error occurred while trying to install software updates</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update">Update</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_cancel">Cancel</system:String>
|
||||
<system:String x:Key="update_flowlauncher_fail">Update Failed</system:String>
|
||||
<system:String x:Key="update_flowlauncher_check_connection">Check your connection and try updating proxy settings to github-cloud.s3.amazonaws.com.</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_restart_flowlauncher_tip">This upgrade will restart Flow Launcher</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_upadte_files">Following files will be updated</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_files">Update files</system:String>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
<!--Setting General-->
|
||||
<system:String x:Key="flowlauncher_settings">Nastavenia Flow Launchera</system:String>
|
||||
<system:String x:Key="general">Všeobecné</system:String>
|
||||
<system:String x:Key="portableMode">Prenosný režim</system:String>
|
||||
<system:String x:Key="startFlowLauncherOnSystemStartup">Spustiť Flow Launcher po štarte systému</system:String>
|
||||
<system:String x:Key="hideFlowLauncherWhenLoseFocus">Schovať Flow Launcher po strate fokusu</system:String>
|
||||
<system:String x:Key="dontPromptUpdateMsg">Nezobrazovať upozornenia na novú verziu</system:String>
|
||||
|
|
@ -39,14 +40,17 @@
|
|||
<!--Setting Plugin-->
|
||||
<system:String x:Key="plugin">Plugin</system:String>
|
||||
<system:String x:Key="browserMorePlugins">Nájsť ďalšie pluginy</system:String>
|
||||
<system:String x:Key="disable">Zakázať</system:String>
|
||||
<system:String x:Key="enable">Povolené</system:String>
|
||||
<system:String x:Key="disable">Zakázané</system:String>
|
||||
<system:String x:Key="actionKeywords">Skratka akcie</system:String>
|
||||
<system:String x:Key="currentActionKeywords">Aktuálna akcia skratky:</system:String>
|
||||
<system:String x:Key="newActionKeyword">Nová akcia skratky:</system:String>
|
||||
<system:String x:Key="currentPriority">Aktuálna priorita:</system:String>
|
||||
<system:String x:Key="newPriority">Nová priorita:</system:String>
|
||||
<system:String x:Key="pluginDirectory">Priečinok s pluginmi</system:String>
|
||||
<system:String x:Key="author">Autor</system:String>
|
||||
<system:String x:Key="plugin_init_time">Príprava: {0}ms</system:String>
|
||||
<system:String x:Key="plugin_query_time">Čas dopytu: {0}ms</system:String>
|
||||
<system:String x:Key="plugin_init_time">Príprava:</system:String>
|
||||
<system:String x:Key="plugin_query_time">Čas dopytu:</system:String>
|
||||
|
||||
<!--Setting Theme-->
|
||||
<system:String x:Key="theme">Motív</system:String>
|
||||
|
|
@ -104,6 +108,10 @@
|
|||
</system:String>
|
||||
<system:String x:Key="releaseNotes">Poznámky k vydaniu</system:String>
|
||||
|
||||
<!--Priority Setting Dialog-->
|
||||
<system:String x:Key="priority_tips">Vyššie číslo znamená, že výsledok bude vyššie. Skúste nastaviť napr. 5. Ak chcete, aby boli výsledky nižšie ako ktorékoľvek iné doplnky, zadajte záporné číslo</system:String>
|
||||
<system:String x:Key="invalidPriority">Prosím, zadajte platné číslo pre prioritu!</system:String>
|
||||
|
||||
<!--Action Keyword Setting Dialog-->
|
||||
<system:String x:Key="oldActionKeywords">Stará skratka akcie</system:String>
|
||||
<system:String x:Key="newActionKeywords">Nová skratka akcie</system:String>
|
||||
|
|
@ -116,6 +124,7 @@
|
|||
<system:String x:Key="actionkeyword_tips">Použite * ak nechcete určiť skratku pre akciu</system:String>
|
||||
|
||||
<!--Custom Query Hotkey Dialog-->
|
||||
<system:String x:Key="customeQueryHotkeyTitle">Vlastná klávesová skratka pre plugin</system:String>
|
||||
<system:String x:Key="preview">Náhľad</system:String>
|
||||
<system:String x:Key="hotkeyIsNotUnavailable">Klávesová skratka je nedostupná, prosím, zadajte novú</system:String>
|
||||
<system:String x:Key="invalidPluginHotkey">Neplatná klávesová skratka pluginu</system:String>
|
||||
|
|
@ -140,11 +149,23 @@
|
|||
<system:String x:Key="reportWindow_report_failed">Odoslanie hlásenia zlyhalo</system:String>
|
||||
<system:String x:Key="reportWindow_flowlauncher_got_an_error">Flow Launcher zaznamenal chybu</system:String>
|
||||
|
||||
<!--General Notice-->
|
||||
<system:String x:Key="pleaseWait">Čakajte, prosím…</system:String>
|
||||
|
||||
<!--update-->
|
||||
<system:String x:Key="update_flowlauncher_update_new_version_available">Je dostupná nová verzia Flow Launcher {0}</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_check">Kontrolujú sa akutalizácie</system:String>
|
||||
<system:String x:Key="update_flowlauncher_already_on_latest">Už máte najnovšiu verizu Flow Launchera</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_found">Bola nájdená aktualizácia</system:String>
|
||||
<system:String x:Key="update_flowlauncher_updating">Aktualizuje sa…</system:String>
|
||||
<system:String x:Key="update_flowlauncher_fail_moving_portable_user_profile_data">Flow Launcher nedokázal presunúť používateľské údaje do aktualizovanej verzie.
|
||||
Prosím, presuňte profilový priečinok „data“ z {0} do {1}</system:String>
|
||||
<system:String x:Key="update_flowlauncher_new_update">Nová aktualizácia</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_new_version_available">Je dostupná nová verzia Flow Launchera {0}</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_error">Počas inštalácie aktualizácií došlo k chybe</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update">Aktualizovať</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_cancel">Zrušiť</system:String>
|
||||
<system:String x:Key="update_flowlauncher_fail">Aktualizácia zlyhala</system:String>
|
||||
<system:String x:Key="update_flowlauncher_check_connection">Skontrolujte pripojenie a skúste aktualizovať nastavenia servera proxy na github-cloud.s3.amazonaws.com.</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_restart_flowlauncher_tip">Tento upgrade reštartuje Flow Launcher</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_upadte_files">Nasledujúce súbory budú aktualizované</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_files">Aktualizovať súbory</system:String>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:converters="clr-namespace:Flow.Launcher.Converters"
|
||||
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
|
||||
mc:Ignorable="d"
|
||||
Title="Flow Launcher"
|
||||
Topmost="True"
|
||||
|
|
@ -33,6 +34,7 @@
|
|||
<Window.InputBindings>
|
||||
<KeyBinding Key="Escape" Command="{Binding EscCommand}"></KeyBinding>
|
||||
<KeyBinding Key="F1" Command="{Binding StartHelpCommand}"></KeyBinding>
|
||||
<KeyBinding Key="F5" Command="{Binding ReloadPluginDataCommand}"></KeyBinding>
|
||||
<KeyBinding Key="Tab" Command="{Binding SelectNextItemCommand}"></KeyBinding>
|
||||
<KeyBinding Key="Tab" Modifiers="Shift" Command="{Binding SelectPrevItemCommand}"></KeyBinding>
|
||||
<KeyBinding Key="N" Modifiers="Ctrl" Command="{Binding SelectNextItemCommand}"></KeyBinding>
|
||||
|
|
@ -92,10 +94,11 @@
|
|||
</ContextMenu>
|
||||
</TextBox.ContextMenu>
|
||||
</TextBox>
|
||||
<Image Source="{Binding Image, IsAsync=True}" Width="48" HorizontalAlignment="Right" />
|
||||
<svgc:SvgControl Source="{Binding Image}" HorizontalAlignment="Right" Width="48" Height="48"
|
||||
Background="Transparent"/>
|
||||
</Grid>
|
||||
<Line x:Name="ProgressBar" HorizontalAlignment="Right"
|
||||
Style="{DynamicResource PendingLineStyle}" Visibility="{Binding ProgressBarVisibility, Mode=TwoWay}"
|
||||
Style="{DynamicResource PendingLineStyle}" Visibility="{Binding ProgressBarVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Y1="0" Y2="0" X2="100" Height="2" Width="752" StrokeThickness="1">
|
||||
</Line>
|
||||
<ContentControl>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace Flow.Launcher
|
|||
#region Private Fields
|
||||
|
||||
private readonly Storyboard _progressBarStoryboard = new Storyboard();
|
||||
private bool isProgressBarStoryboardPaused;
|
||||
private Settings _settings;
|
||||
private NotifyIcon _notifyIcon;
|
||||
private MainViewModel _viewModel;
|
||||
|
|
@ -52,7 +53,7 @@ namespace Flow.Launcher
|
|||
|
||||
private void OnInitialized(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs _)
|
||||
|
|
@ -73,7 +74,7 @@ namespace Flow.Launcher
|
|||
{
|
||||
if (e.PropertyName == nameof(MainViewModel.MainWindowVisibility))
|
||||
{
|
||||
if (Visibility == Visibility.Visible)
|
||||
if (_viewModel.MainWindowVisibility == Visibility.Visible)
|
||||
{
|
||||
Activate();
|
||||
QueryTextBox.Focus();
|
||||
|
|
@ -84,7 +85,34 @@ namespace Flow.Launcher
|
|||
QueryTextBox.SelectAll();
|
||||
_viewModel.LastQuerySelected = true;
|
||||
}
|
||||
|
||||
if (_viewModel.ProgressBarVisibility == Visibility.Visible && isProgressBarStoryboardPaused)
|
||||
{
|
||||
_progressBarStoryboard.Resume();
|
||||
isProgressBarStoryboardPaused = false;
|
||||
}
|
||||
}
|
||||
else if (!isProgressBarStoryboardPaused)
|
||||
{
|
||||
_progressBarStoryboard.Pause();
|
||||
isProgressBarStoryboardPaused = true;
|
||||
}
|
||||
}
|
||||
else if (e.PropertyName == nameof(MainViewModel.ProgressBarVisibility))
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (_viewModel.ProgressBarVisibility == Visibility.Hidden && !isProgressBarStoryboardPaused)
|
||||
{
|
||||
_progressBarStoryboard.Pause();
|
||||
isProgressBarStoryboardPaused = true;
|
||||
}
|
||||
else if (_viewModel.MainWindowVisibility == Visibility.Visible && isProgressBarStoryboardPaused)
|
||||
{
|
||||
_progressBarStoryboard.Resume();
|
||||
isProgressBarStoryboardPaused = false;
|
||||
}
|
||||
}, System.Windows.Threading.DispatcherPriority.Render);
|
||||
}
|
||||
};
|
||||
_settings.PropertyChanged += (o, e) =>
|
||||
|
|
@ -170,6 +198,7 @@ namespace Flow.Launcher
|
|||
_progressBarStoryboard.RepeatBehavior = RepeatBehavior.Forever;
|
||||
ProgressBar.BeginStoryboard(_progressBarStoryboard);
|
||||
_viewModel.ProgressBarVisibility = Visibility.Hidden;
|
||||
isProgressBarStoryboardPaused = true;
|
||||
}
|
||||
|
||||
private void OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ namespace Flow.Launcher
|
|||
}
|
||||
if (!File.Exists(iconPath))
|
||||
{
|
||||
imgIco.Source = ImageLoader.Load(Path.Combine(Infrastructure.Constant.ProgramDirectory, "Images\\app.png"));
|
||||
imgIco.Source = ImageLoader.Load(Path.Combine(Constant.ProgramDirectory, "Images\\app.png"));
|
||||
}
|
||||
else {
|
||||
imgIco.Source = ImageLoader.Load(iconPath);
|
||||
|
|
|
|||
45
Flow.Launcher/PriorityChangeWindow.xaml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<Window x:Class="Flow.Launcher.PriorityChangeWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Flow.Launcher"
|
||||
Loaded="PriorityChangeWindow_Loaded"
|
||||
mc:Ignorable="d"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Title="PriorityChangeWindow" Height="250" Width="300">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="60"/>
|
||||
<RowDefinition Height="75"/>
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="20" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock FontSize="14" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left" Text="{DynamicResource currentPriority}" />
|
||||
<TextBlock x:Name="OldPriority" Grid.Row="0" Grid.Column="1" Margin="170 10 10 10" FontSize="14"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock FontSize="14" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left" Text="{DynamicResource newPriority}" />
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Grid.Column="1">
|
||||
<TextBox x:Name="tbAction" Margin="140 10 15 10" Width="105" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.ColumnSpan="1" Grid.Column="1" Foreground="Gray"
|
||||
Text="{DynamicResource priority_tips}" TextWrapping="Wrap"
|
||||
Margin="0,0,20,0"/>
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="3" Grid.Column="1">
|
||||
<Button x:Name="btnCancel" Click="BtnCancel_OnClick" Margin="10 0 10 0" Width="80" Height="30"
|
||||
Content="{DynamicResource cancel}" />
|
||||
<Button x:Name="btnDone" Margin="10 0 10 0" Width="80" Height="30" Click="btnDone_OnClick">
|
||||
<TextBlock x:Name="lblAdd" Text="{DynamicResource done}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
69
Flow.Launcher/PriorityChangeWindow.xaml.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.ViewModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction Logic of PriorityChangeWindow.xaml
|
||||
/// </summary>
|
||||
public partial class PriorityChangeWindow : Window
|
||||
{
|
||||
private readonly PluginPair plugin;
|
||||
private Settings settings;
|
||||
private readonly Internationalization translater = InternationalizationManager.Instance;
|
||||
private readonly PluginViewModel pluginViewModel;
|
||||
|
||||
public PriorityChangeWindow(string pluginId, Settings settings, PluginViewModel pluginViewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
plugin = PluginManager.GetPluginForId(pluginId);
|
||||
this.settings = settings;
|
||||
this.pluginViewModel = pluginViewModel;
|
||||
if (plugin == null)
|
||||
{
|
||||
MessageBox.Show(translater.GetTranslation("cannotFindSpecifiedPlugin"));
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void btnDone_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (int.TryParse(tbAction.Text.Trim(), out var newPriority))
|
||||
{
|
||||
pluginViewModel.ChangePriority(newPriority);
|
||||
Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
string msg = translater.GetTranslation("invalidPriority");
|
||||
MessageBox.Show(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void PriorityChangeWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OldPriority.Text = pluginViewModel.Priority.ToString();
|
||||
tbAction.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ using System.Net;
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Squirrel;
|
||||
using Flow.Launcher.Core;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Helper;
|
||||
|
|
@ -14,6 +13,14 @@ using Flow.Launcher.Infrastructure.Hotkey;
|
|||
using Flow.Launcher.Infrastructure.Image;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.ViewModel;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
using Flow.Launcher.Infrastructure.Http;
|
||||
using JetBrains.Annotations;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
|
|
@ -57,18 +64,15 @@ namespace Flow.Launcher
|
|||
// which will cause ungraceful exit
|
||||
SaveAppAllSettings();
|
||||
|
||||
// Restart requires Squirrel's Update.exe to be present in the parent folder,
|
||||
// it is only published from the project's release pipeline. When debugging without it,
|
||||
// the project may not restart or just terminates. This is expected.
|
||||
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
||||
}
|
||||
|
||||
public void RestarApp()
|
||||
{
|
||||
RestartApp();
|
||||
}
|
||||
public void RestarApp() => RestartApp();
|
||||
|
||||
public void CheckForNewUpdate()
|
||||
{
|
||||
_settingsVM.UpdateApp();
|
||||
}
|
||||
public void CheckForNewUpdate() => _settingsVM.UpdateApp();
|
||||
|
||||
public void SaveAppAllSettings()
|
||||
{
|
||||
|
|
@ -78,21 +82,15 @@ namespace Flow.Launcher
|
|||
ImageLoader.Save();
|
||||
}
|
||||
|
||||
public void ReloadAllPluginData()
|
||||
{
|
||||
PluginManager.ReloadData();
|
||||
}
|
||||
public Task ReloadAllPluginData() => PluginManager.ReloadData();
|
||||
|
||||
public void ShowMsg(string title, string subTitle = "", string iconPath = "")
|
||||
{
|
||||
ShowMsg(title, subTitle, iconPath, true);
|
||||
}
|
||||
public void ShowMsg(string title, string subTitle = "", string iconPath = "") => ShowMsg(title, subTitle, iconPath, true);
|
||||
|
||||
public void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var msg = useMainWindowAsOwner ? new Msg {Owner = Application.Current.MainWindow} : new Msg();
|
||||
var msg = useMainWindowAsOwner ? new Msg { Owner = Application.Current.MainWindow } : new Msg();
|
||||
msg.Show(title, subTitle, iconPath);
|
||||
});
|
||||
}
|
||||
|
|
@ -105,25 +103,37 @@ namespace Flow.Launcher
|
|||
});
|
||||
}
|
||||
|
||||
public void StartLoadingBar()
|
||||
{
|
||||
_mainVM.ProgressBarVisibility = Visibility.Visible;
|
||||
}
|
||||
public void StartLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Visible;
|
||||
|
||||
public void StopLoadingBar()
|
||||
{
|
||||
_mainVM.ProgressBarVisibility = Visibility.Collapsed;
|
||||
}
|
||||
public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed;
|
||||
|
||||
public string GetTranslation(string key)
|
||||
{
|
||||
return InternationalizationManager.Instance.GetTranslation(key);
|
||||
}
|
||||
public string GetTranslation(string key) => InternationalizationManager.Instance.GetTranslation(key);
|
||||
|
||||
public List<PluginPair> GetAllPlugins()
|
||||
{
|
||||
return PluginManager.AllPlugins.ToList();
|
||||
}
|
||||
public List<PluginPair> GetAllPlugins() => PluginManager.AllPlugins.ToList();
|
||||
|
||||
public MatchResult FuzzySearch(string query, string stringToCompare) => StringMatcher.FuzzySearch(query, stringToCompare);
|
||||
|
||||
public Task<string> HttpGetStringAsync(string url, CancellationToken token = default) => Http.GetAsync(url);
|
||||
|
||||
public Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = default) => Http.GetStreamAsync(url);
|
||||
|
||||
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default) => Http.DownloadAsync(url, filePath, token);
|
||||
|
||||
public void AddActionKeyword(string pluginId, string newActionKeyword) => PluginManager.AddActionKeyword(pluginId, newActionKeyword);
|
||||
|
||||
public void RemoveActionKeyword(string pluginId, string oldActionKeyword) => PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword);
|
||||
|
||||
public void LogDebug(string className, string message, [CallerMemberName] string methodName = "") => Log.Debug(className, message, methodName);
|
||||
|
||||
public void LogInfo(string className, string message, [CallerMemberName] string methodName = "") => Log.Info(className, message, methodName);
|
||||
|
||||
public void LogWarn(string className, string message, [CallerMemberName] string methodName = "") => Log.Warn(className, message, methodName);
|
||||
|
||||
public void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "") => Log.Exception(className, message, e, methodName);
|
||||
|
||||
public T LoadJsonStorage<T>() where T : new() => new PluginJsonStorage<T>().Load();
|
||||
|
||||
public void SaveJsonStorage<T>(T setting) where T : new() => new PluginJsonStorage<T>(setting).Save();
|
||||
|
||||
public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent;
|
||||
|
||||
|
|
@ -139,6 +149,7 @@ namespace Flow.Launcher
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
d:DataContext="{d:DesignInstance vm:ResultsViewModel}"
|
||||
MaxHeight="{Binding MaxHeight}"
|
||||
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
|
||||
SelectedItem="{Binding SelectedItem, Mode=OneWayToSource}"
|
||||
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
|
||||
HorizontalContentAlignment="Stretch" ItemsSource="{Binding Results}"
|
||||
Margin="{Binding Margin}"
|
||||
Visibility="{Binding Visbility}"
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@
|
|||
xmlns:userSettings="clr-namespace:Flow.Launcher.Infrastructure.UserSettings;assembly=Flow.Launcher.Infrastructure"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
|
||||
x:Class="Flow.Launcher.SettingWindow"
|
||||
mc:Ignorable="d"
|
||||
Icon="Images\app.png"
|
||||
Icon="Images\app.ico"
|
||||
Title="{DynamicResource flowlauncher_settings}"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
|
|
@ -36,7 +37,7 @@
|
|||
<ScrollViewer ui:ScrollViewerHelper.AutoHideScrollBars="True" Margin="60,30,0,30">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<ui:ToggleSwitch Margin="10" IsOn="{Binding PortableMode}">
|
||||
<TextBlock Text="Portable Mode" />
|
||||
<TextBlock Text="{DynamicResource portableMode}" />
|
||||
</ui:ToggleSwitch>
|
||||
<CheckBox Margin="10" IsChecked="{Binding Settings.StartFlowLauncherOnSystemStartup}"
|
||||
Checked="OnAutoStartupChecked" Unchecked="OnAutoStartupUncheck">
|
||||
|
|
@ -165,17 +166,21 @@
|
|||
</TextBlock>
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
<ui:ToggleSwitch Grid.Column="1" OffContent="Disabled" OnContent="Enabled"
|
||||
<ui:ToggleSwitch Grid.Column="1" OffContent="{DynamicResource disable}" OnContent="{DynamicResource enable}"
|
||||
MaxWidth="110" HorizontalAlignment="Right"
|
||||
IsOn="{Binding PluginState}"/>
|
||||
</Grid>
|
||||
<TextBlock Text="{Binding PluginPair.Metadata.Description}"
|
||||
Grid.Row="1" Opacity="0.5" />
|
||||
<DockPanel Grid.Row="2" Margin="0 10 0 8" HorizontalAlignment="Right">
|
||||
|
||||
<TextBlock Text="Priority" Margin="20,0,0,0"/>
|
||||
<TextBlock Text="{Binding Priority}"
|
||||
ToolTip="Change Plugin Results Priority"
|
||||
Margin="5 0 0 0" Cursor="Hand" Foreground="Blue"
|
||||
MouseUp="OnPluginPriorityClick"/>
|
||||
<TextBlock Text="{DynamicResource actionKeywords}"
|
||||
Visibility="{Binding ActionKeywordsVisibility}"
|
||||
Margin="20 0 0 0"/>
|
||||
Margin="5 0 0 0"/>
|
||||
<TextBlock Text="{Binding ActionKeywordsText}"
|
||||
Visibility="{Binding ActionKeywordsVisibility}"
|
||||
ToolTip="Change Action Keywords"
|
||||
|
|
@ -252,7 +257,8 @@
|
|||
Text="{DynamicResource hiThere}" IsReadOnly="True"
|
||||
Style="{DynamicResource QueryBoxStyle}"
|
||||
Margin="18 0 56 0" />
|
||||
<Image Source="{Binding ThemeImage}" HorizontalAlignment="Right" />
|
||||
<svgc:SvgControl Source="{Binding ThemeImage}" Height="48" Width="48" HorizontalAlignment="Right"
|
||||
Background="Transparent" />
|
||||
<ContentControl Grid.Row="1">
|
||||
<flowlauncher:ResultListBox DataContext="{Binding PreviewResults, Mode=OneTime}" Visibility="Visible" />
|
||||
</ContentControl>
|
||||
|
|
@ -429,8 +435,8 @@
|
|||
<TextBlock Grid.Row="0" Grid.ColumnSpan="2" Text="{Binding ActivatedTimes, Mode=OneWay}" FontSize="12" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="{DynamicResource website}"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left">
|
||||
<Hyperlink NavigateUri="{Binding Github, Mode=OneWay}" RequestNavigate="OnRequestNavigate">
|
||||
<Run Text="{Binding Github, Mode=OneWay}" />
|
||||
<Hyperlink NavigateUri="{Binding Website, Mode=OneWay}" RequestNavigate="OnRequestNavigate">
|
||||
<Run Text="{Binding Website, Mode=OneWay}" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Version" />
|
||||
|
|
|
|||
|
|
@ -206,7 +206,16 @@ namespace Flow.Launcher
|
|||
{
|
||||
var id = viewModel.SelectedPlugin.PluginPair.Metadata.ID;
|
||||
// used to sync the current status from the plugin manager into the setting to keep consistency after save
|
||||
settings.PluginSettings.Plugins[id].Disabled = viewModel.SelectedPlugin.PluginPair.Metadata.Disabled;
|
||||
settings.PluginSettings.Plugins[id].Disabled = viewModel.SelectedPlugin.PluginPair.Metadata.Disabled;
|
||||
}
|
||||
|
||||
private void OnPluginPriorityClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(viewModel.SelectedPlugin.PluginPair.Metadata.ID, settings, viewModel.SelectedPlugin);
|
||||
priorityChangeWindow.ShowDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPluginActionKeywordsClick(object sender, MouseButtonEventArgs e)
|
||||
|
|
@ -281,5 +290,6 @@ namespace Flow.Launcher
|
|||
{
|
||||
FilesFolders.OpenPath(Path.Combine(DataLocation.DataDirectory(), Constant.Themes));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Storage
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Storage
|
||||
|
|
@ -8,21 +9,24 @@ namespace Flow.Launcher.Storage
|
|||
// todo this class is not thread safe.... but used from multiple threads.
|
||||
public class TopMostRecord
|
||||
{
|
||||
[JsonProperty]
|
||||
private Dictionary<string, Record> records = new Dictionary<string, Record>();
|
||||
/// <summary>
|
||||
/// You should not directly access this field
|
||||
/// <para>
|
||||
/// It is public due to System.Text.Json limitation in version 3.1
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// TODO: Set it to private
|
||||
public Dictionary<string, Record> records { get; set; } = new Dictionary<string, Record>();
|
||||
|
||||
internal bool IsTopMost(Result result)
|
||||
{
|
||||
if (records.Count == 0)
|
||||
if (records.Count == 0 || !records.ContainsKey(result.OriginQuery.RawQuery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// since this dictionary should be very small (or empty) going over it should be pretty fast.
|
||||
return records.Any(o => o.Value.Title == result.Title
|
||||
&& o.Value.SubTitle == result.SubTitle
|
||||
&& o.Value.PluginID == result.PluginID
|
||||
&& o.Key == result.OriginQuery.RawQuery);
|
||||
// since this dictionary should be very small (or empty) going over it should be pretty fast.
|
||||
return records[result.OriginQuery.RawQuery].Equals(result);
|
||||
}
|
||||
|
||||
internal void Remove(Result result)
|
||||
|
|
@ -54,5 +58,12 @@ namespace Flow.Launcher.Storage
|
|||
public string Title { get; set; }
|
||||
public string SubTitle { get; set; }
|
||||
public string PluginID { get; set; }
|
||||
|
||||
public bool Equals(Result r)
|
||||
{
|
||||
return Title == r.Title
|
||||
&& SubTitle == r.SubTitle
|
||||
&& PluginID == r.PluginID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
|
|
@ -7,15 +7,27 @@ namespace Flow.Launcher.Storage
|
|||
{
|
||||
public class UserSelectedRecord
|
||||
{
|
||||
[JsonProperty]
|
||||
private Dictionary<string, int> records = new Dictionary<string, int>();
|
||||
/// <summary>
|
||||
/// You should not directly access this field
|
||||
/// <para>
|
||||
/// It is public due to System.Text.Json limitation in version 3.1
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// TODO: Set it to private
|
||||
[JsonPropertyName("records")]
|
||||
public Dictionary<string, int> records { get; set; }
|
||||
|
||||
public UserSelectedRecord()
|
||||
{
|
||||
records = new Dictionary<string, int>();
|
||||
}
|
||||
|
||||
public void Add(Result result)
|
||||
{
|
||||
var key = result.ToString();
|
||||
if (records.TryGetValue(key, out int value))
|
||||
if (records.ContainsKey(key))
|
||||
{
|
||||
records[key] = value + 1;
|
||||
records[key]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
63
Flow.Launcher/Themes/BlurBlack Darker.xaml
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<system:Boolean x:Key="ThemeBlurEnabled">True</system:Boolean>
|
||||
|
||||
<Style x:Key="QueryBoxStyle" BasedOn="{StaticResource BaseQueryBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Foreground" Value="#FFFFFFFF" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Foreground" Value="LightGray" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="WindowBorderStyle" BasedOn="{StaticResource BaseWindowBorderStyle}" TargetType="{x:Type Border}">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="Black" Opacity="0.6"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="WindowStyle" BasedOn="{StaticResource BaseWindowStyle}" TargetType="{x:Type Window}">
|
||||
<Setter Property="Width" Value="750" /> <!-- This is used to set the blur width only -->
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="Black" Opacity="0.6"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PendingLineStyle" BasedOn="{StaticResource BasePendingLineStyle}" TargetType="{x:Type Line}">
|
||||
</Style>
|
||||
|
||||
<!-- Item Style -->
|
||||
<Style x:Key="ItemTitleStyle" BasedOn="{StaticResource BaseItemTitleStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Margin" Value="0, -10"/>
|
||||
<Setter Property="Foreground" Value="#FFFFFFFF"/>
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleStyle" BasedOn="{StaticResource BaseItemSubTitleStyle}" TargetType="{x:Type TextBlock}" >
|
||||
<Setter Property="Foreground" Value="#FFFFFFFF"/>
|
||||
</Style>
|
||||
<Style x:Key="ItemTitleSelectedStyle" BasedOn="{StaticResource BaseItemTitleSelectedStyle}" TargetType="{x:Type TextBlock}" >
|
||||
<Setter Property="Margin" Value="0, -10"/>
|
||||
<Setter Property="Foreground" Value="#FFFFFFFF"/>
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleSelectedStyle" BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}" TargetType="{x:Type TextBlock}" >
|
||||
<Setter Property="Foreground" Value="#FFFFFFFF"/>
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#356ef3</SolidColorBrush>
|
||||
|
||||
<!-- button style in the middle of the scrollbar -->
|
||||
<Style x:Key="ThumbStyle" BasedOn="{StaticResource BaseThumbStyle}" TargetType="{x:Type Thumb}">
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ScrollBarStyle" BasedOn="{StaticResource BaseScrollBarStyle}" TargetType="{x:Type ScrollBar}">
|
||||
<Setter Property="Background" Value="#a0a0a0"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
<Style x:Key="WindowBorderStyle" BasedOn="{StaticResource BaseWindowBorderStyle}" TargetType="{x:Type Border}">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="White" Opacity="0.1"/>
|
||||
<SolidColorBrush Color="White" Opacity="0.5"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
<Setter Property="Width" Value="750" /> <!-- This is used to set the blur width only -->
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="White" Opacity="0.1"/>
|
||||
<SolidColorBrush Color="White" Opacity="0.5"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
|
|
|||
|
|
@ -4,21 +4,19 @@
|
|||
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style x:Key="QueryBoxStyle" BasedOn="{StaticResource BaseQueryBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#EDEDED" />
|
||||
<Setter Property="Background" Value="LightGray" />
|
||||
<Setter Property="Foreground" Value="#222222" />
|
||||
<Setter Property="FontSize" Value="38" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#EDEDED" />
|
||||
<Setter Property="Foreground" Value="#222222" />
|
||||
<Setter Property="FontSize" Value="38" />
|
||||
<Setter Property="Background" Value="LightGray" />
|
||||
<Setter Property="Foreground" Value="#6E6E6E" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="WindowBorderStyle" BasedOn="{StaticResource BaseWindowBorderStyle}" TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderBrush" Value="#B0B0B0" />
|
||||
<Setter Property="BorderBrush" Value="LightGray" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Background" Value="#EDEDED"></Setter>
|
||||
<Setter Property="Background" Value="LightGray"></Setter>
|
||||
</Style>
|
||||
<Style x:Key="WindowStyle" TargetType="{x:Type Window}" BasedOn="{StaticResource BaseWindowStyle}" >
|
||||
</Style>
|
||||
|
|
@ -31,15 +29,15 @@
|
|||
<Setter Property="Foreground" Value="#A6A6A6" />
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleStyle" BasedOn="{StaticResource BaseItemSubTitleStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#A6A6A6" />
|
||||
<Setter Property="Foreground" Value="#6B6B6B" />
|
||||
</Style>
|
||||
<Style x:Key="ItemTitleSelectedStyle" BasedOn="{StaticResource BaseItemTitleSelectedStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#FFFFF8" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleSelectedStyle" BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#ffffff" />
|
||||
<Setter Property="Foreground" Value="#EDEDED" />
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#00AAF6</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#787878</SolidColorBrush>
|
||||
<Style x:Key="ThumbStyle" BasedOn="{StaticResource BaseThumbStyle}" TargetType="{x:Type Thumb}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
|
|
|
|||
|
|
@ -6,19 +6,19 @@
|
|||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Style x:Key="QueryBoxStyle" BasedOn="{StaticResource BaseQueryBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#EBEBEB"/>
|
||||
<Setter Property="Background" Value="White "/>
|
||||
<Setter Property="Foreground" Value="#000000" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#EBEBEB"/>
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="Foreground" Value="#000000" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="WindowBorderStyle" BasedOn="{StaticResource BaseWindowBorderStyle}" TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderBrush" Value="#AAAAAA" />
|
||||
<Setter Property="BorderThickness" Value="5" />
|
||||
<Setter Property="Background" Value="#ffffff"></Setter>
|
||||
<Setter Property="BorderBrush" Value="LightGray" />
|
||||
<Setter Property="BorderThickness" Value="1"></Setter>
|
||||
<Setter Property="Background" Value="White"></Setter>
|
||||
</Style>
|
||||
<Style x:Key="WindowStyle" TargetType="{x:Type Window}" BasedOn="{StaticResource BaseWindowStyle}" >
|
||||
</Style>
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
<Style x:Key="ItemSubTitleSelectedStyle" BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#F6F6FF" />
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#3875D7</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#909090</SolidColorBrush>
|
||||
|
||||
<!-- button style in the middle of the scrollbar -->
|
||||
<Style x:Key="ThumbStyle" BasedOn="{StaticResource BaseThumbStyle}" TargetType="{x:Type Thumb}">
|
||||
|
|
|
|||
56
Flow.Launcher/Themes/Nord Darker.xaml
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style x:Key="QueryBoxStyle" BasedOn="{StaticResource BaseQueryBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#2e3440" />
|
||||
<Setter Property="Foreground" Value="#eceff4" />
|
||||
<Setter Property="FontSize" Value="30" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#2e3440" />
|
||||
<Setter Property="Foreground" Value="#eceff4" />
|
||||
<Setter Property="FontSize" Value="30" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="WindowBorderStyle" BasedOn="{StaticResource BaseWindowBorderStyle}" TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderBrush" Value="#4c566a" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Background" Value="#2e3440"></Setter>
|
||||
</Style>
|
||||
<Style x:Key="WindowStyle" TargetType="{x:Type Window}" BasedOn="{StaticResource BaseWindowStyle}" >
|
||||
</Style>
|
||||
<Style x:Key="PendingLineStyle" BasedOn="{StaticResource BasePendingLineStyle}" TargetType="{x:Type Line}" />
|
||||
<Style x:Key="ItemTitleStyle" BasedOn="{StaticResource BaseItemTitleStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#e5e9f0" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
</Style>
|
||||
<Style x:Key="ItemNumberStyle" BasedOn="{StaticResource BaseItemNumberStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#A6A6A6" />
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleStyle" BasedOn="{StaticResource BaseItemSubTitleStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#d8dee9" />
|
||||
</Style>
|
||||
<Style x:Key="ItemTitleSelectedStyle" BasedOn="{StaticResource BaseItemTitleSelectedStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#2e3440" />
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleSelectedStyle" BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#4c566a" />
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#5e81ac</SolidColorBrush>
|
||||
<Style x:Key="ThumbStyle" BasedOn="{StaticResource BaseThumbStyle}" TargetType="{x:Type Thumb}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Thumb}">
|
||||
<Border CornerRadius="2" DockPanel.Dock="Right" Background="#4c566a" BorderBrush="Transparent" BorderThickness="0" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ScrollBarStyle" BasedOn="{StaticResource BaseScrollBarStyle}" TargetType="{x:Type ScrollBar}" >
|
||||
<Setter Property="Width" Value="3"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
56
Flow.Launcher/Themes/Nord.xaml
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style x:Key="QueryBoxStyle" BasedOn="{StaticResource BaseQueryBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#4c566a" />
|
||||
<Setter Property="Foreground" Value="#eceff4" />
|
||||
<Setter Property="FontSize" Value="30" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#4c566a" />
|
||||
<Setter Property="Foreground" Value="#eceff4" />
|
||||
<Setter Property="FontSize" Value="30" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="WindowBorderStyle" BasedOn="{StaticResource BaseWindowBorderStyle}" TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderBrush" Value="#2e3440" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Background" Value="#4c566a"></Setter>
|
||||
</Style>
|
||||
<Style x:Key="WindowStyle" TargetType="{x:Type Window}" BasedOn="{StaticResource BaseWindowStyle}" >
|
||||
</Style>
|
||||
<Style x:Key="PendingLineStyle" BasedOn="{StaticResource BasePendingLineStyle}" TargetType="{x:Type Line}" />
|
||||
<Style x:Key="ItemTitleStyle" BasedOn="{StaticResource BaseItemTitleStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#e5e9f0" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
</Style>
|
||||
<Style x:Key="ItemNumberStyle" BasedOn="{StaticResource BaseItemNumberStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#A6A6A6" />
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleStyle" BasedOn="{StaticResource BaseItemSubTitleStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#d8dee9" />
|
||||
</Style>
|
||||
<Style x:Key="ItemTitleSelectedStyle" BasedOn="{StaticResource BaseItemTitleSelectedStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#2e3440" />
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleSelectedStyle" BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#4c566a" />
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#5e81ac</SolidColorBrush>
|
||||
<Style x:Key="ThumbStyle" BasedOn="{StaticResource BaseThumbStyle}" TargetType="{x:Type Thumb}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Thumb}">
|
||||
<Border CornerRadius="2" DockPanel.Dock="Right" Background="#2e3440" BorderBrush="Transparent" BorderThickness="0" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ScrollBarStyle" BasedOn="{StaticResource BaseScrollBarStyle}" TargetType="{x:Type ScrollBar}" >
|
||||
<Setter Property="Width" Value="3"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using NHotkey;
|
||||
|
|
@ -19,8 +18,7 @@ using Flow.Launcher.Infrastructure.UserSettings;
|
|||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.Storage;
|
||||
using System.Windows.Media;
|
||||
using Flow.Launcher.Infrastructure.Image;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
|
|
@ -48,6 +46,9 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
private readonly Internationalization _translator = InternationalizationManager.Instance;
|
||||
|
||||
private BufferBlock<ResultsForUpdate> _resultsUpdateQueue;
|
||||
private Task _resultsViewUpdateTask;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
|
@ -74,6 +75,7 @@ namespace Flow.Launcher.ViewModel
|
|||
_selectedResults = Results;
|
||||
|
||||
InitializeKeyCommands();
|
||||
RegisterViewUpdate();
|
||||
RegisterResultsUpdatedEvent();
|
||||
|
||||
SetHotkey(_settings.Hotkey, OnHotkey);
|
||||
|
|
@ -81,6 +83,41 @@ namespace Flow.Launcher.ViewModel
|
|||
SetOpenResultModifiers();
|
||||
}
|
||||
|
||||
private void RegisterViewUpdate()
|
||||
{
|
||||
_resultsUpdateQueue = new BufferBlock<ResultsForUpdate>();
|
||||
_resultsViewUpdateTask =
|
||||
Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted);
|
||||
|
||||
async Task updateAction()
|
||||
{
|
||||
var queue = new Dictionary<string, ResultsForUpdate>();
|
||||
while (await _resultsUpdateQueue.OutputAvailableAsync())
|
||||
{
|
||||
queue.Clear();
|
||||
await Task.Delay(20);
|
||||
while (_resultsUpdateQueue.TryReceive(out var item))
|
||||
{
|
||||
if (!item.Token.IsCancellationRequested)
|
||||
queue[item.ID] = item;
|
||||
}
|
||||
|
||||
UpdateResultView(queue.Values);
|
||||
}
|
||||
};
|
||||
|
||||
void continueAction(Task t)
|
||||
{
|
||||
#if DEBUG
|
||||
throw t.Exception;
|
||||
#else
|
||||
Log.Error($"Error happen in task dealing with viewupdate for results. {t.Exception}");
|
||||
_resultsViewUpdateTask =
|
||||
Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterResultsUpdatedEvent()
|
||||
{
|
||||
foreach (var pair in PluginManager.GetPluginsForInterface<IResultUpdated>())
|
||||
|
|
@ -88,11 +125,11 @@ namespace Flow.Launcher.ViewModel
|
|||
var plugin = (IResultUpdated)pair.Plugin;
|
||||
plugin.ResultsUpdated += (s, e) =>
|
||||
{
|
||||
Task.Run(() =>
|
||||
if (e.Query.RawQuery == QueryText) // TODO: allow cancellation
|
||||
{
|
||||
PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query);
|
||||
UpdateResultView(e.Results, pair.Metadata, e.Query);
|
||||
}, _updateToken);
|
||||
_resultsUpdateQueue.Post(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -112,25 +149,13 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
});
|
||||
|
||||
SelectNextItemCommand = new RelayCommand(_ =>
|
||||
{
|
||||
SelectedResults.SelectNextResult();
|
||||
});
|
||||
SelectNextItemCommand = new RelayCommand(_ => { SelectedResults.SelectNextResult(); });
|
||||
|
||||
SelectPrevItemCommand = new RelayCommand(_ =>
|
||||
{
|
||||
SelectedResults.SelectPrevResult();
|
||||
});
|
||||
SelectPrevItemCommand = new RelayCommand(_ => { SelectedResults.SelectPrevResult(); });
|
||||
|
||||
SelectNextPageCommand = new RelayCommand(_ =>
|
||||
{
|
||||
SelectedResults.SelectNextPage();
|
||||
});
|
||||
SelectNextPageCommand = new RelayCommand(_ => { SelectedResults.SelectNextPage(); });
|
||||
|
||||
SelectPrevPageCommand = new RelayCommand(_ =>
|
||||
{
|
||||
SelectedResults.SelectPrevPage();
|
||||
});
|
||||
SelectPrevPageCommand = new RelayCommand(_ => { SelectedResults.SelectPrevPage(); });
|
||||
|
||||
SelectFirstResultCommand = new RelayCommand(_ => SelectedResults.SelectFirstResult());
|
||||
|
||||
|
|
@ -197,6 +222,25 @@ namespace Flow.Launcher.ViewModel
|
|||
SelectedResults = Results;
|
||||
}
|
||||
});
|
||||
|
||||
ReloadPluginDataCommand = new RelayCommand(_ =>
|
||||
{
|
||||
var msg = new Msg { Owner = Application.Current.MainWindow };
|
||||
|
||||
MainWindowVisibility = Visibility.Collapsed;
|
||||
|
||||
PluginManager
|
||||
.ReloadData()
|
||||
.ContinueWith(_ =>
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
msg.Show(
|
||||
InternationalizationManager.Instance.GetTranslation("success"),
|
||||
InternationalizationManager.Instance.GetTranslation("completedSuccessfully"),
|
||||
"");
|
||||
}))
|
||||
.ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
@ -208,9 +252,10 @@ namespace Flow.Launcher.ViewModel
|
|||
public ResultsViewModel History { get; private set; }
|
||||
|
||||
private string _queryText;
|
||||
|
||||
public string QueryText
|
||||
{
|
||||
get { return _queryText; }
|
||||
get => _queryText;
|
||||
set
|
||||
{
|
||||
_queryText = value;
|
||||
|
|
@ -228,10 +273,12 @@ namespace Flow.Launcher.ViewModel
|
|||
QueryTextCursorMovedToEnd = true;
|
||||
QueryText = queryText;
|
||||
}
|
||||
|
||||
public bool LastQuerySelected { get; set; }
|
||||
public bool QueryTextCursorMovedToEnd { get; set; }
|
||||
|
||||
private ResultsViewModel _selectedResults;
|
||||
|
||||
private ResultsViewModel SelectedResults
|
||||
{
|
||||
get { return _selectedResults; }
|
||||
|
|
@ -263,6 +310,7 @@ namespace Flow.Launcher.ViewModel
|
|||
QueryText = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
_selectedResults.Visbility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
|
@ -281,10 +329,11 @@ namespace Flow.Launcher.ViewModel
|
|||
public ICommand LoadContextMenuCommand { get; set; }
|
||||
public ICommand LoadHistoryCommand { get; set; }
|
||||
public ICommand OpenResultCommand { get; set; }
|
||||
public ICommand ReloadPluginDataCommand { get; set; }
|
||||
|
||||
public string OpenResultCommandModifiers { get; private set; }
|
||||
|
||||
public ImageSource Image => ImageLoader.Load(Constant.QueryTextBoxIconImagePath);
|
||||
public string Image => Constant.QueryTextBoxIconImagePath;
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
@ -322,9 +371,20 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
var filtered = results.Where
|
||||
(
|
||||
r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet()
|
||||
|| StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet()
|
||||
).ToList();
|
||||
r =>
|
||||
{
|
||||
var match = StringMatcher.FuzzySearch(query, r.Title);
|
||||
if (!match.IsSearchPrecisionScoreMet())
|
||||
{
|
||||
match = StringMatcher.FuzzySearch(query, r.SubTitle);
|
||||
}
|
||||
|
||||
if (!match.IsSearchPrecisionScoreMet()) return false;
|
||||
|
||||
r.Score = match.Score;
|
||||
return true;
|
||||
|
||||
}).ToList();
|
||||
ContextMenu.AddResults(filtered, id);
|
||||
}
|
||||
else
|
||||
|
|
@ -378,90 +438,128 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
private void QueryResults()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(QueryText))
|
||||
_updateSource?.Cancel();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(QueryText))
|
||||
{
|
||||
_updateSource?.Cancel();
|
||||
var currentUpdateSource = new CancellationTokenSource();
|
||||
_updateSource = currentUpdateSource;
|
||||
var currentCancellationToken = _updateSource.Token;
|
||||
_updateToken = currentCancellationToken;
|
||||
Results.Clear();
|
||||
Results.Visbility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
_isQueryRunning = true;
|
||||
var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins);
|
||||
if (query != null)
|
||||
_updateSource?.Dispose();
|
||||
|
||||
var currentUpdateSource = new CancellationTokenSource();
|
||||
_updateSource = currentUpdateSource;
|
||||
var currentCancellationToken = _updateSource.Token;
|
||||
_updateToken = currentCancellationToken;
|
||||
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
_isQueryRunning = true;
|
||||
|
||||
var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins);
|
||||
|
||||
// handle the exclusiveness of plugin using action keyword
|
||||
RemoveOldQueryResults(query);
|
||||
|
||||
_lastQuery = query;
|
||||
|
||||
var plugins = PluginManager.ValidPluginsForQuery(query);
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
// handle the exclusiveness of plugin using action keyword
|
||||
RemoveOldQueryResults(query);
|
||||
if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign)
|
||||
{
|
||||
// Wait 45 millisecond for query change in global query
|
||||
// if query changes, return so that it won't be calculated
|
||||
await Task.Delay(45, currentCancellationToken);
|
||||
if (currentCancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
}
|
||||
|
||||
_lastQuery = query;
|
||||
Task.Delay(200, currentCancellationToken).ContinueWith(_ =>
|
||||
{ // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet
|
||||
if (currentUpdateSource == _updateSource && _isQueryRunning)
|
||||
_ = Task.Delay(200, currentCancellationToken).ContinueWith(_ =>
|
||||
{
|
||||
// start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet
|
||||
if (!currentCancellationToken.IsCancellationRequested && _isQueryRunning)
|
||||
{
|
||||
ProgressBarVisibility = Visibility.Visible;
|
||||
}
|
||||
}, currentCancellationToken);
|
||||
|
||||
var plugins = PluginManager.ValidPluginsForQuery(query);
|
||||
Task.Run(() =>
|
||||
Task[] tasks = new Task[plugins.Count];
|
||||
try
|
||||
{
|
||||
// so looping will stop once it was cancelled
|
||||
var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken };
|
||||
try
|
||||
for (var i = 0; i < plugins.Count; i++)
|
||||
{
|
||||
Parallel.ForEach(plugins, parallelOptions, plugin =>
|
||||
if (!plugins[i].Metadata.Disabled)
|
||||
{
|
||||
if (!plugin.Metadata.Disabled)
|
||||
{
|
||||
var results = PluginManager.QueryForPlugin(plugin, query);
|
||||
UpdateResultView(results, plugin.Metadata, query);
|
||||
}
|
||||
});
|
||||
tasks[i] = QueryTask(plugins[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
tasks[i] = Task.CompletedTask; // Avoid Null
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
|
||||
// this should happen once after all queries are done so progress bar should continue
|
||||
// until the end of all querying
|
||||
_isQueryRunning = false;
|
||||
if (currentUpdateSource == _updateSource)
|
||||
{ // update to hidden if this is still the current query
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
}
|
||||
}, currentCancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Results.Clear();
|
||||
Results.Visbility = Visibility.Collapsed;
|
||||
}
|
||||
// Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
if (currentCancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
// this should happen once after all queries are done so progress bar should continue
|
||||
// until the end of all querying
|
||||
_isQueryRunning = false;
|
||||
if (!currentCancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// update to hidden if this is still the current query
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
// Local function
|
||||
async Task QueryTask(PluginPair plugin)
|
||||
{
|
||||
// Since it is wrapped within a Task.Run, the synchronous context is null
|
||||
// Task.Yield will force it to run in ThreadPool
|
||||
await Task.Yield();
|
||||
|
||||
var results = await PluginManager.QueryForPlugin(plugin, query, currentCancellationToken);
|
||||
if (!currentCancellationToken.IsCancellationRequested && results != null)
|
||||
_resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, query,
|
||||
currentCancellationToken));
|
||||
}
|
||||
}, currentCancellationToken)
|
||||
.ContinueWith(t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception),
|
||||
TaskContinuationOptions.OnlyOnFaulted);
|
||||
}
|
||||
|
||||
|
||||
private void RemoveOldQueryResults(Query query)
|
||||
{
|
||||
string lastKeyword = _lastQuery.ActionKeyword;
|
||||
|
||||
string keyword = query.ActionKeyword;
|
||||
if (string.IsNullOrEmpty(lastKeyword))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(keyword))
|
||||
{
|
||||
Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata);
|
||||
Results.KeepResultsFor(PluginManager.NonGlobalPlugins[keyword].Metadata);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(keyword))
|
||||
{
|
||||
Results.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata);
|
||||
Results.KeepResultsExcept(PluginManager.NonGlobalPlugins[lastKeyword].Metadata);
|
||||
}
|
||||
else if (lastKeyword != keyword)
|
||||
{
|
||||
Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata);
|
||||
Results.KeepResultsFor(PluginManager.NonGlobalPlugins[keyword].Metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -499,6 +597,7 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
|
|
@ -538,12 +637,12 @@ namespace Flow.Launcher.ViewModel
|
|||
return selected;
|
||||
}
|
||||
|
||||
|
||||
private bool HistorySelected()
|
||||
{
|
||||
var selected = SelectedResults == History;
|
||||
return selected;
|
||||
}
|
||||
|
||||
#region Hotkey
|
||||
|
||||
private void SetHotkey(string hotkeyStr, EventHandler<HotkeyEventArgs> action)
|
||||
|
|
@ -562,7 +661,8 @@ namespace Flow.Launcher.ViewModel
|
|||
catch (Exception)
|
||||
{
|
||||
string errorMsg =
|
||||
string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr);
|
||||
string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"),
|
||||
hotkeyStr);
|
||||
MessageBox.Show(errorMsg);
|
||||
}
|
||||
}
|
||||
|
|
@ -612,7 +712,6 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
if (!ShouldIgnoreHotkeys())
|
||||
{
|
||||
|
||||
if (_settings.LastQueryMode == LastQueryMode.Empty)
|
||||
{
|
||||
ChangeQueryText(string.Empty);
|
||||
|
|
@ -666,29 +765,47 @@ namespace Flow.Launcher.ViewModel
|
|||
/// <summary>
|
||||
/// To avoid deadlock, this method should not called from main thread
|
||||
/// </summary>
|
||||
public void UpdateResultView(List<Result> list, PluginMetadata metadata, Query originQuery)
|
||||
public void UpdateResultView(IEnumerable<ResultsForUpdate> resultsForUpdates)
|
||||
{
|
||||
foreach (var result in list)
|
||||
if (!resultsForUpdates.Any())
|
||||
return;
|
||||
CancellationToken token;
|
||||
|
||||
try
|
||||
{
|
||||
if (_topMostRecord.IsTopMost(result))
|
||||
// Don't know why sometimes even resultsForUpdates is empty, the method won't return;
|
||||
token = resultsForUpdates.Select(r => r.Token).Distinct().SingleOrDefault();
|
||||
}
|
||||
#if DEBUG
|
||||
catch
|
||||
{
|
||||
throw new ArgumentException("Unacceptable token");
|
||||
}
|
||||
#else
|
||||
catch
|
||||
{
|
||||
token = default;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
foreach (var metaResults in resultsForUpdates)
|
||||
{
|
||||
foreach (var result in metaResults.Results)
|
||||
{
|
||||
result.Score = int.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Score += _userSelectedRecord.GetSelectedCount(result) * 5;
|
||||
if (_topMostRecord.IsTopMost(result))
|
||||
{
|
||||
result.Score = int.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
var priorityScore = metaResults.Metadata.Priority * 150;
|
||||
result.Score += _userSelectedRecord.GetSelectedCount(result) * 5 + priorityScore;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (originQuery.RawQuery == _lastQuery.RawQuery)
|
||||
{
|
||||
Results.AddResults(list, metadata.ID);
|
||||
}
|
||||
|
||||
if (Results.Visbility != Visibility.Visible && list.Count > 0)
|
||||
{
|
||||
Results.Visbility = Visibility.Visible;
|
||||
}
|
||||
Results.AddResults(resultsForUpdates, token);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace Flow.Launcher.ViewModel
|
|||
public string InitilizaTime => PluginPair.Metadata.InitTime.ToString() + "ms";
|
||||
public string QueryTime => PluginPair.Metadata.AvgQueryTime + "ms";
|
||||
public string ActionKeywordsText => string.Join(Query.ActionKeywordSeperater, PluginPair.Metadata.ActionKeywords);
|
||||
public int Priority => PluginPair.Metadata.Priority;
|
||||
|
||||
public void ChangeActionKeyword(string newActionKeyword, string oldActionKeyword)
|
||||
{
|
||||
|
|
@ -34,6 +35,12 @@ namespace Flow.Launcher.ViewModel
|
|||
OnPropertyChanged(nameof(ActionKeywordsText));
|
||||
}
|
||||
|
||||
public void ChangePriority(int newPriority)
|
||||
{
|
||||
PluginPair.Metadata.Priority = newPriority;
|
||||
OnPropertyChanged(nameof(Priority));
|
||||
}
|
||||
|
||||
public bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Image;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
|
|
@ -12,9 +11,9 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
public class ResultViewModel : BaseModel
|
||||
{
|
||||
public class LazyAsync<T> : Lazy<Task<T>>
|
||||
public class LazyAsync<T> : Lazy<ValueTask<T>>
|
||||
{
|
||||
private T defaultValue;
|
||||
private readonly T defaultValue;
|
||||
|
||||
private readonly Action _updateCallback;
|
||||
public new T Value
|
||||
|
|
@ -23,21 +22,27 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
if (!IsValueCreated)
|
||||
{
|
||||
base.Value.ContinueWith(_ =>
|
||||
{
|
||||
_updateCallback();
|
||||
});
|
||||
_ = Exercute(); // manually use callback strategy
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (!base.Value.IsCompleted || base.Value.IsFaulted)
|
||||
|
||||
if (!base.Value.IsCompletedSuccessfully)
|
||||
return defaultValue;
|
||||
|
||||
return base.Value.Result;
|
||||
|
||||
// If none of the variables captured by the local function are captured by other lambdas,
|
||||
// the compiler can avoid heap allocations.
|
||||
async ValueTask Exercute()
|
||||
{
|
||||
await base.Value.ConfigureAwait(false);
|
||||
_updateCallback();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public LazyAsync(Func<Task<T>> factory, T defaultValue, Action updateCallback) : base(factory)
|
||||
public LazyAsync(Func<ValueTask<T>> factory, T defaultValue, Action updateCallback) : base(factory)
|
||||
{
|
||||
if (defaultValue != null)
|
||||
{
|
||||
|
|
@ -55,13 +60,13 @@ namespace Flow.Launcher.ViewModel
|
|||
Result = result;
|
||||
|
||||
Image = new LazyAsync<ImageSource>(
|
||||
SetImage,
|
||||
SetImage,
|
||||
ImageLoader.DefaultImage,
|
||||
() =>
|
||||
{
|
||||
OnPropertyChanged(nameof(Image));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Settings = settings;
|
||||
}
|
||||
|
|
@ -82,7 +87,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
public LazyAsync<ImageSource> Image { get; set; }
|
||||
|
||||
private async Task<ImageSource> SetImage()
|
||||
private async ValueTask<ImageSource> SetImage()
|
||||
{
|
||||
var imagePath = Result.IcoPath;
|
||||
if (string.IsNullOrEmpty(imagePath) && Result.Icon != null)
|
||||
|
|
@ -94,19 +99,15 @@ namespace Flow.Launcher.ViewModel
|
|||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e);
|
||||
imagePath = Constant.MissingImgIcon;
|
||||
return ImageLoader.DefaultImage;
|
||||
}
|
||||
}
|
||||
|
||||
if (ImageLoader.CacheContainImage(imagePath))
|
||||
{
|
||||
// will get here either when icoPath has value\icon delegate is null\when had exception in delegate
|
||||
return ImageLoader.Load(imagePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await Task.Run(() => ImageLoader.Load(imagePath));
|
||||
}
|
||||
|
||||
return await Task.Run(() => ImageLoader.Load(imagePath));
|
||||
}
|
||||
|
||||
public Result Result { get; }
|
||||
|
|
|
|||
35
Flow.Launcher/ViewModel/ResultsForUpdate.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using Flow.Launcher.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
public class ResultsForUpdate
|
||||
{
|
||||
public List<Result> Results { get; }
|
||||
|
||||
public PluginMetadata Metadata { get; }
|
||||
public string ID { get; }
|
||||
|
||||
public Query Query { get; }
|
||||
public CancellationToken Token { get; }
|
||||
|
||||
public ResultsForUpdate(List<Result> results, string resultID, CancellationToken token)
|
||||
{
|
||||
Results = results;
|
||||
ID = resultID;
|
||||
Token = token;
|
||||
}
|
||||
|
||||
public ResultsForUpdate(List<Result> results, PluginMetadata metadata, Query query, CancellationToken token)
|
||||
{
|
||||
Results = results;
|
||||
Metadata = metadata;
|
||||
Query = query;
|
||||
Token = token;
|
||||
ID = metadata.ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
|
@ -17,7 +20,6 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
public ResultCollection Results { get; }
|
||||
|
||||
private readonly object _addResultsLock = new object();
|
||||
private readonly object _collectionLock = new object();
|
||||
private readonly Settings _settings;
|
||||
private int MaxResults => _settings?.MaxResultsToShow ?? 6;
|
||||
|
|
@ -116,17 +118,20 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
public void Clear()
|
||||
{
|
||||
Results.Clear();
|
||||
lock (_collectionLock)
|
||||
Results.RemoveAll();
|
||||
}
|
||||
|
||||
public void RemoveResultsExcept(PluginMetadata metadata)
|
||||
public void KeepResultsFor(PluginMetadata metadata)
|
||||
{
|
||||
Results.RemoveAll(r => r.Result.PluginID != metadata.ID);
|
||||
lock (_collectionLock)
|
||||
Results.Update(Results.Where(r => r.Result.PluginID == metadata.ID).ToList());
|
||||
}
|
||||
|
||||
public void RemoveResultsFor(PluginMetadata metadata)
|
||||
public void KeepResultsExcept(PluginMetadata metadata)
|
||||
{
|
||||
Results.RemoveAll(r => r.Result.PluginID == metadata.ID);
|
||||
lock (_collectionLock)
|
||||
Results.Update(Results.Where(r => r.Result.PluginID != metadata.ID).ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -134,70 +139,73 @@ namespace Flow.Launcher.ViewModel
|
|||
/// </summary>
|
||||
public void AddResults(List<Result> newRawResults, string resultId)
|
||||
{
|
||||
lock (_addResultsLock)
|
||||
var newResults = NewResults(newRawResults, resultId);
|
||||
|
||||
UpdateResults(newResults);
|
||||
}
|
||||
/// <summary>
|
||||
/// To avoid deadlock, this method should not called from main thread
|
||||
/// </summary>
|
||||
public void AddResults(IEnumerable<ResultsForUpdate> resultsForUpdates, CancellationToken token)
|
||||
{
|
||||
var newResults = NewResults(resultsForUpdates);
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
UpdateResults(newResults, token);
|
||||
}
|
||||
|
||||
private void UpdateResults(List<ResultViewModel> newResults, CancellationToken token = default)
|
||||
{
|
||||
lock (_collectionLock)
|
||||
{
|
||||
var newResults = NewResults(newRawResults, resultId);
|
||||
|
||||
// update UI in one run, so it can avoid UI flickering
|
||||
Results.Update(newResults);
|
||||
Results.Update(newResults, token);
|
||||
if (Results.Any())
|
||||
SelectedItem = Results[0];
|
||||
}
|
||||
|
||||
if (Results.Count > 0)
|
||||
{
|
||||
switch (Visbility)
|
||||
{
|
||||
case Visibility.Collapsed when Results.Count > 0:
|
||||
Margin = new Thickness { Top = 8 };
|
||||
SelectedIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Visbility = Visibility.Visible;
|
||||
break;
|
||||
case Visibility.Visible when Results.Count == 0:
|
||||
Margin = new Thickness { Top = 0 };
|
||||
}
|
||||
Visbility = Visibility.Collapsed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private List<ResultViewModel> NewResults(List<Result> newRawResults, string resultId)
|
||||
{
|
||||
var results = Results.ToList();
|
||||
var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).ToList();
|
||||
var oldResults = results.Where(r => r.Result.PluginID == resultId).ToList();
|
||||
if (newRawResults.Count == 0)
|
||||
return Results.ToList();
|
||||
|
||||
// Find the same results in A (old results) and B (new newResults)
|
||||
var sameResults = oldResults
|
||||
.Where(t1 => newResults.Any(x => x.Result.Equals(t1.Result)))
|
||||
.ToList();
|
||||
var results = Results as IEnumerable<ResultViewModel>;
|
||||
|
||||
// remove result of relative complement of B in A
|
||||
foreach (var result in oldResults.Except(sameResults))
|
||||
{
|
||||
results.Remove(result);
|
||||
}
|
||||
var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings));
|
||||
|
||||
// update result with B's score and index position
|
||||
foreach (var sameResult in sameResults)
|
||||
{
|
||||
int oldIndex = results.IndexOf(sameResult);
|
||||
int oldScore = results[oldIndex].Result.Score;
|
||||
var newResult = newResults[newResults.IndexOf(sameResult)];
|
||||
int newScore = newResult.Result.Score;
|
||||
if (newScore != oldScore)
|
||||
{
|
||||
var oldResult = results[oldIndex];
|
||||
return results.Where(r => r.Result.PluginID != resultId)
|
||||
.Concat(newResults)
|
||||
.OrderByDescending(r => r.Result.Score)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
oldResult.Result.Score = newScore;
|
||||
oldResult.Result.OriginQuery = newResult.Result.OriginQuery;
|
||||
private List<ResultViewModel> NewResults(IEnumerable<ResultsForUpdate> resultsForUpdates)
|
||||
{
|
||||
if (!resultsForUpdates.Any())
|
||||
return Results.ToList();
|
||||
|
||||
results.RemoveAt(oldIndex);
|
||||
int newIndex = InsertIndexOf(newScore, results);
|
||||
results.Insert(newIndex, oldResult);
|
||||
}
|
||||
}
|
||||
var results = Results as IEnumerable<ResultViewModel>;
|
||||
|
||||
// insert result in relative complement of A in B
|
||||
foreach (var result in newResults.Except(sameResults))
|
||||
{
|
||||
int newIndex = InsertIndexOf(result.Result.Score, results);
|
||||
results.Insert(newIndex, result);
|
||||
}
|
||||
|
||||
return results;
|
||||
return results.Where(r => r != null && !resultsForUpdates.Any(u => u.Metadata.ID == r.Result.PluginID))
|
||||
.Concat(resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings)))
|
||||
.OrderByDescending(rv => rv.Result.Score)
|
||||
.ToList();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
|
@ -232,60 +240,78 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
#endregion
|
||||
|
||||
public class ResultCollection : ObservableCollection<ResultViewModel>
|
||||
public class ResultCollection : List<ResultViewModel>, INotifyCollectionChanged
|
||||
{
|
||||
private long editTime = 0;
|
||||
|
||||
public void RemoveAll(Predicate<ResultViewModel> predicate)
|
||||
private CancellationToken _token;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
|
||||
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
CheckReentrancy();
|
||||
CollectionChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
for (int i = Count - 1; i >= 0; i--)
|
||||
public void BulkAddAll(List<ResultViewModel> resultViews)
|
||||
{
|
||||
AddRange(resultViews);
|
||||
|
||||
// can return because the list will be cleared next time updated, which include a reset event
|
||||
if (_token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
// manually update event
|
||||
// wpf use directx / double buffered already, so just reset all won't cause ui flickering
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
private void AddAll(List<ResultViewModel> Items)
|
||||
{
|
||||
for (int i = 0; i < Items.Count; i++)
|
||||
{
|
||||
if (predicate(this[i]))
|
||||
{
|
||||
RemoveAt(i);
|
||||
}
|
||||
var item = Items[i];
|
||||
if (_token.IsCancellationRequested)
|
||||
return;
|
||||
Add(item);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, i));
|
||||
}
|
||||
}
|
||||
public void RemoveAll(int Capacity = 512)
|
||||
{
|
||||
Clear();
|
||||
if (this.Capacity > 8000 && Capacity < this.Capacity)
|
||||
this.Capacity = Capacity;
|
||||
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the results collection with new results, try to keep identical results
|
||||
/// </summary>
|
||||
/// <param name="newItems"></param>
|
||||
public void Update(List<ResultViewModel> newItems)
|
||||
public void Update(List<ResultViewModel> newItems, CancellationToken token = default)
|
||||
{
|
||||
int newCount = newItems.Count;
|
||||
int oldCount = Items.Count;
|
||||
int location = newCount > oldCount ? oldCount : newCount;
|
||||
_token = token;
|
||||
if (Count == 0 && newItems.Count == 0 || _token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < location; i++)
|
||||
if (editTime < 10 || newItems.Count < 30)
|
||||
{
|
||||
ResultViewModel oldResult = this[i];
|
||||
ResultViewModel newResult = newItems[i];
|
||||
if (!oldResult.Equals(newResult))
|
||||
{ // result is not the same update it in the current index
|
||||
this[i] = newResult;
|
||||
}
|
||||
else if (oldResult.Result.Score != newResult.Result.Score)
|
||||
{
|
||||
this[i].Result.Score = newResult.Result.Score;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (newCount >= oldCount)
|
||||
{
|
||||
for (int i = oldCount; i < newCount; i++)
|
||||
{
|
||||
Add(newItems[i]);
|
||||
}
|
||||
if (Count != 0) RemoveAll(newItems.Count);
|
||||
AddAll(newItems);
|
||||
editTime++;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = oldCount - 1; i >= newCount; i--)
|
||||
Clear();
|
||||
BulkAddAll(newItems);
|
||||
if (Capacity > 8000 && newItems.Count < 3000)
|
||||
{
|
||||
RemoveAt(i);
|
||||
Capacity = newItems.Count;
|
||||
}
|
||||
editTime++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ using Flow.Launcher.Infrastructure.Image;
|
|||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
|
|
@ -88,6 +89,7 @@ namespace Flow.Launcher.ViewModel
|
|||
var id = vm.PluginPair.Metadata.ID;
|
||||
|
||||
Settings.PluginSettings.Plugins[id].Disabled = vm.PluginPair.Metadata.Disabled;
|
||||
Settings.PluginSettings.Plugins[id].Priority = vm.Priority;
|
||||
}
|
||||
|
||||
PluginManager.Save();
|
||||
|
|
@ -152,7 +154,7 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
var precisionStrings = new List<string>();
|
||||
|
||||
var enumList = Enum.GetValues(typeof(StringMatcher.SearchPrecisionScore)).Cast<StringMatcher.SearchPrecisionScore>().ToList();
|
||||
var enumList = Enum.GetValues(typeof(SearchPrecisionScore)).Cast<SearchPrecisionScore>().ToList();
|
||||
|
||||
enumList.ForEach(x => precisionStrings.Add(x.ToString()));
|
||||
|
||||
|
|
@ -213,7 +215,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
#region plugin
|
||||
|
||||
public static string Plugin => "http://www.wox.one/plugin";
|
||||
public static string Plugin => @"https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest";
|
||||
public PluginViewModel SelectedPlugin { get; set; }
|
||||
|
||||
public IList<PluginViewModel> PluginViewModels
|
||||
|
|
@ -438,7 +440,7 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
public ImageSource ThemeImage => ImageLoader.Load(Constant.QueryTextBoxIconImagePath);
|
||||
public string ThemeImage => Constant.QueryTextBoxIconImagePath;
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
@ -450,7 +452,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
#region about
|
||||
|
||||
public string Github => _updater.GitHubRepository;
|
||||
public string Website => Constant.Website;
|
||||
public string ReleaseNotes => _updater.GitHubRepository + @"/releases/latest";
|
||||
public static string Version => Constant.Version;
|
||||
public string ActivatedTimes => string.Format(_translater.GetTranslation("about_activate_times"), Settings.ActivateTimes);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
|
||||
namespace Flow.Launcher.Plugin.BrowserBookmark.Commands
|
||||
{
|
||||
|
|
|
|||
|
|
@ -40,14 +40,6 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Images\bookmark.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Languages\en.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="x64\SQLite.Interop.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
|
@ -60,13 +52,16 @@
|
|||
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\tr.xaml">
|
||||
<Content Include="Languages\*.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\*.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
BIN
Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
|
@ -12,4 +12,6 @@
|
|||
<system:String x:Key="flowlauncher_plugin_browserbookmark_settings_newTab">New tab</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_browserbookmark_settings_setBrowserFromPath">Set browser from path:</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_browserbookmark_settings_choose">Choose</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_browserbookmark_copyurl_title">Copy url</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_browserbookmark_copyurl_subtitle">Copy the bookmark's url to clipboard</system:String>
|
||||
</ResourceDictionary>
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Plugin.BrowserBookmark.Commands;
|
||||
using Flow.Launcher.Plugin.BrowserBookmark.Models;
|
||||
|
|
@ -9,7 +12,7 @@ using Flow.Launcher.Plugin.SharedCommands;
|
|||
|
||||
namespace Flow.Launcher.Plugin.BrowserBookmark
|
||||
{
|
||||
public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, ISavable
|
||||
public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, ISavable, IContextMenu
|
||||
{
|
||||
private PluginInitContext context;
|
||||
|
||||
|
|
@ -60,7 +63,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
|
|||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
ContextData = new BookmarkAttributes { Url = c.Url }
|
||||
}).Where(r => r.Score > 0);
|
||||
return returnList.ToList();
|
||||
}
|
||||
|
|
@ -84,7 +88,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
|
|||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
ContextData = new BookmarkAttributes { Url = c.Url }
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
|
@ -115,5 +120,39 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
|
|||
{
|
||||
_storage.Save();
|
||||
}
|
||||
|
||||
public List<Result> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
return new List<Result>() {
|
||||
new Result
|
||||
{
|
||||
Title = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"),
|
||||
SubTitle = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_subtitle"),
|
||||
Action = _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetDataObject(((BookmarkAttributes)selectedResult.ContextData).Url);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = "Failed to set url in clipboard";
|
||||
Log.Exception("Main",message, e, "LoadContextMenus");
|
||||
|
||||
context.API.ShowMsg(message);
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
IcoPath = "Images\\copylink.png"
|
||||
}};
|
||||
}
|
||||
|
||||
internal class BookmarkAttributes
|
||||
{
|
||||
internal string Url { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Name": "Browser Bookmarks",
|
||||
"Description": "Search your browser bookmarks",
|
||||
"Author": "qianlifeng, Ioannis G.",
|
||||
"Version": "1.3.1",
|
||||
"Version": "1.4.0",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
|
|
@ -43,57 +44,14 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Core\Flow.Launcher.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Images\calculator.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\en.xaml">
|
||||
<Content Include="Languages\*.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\zh-cn.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\zh-tw.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\de.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\pl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\tr.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<Content Include="Images\*.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<system:String x:Key="flowlauncher_plugin_caculator_plugin_description">Spracúva matematické operácie.(Skúste 5*3-2 vo flowlauncheri)</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_calculator_not_a_number">Nie je číslo (NaN)</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_calculator_expression_not_complete">Nesprávny alebo neúplný výraz (Nezabudli ste na zátvorky?)</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_calculator_copy_number_to_clipboard">Kopírovať toto číslo do schránky</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_calculator_copy_number_to_clipboard">Kopírovať výsledok do schránky</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_calculator_output_decimal_seperator">Oddeľovač des. miest</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_calculator_output_decimal_seperator_help">Oddeľovač desatinných miest použitý vo výsledku.</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_calculator_decimal_seperator_use_system_locale">Použiť podľa systému</system:String>
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ namespace Flow.Launcher.Plugin.Caculator
|
|||
};
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Name": "Calculator",
|
||||
"Description": "Provide mathematical calculations.(Try 5*3-2 in Flow Launcher)",
|
||||
"Author": "cxfksword",
|
||||
"Version": "1.1.3",
|
||||
"Version": "1.1.4",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Caculator.dll",
|
||||
|
|
|
|||
|
|
@ -1,101 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<ProjectGuid>{F35190AA-4758-4D9E-A193-E3BDF6AD3567}</ProjectGuid>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Flow.Launcher.Plugin.Color</RootNamespace>
|
||||
<AssemblyName>Flow.Launcher.Plugin.Color</AssemblyName>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Color\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\..\Output\Release\Plugins\Flow.Launcher.Plugin.Color\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Images\color.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="plugin.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\en.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\zh-cn.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\zh-tw.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\de.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\pl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\tr.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
|
|
@ -1,8 +0,0 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_color_plugin_name">Farben</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_color_plugin_description">Stellt eine HEX-Farben Vorschau bereit. (Versuche #000 in Flow Launcher)</system:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_color_plugin_name">Colors</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_color_plugin_description">Allows to preview colors using hex values.(Try #000 in Flow Launcher)</system:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_color_plugin_name">Kolory</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_color_plugin_description">Podgląd kolorów po wpisaniu ich kodu szesnastkowego. (Spróbuj wpisać #000 w oknie Flow Launchera)</system:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_color_plugin_name">Farby</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_color_plugin_description">Zobrazuje náhľad farieb v HEX formáte. (Skúste #000 vo Flow Launcheri)</system:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_color_plugin_name">Renkler</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_color_plugin_description">Hex kodunu girdiğiniz renkleri görüntülemeye yarar.(#000 yazmayı deneyin)</system:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_color_plugin_name">颜色</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_color_plugin_description">提供在Flow Launcher查询hex颜色。(尝试在Flow Launcher中输入#000)</system:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_color_plugin_name">顏色</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_color_plugin_description">提供在 Flow Launcher 查詢 hex 顏色。(試著在 Flow Launcher 中輸入 #000)</system:String>
|
||||
</ResourceDictionary>
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Color
|
||||
{
|
||||
public sealed class ColorsPlugin : IPlugin, IPluginI18n
|
||||
{
|
||||
private string DIR_PATH = Path.Combine(Path.GetTempPath(), @"Plugins\Colors\");
|
||||
private PluginInitContext context;
|
||||
private const int IMG_SIZE = 32;
|
||||
|
||||
private DirectoryInfo ColorsDirectory { get; set; }
|
||||
|
||||
public ColorsPlugin()
|
||||
{
|
||||
if (!Directory.Exists(DIR_PATH))
|
||||
{
|
||||
ColorsDirectory = Directory.CreateDirectory(DIR_PATH);
|
||||
}
|
||||
else
|
||||
{
|
||||
ColorsDirectory = new DirectoryInfo(DIR_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
var raw = query.Search;
|
||||
if (!IsAvailable(raw)) return new List<Result>(0);
|
||||
try
|
||||
{
|
||||
var cached = Find(raw);
|
||||
if (cached.Length == 0)
|
||||
{
|
||||
var path = CreateImage(raw);
|
||||
return new List<Result>
|
||||
{
|
||||
new Result
|
||||
{
|
||||
Title = raw,
|
||||
IcoPath = path,
|
||||
Action = _ =>
|
||||
{
|
||||
Clipboard.SetText(raw);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return cached.Select(x => new Result
|
||||
{
|
||||
Title = raw,
|
||||
IcoPath = x.FullName,
|
||||
Action = _ =>
|
||||
{
|
||||
Clipboard.SetText(raw);
|
||||
return true;
|
||||
}
|
||||
}).ToList();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
// todo: log
|
||||
return new List<Result>(0);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsAvailable(string query)
|
||||
{
|
||||
// todo: rgb, names
|
||||
var length = query.Length - 1; // minus `#` sign
|
||||
return query.StartsWith("#") && (length == 3 || length == 6);
|
||||
}
|
||||
|
||||
public FileInfo[] Find(string name)
|
||||
{
|
||||
var file = string.Format("{0}.png", name.Substring(1));
|
||||
return ColorsDirectory.GetFiles(file, SearchOption.TopDirectoryOnly);
|
||||
}
|
||||
|
||||
private string CreateImage(string name)
|
||||
{
|
||||
using (var bitmap = new Bitmap(IMG_SIZE, IMG_SIZE))
|
||||
using (var graphics = Graphics.FromImage(bitmap))
|
||||
{
|
||||
var color = ColorTranslator.FromHtml(name);
|
||||
graphics.Clear(color);
|
||||
|
||||
var path = CreateFileName(name);
|
||||
bitmap.Save(path, ImageFormat.Png);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
private string CreateFileName(string name)
|
||||
{
|
||||
return string.Format("{0}{1}.png", ColorsDirectory.FullName, name.Substring(1));
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
|
||||
public string GetTranslatedPluginTitle()
|
||||
{
|
||||
return context.API.GetTranslation("flowlauncher_plugin_color_plugin_name");
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginDescription()
|
||||
{
|
||||
return context.API.GetTranslation("flowlauncher_plugin_color_plugin_description");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"ID": "9B36CE6181FC47FBB597AA2C29CD9B0A",
|
||||
"ActionKeyword": "*",
|
||||
"Name": "Colors",
|
||||
"Description": "Provide hex color preview.(Try #000 in Flow Launcher)",
|
||||
"Author": "qianlifeng",
|
||||
"Version": "1.1.1",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Color.dll",
|
||||
"IcoPath": "Images\\color.png"
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ namespace Flow.Launcher.Plugin.ControlPanel
|
|||
int cxDesired, int cyDesired, uint fuLoad);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
extern static bool DestroyIcon(IntPtr handle);
|
||||
static extern bool DestroyIcon(IntPtr handle);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType);
|
||||
|
|
|
|||
|
|
@ -45,53 +45,10 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Images\ControlPanel.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\en.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<Content Include="Images\*.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\zh-cn.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\zh-tw.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\de.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\pl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Languages\tr.xaml">
|
||||
<Content Include="Languages\*.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Name": "Control Panel",
|
||||
"Description": "Search within the Control Panel.",
|
||||
"Author": "CoenraadS",
|
||||
"Version": "1.1.1",
|
||||
"Version": "1.1.2",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.ControlPanel.dll",
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ using System.Windows;
|
|||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.Plugin.Explorer.Search;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
||||
using System.Linq;
|
||||
using MessageBox = System.Windows.Forms.MessageBox;
|
||||
using MessageBoxIcon = System.Windows.Forms.MessageBoxIcon;
|
||||
using MessageBoxButton = System.Windows.Forms.MessageBoxButtons;
|
||||
using DialogResult = System.Windows.Forms.DialogResult;
|
||||
using Flow.Launcher.Plugin.Explorer.ViewModels;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer
|
||||
{
|
||||
|
|
@ -22,10 +23,13 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
|
||||
private Settings Settings { get; set; }
|
||||
|
||||
public ContextMenu(PluginInitContext context, Settings settings)
|
||||
private SettingsViewModel ViewModel { get; set; }
|
||||
|
||||
public ContextMenu(PluginInitContext context, Settings settings, SettingsViewModel vm)
|
||||
{
|
||||
Context = context;
|
||||
Settings = settings;
|
||||
ViewModel = vm;
|
||||
}
|
||||
|
||||
public List<Result> LoadContextMenus(Result selectedResult)
|
||||
|
|
@ -50,6 +54,58 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
|
||||
var icoPath = (record.Type == ResultType.File) ? Constants.FileImagePath : Constants.FolderImagePath;
|
||||
var fileOrFolder = (record.Type == ResultType.File) ? "file" : "folder";
|
||||
|
||||
if (!Settings.QuickAccessLinks.Any(x => x.Path == record.FullPath))
|
||||
{
|
||||
contextMenus.Add(new Result
|
||||
{
|
||||
Title = Context.API.GetTranslation("plugin_explorer_add_to_quickaccess_title"),
|
||||
SubTitle = string.Format(Context.API.GetTranslation("plugin_explorer_add_to_quickaccess_subtitle"), fileOrFolder),
|
||||
Action = (context) =>
|
||||
{
|
||||
Settings.QuickAccessLinks.Add(new AccessLink { Path = record.FullPath, Type = record.Type });
|
||||
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess"),
|
||||
string.Format(
|
||||
Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess_detail"),
|
||||
fileOrFolder),
|
||||
Constants.ExplorerIconImageFullPath);
|
||||
|
||||
ViewModel.Save();
|
||||
|
||||
return true;
|
||||
},
|
||||
SubTitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_titletooltip"),
|
||||
TitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_titletooltip"),
|
||||
IcoPath = Constants.QuickAccessImagePath
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
contextMenus.Add(new Result
|
||||
{
|
||||
Title = Context.API.GetTranslation("plugin_explorer_remove_from_quickaccess_title"),
|
||||
SubTitle = string.Format(Context.API.GetTranslation("plugin_explorer_remove_from_quickaccess_subtitle"), fileOrFolder),
|
||||
Action = (context) =>
|
||||
{
|
||||
Settings.QuickAccessLinks.Remove(Settings.QuickAccessLinks.FirstOrDefault(x => x.Path == record.FullPath));
|
||||
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess"),
|
||||
string.Format(
|
||||
Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess_detail"),
|
||||
fileOrFolder),
|
||||
Constants.ExplorerIconImageFullPath);
|
||||
|
||||
ViewModel.Save();
|
||||
|
||||
return true;
|
||||
},
|
||||
SubTitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_remove_titletooltip"),
|
||||
TitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_remove_titletooltip"),
|
||||
IcoPath = Constants.RemoveQuickAccessImagePath
|
||||
});
|
||||
}
|
||||
|
||||
contextMenus.Add(new Result
|
||||
{
|
||||
Title = Context.API.GetTranslation("plugin_explorer_copypath"),
|
||||
|
|
@ -228,7 +284,7 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
Action = _ =>
|
||||
{
|
||||
if(!Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x.Path == record.FullPath))
|
||||
Settings.IndexSearchExcludedSubdirectoryPaths.Add(new FolderLink { Path = record.FullPath });
|
||||
Settings.IndexSearchExcludedSubdirectoryPaths.Add(new AccessLink { Path = record.FullPath });
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
|
|
@ -26,73 +27,10 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Images\explorer.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
<None Include="Images\index.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
<None Include="Images\excludeindexpath.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
<None Include="Images\copy.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
<None Include="Images\deletefilefolder.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
<Content Include="Images\file.png">
|
||||
<Content Include="Images\*.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
||||
<None Include="Images\folder.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
<None Include="Images\user.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
<None Include="Images\windowsindexingoptions.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
<Content Include="Languages\en.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
||||
<Content Include="Languages\zh-cn.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
||||
<Content Include="Languages\zh-tw.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
||||
<Content Include="Languages\de.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
||||
<Content Include="Languages\pl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
||||
<Content Include="Languages\tr.xaml">
|
||||
<Content Include="Languages\*.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
|
|
|
|||
BIN
Plugins/Flow.Launcher.Plugin.Explorer/Images/quickaccess.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -16,7 +16,7 @@
|
|||
<system:String x:Key="plugin_explorer_edit">Edit</system:String>
|
||||
<system:String x:Key="plugin_explorer_add">Add</system:String>
|
||||
<system:String x:Key="plugin_explorer_manageactionkeywords_header">Customise Action Keywords</system:String>
|
||||
<system:String x:Key="plugin_explorer_quickfolderaccess_header">Quick Folder Access Paths</system:String>
|
||||
<system:String x:Key="plugin_explorer_quickaccesslinks_header">Quick Access Links</system:String>
|
||||
<system:String x:Key="plugin_explorer_indexsearchexcludedpaths_header">Index Search Excluded Paths</system:String>
|
||||
<system:String x:Key="plugin_explorer_manageindexoptions">Indexing Options</system:String>
|
||||
<system:String x:Key="plugin_explorer_actionkeywordview_search">Search Activation:</system:String>
|
||||
|
|
@ -42,5 +42,15 @@
|
|||
<system:String x:Key="plugin_explorer_openindexingoptions">Open Windows Indexing Options</system:String>
|
||||
<system:String x:Key="plugin_explorer_openindexingoptions_subtitle">Manage indexed files and folders</system:String>
|
||||
<system:String x:Key="plugin_explorer_openindexingoptions_errormsg">Failed to open Windows Indexing Options</system:String>
|
||||
<system:String x:Key="plugin_explorer_add_to_quickaccess_title">Add to Quick Access</system:String>
|
||||
<system:String x:Key="plugin_explorer_add_to_quickaccess_subtitle">Add the current {0} to Quick Access</system:String>
|
||||
<system:String x:Key="plugin_explorer_addfilefoldersuccess">Successfully Added</system:String>
|
||||
<system:String x:Key="plugin_explorer_addfilefoldersuccess_detail">Successfully added to Quick Access</system:String>
|
||||
<system:String x:Key="plugin_explorer_removefilefoldersuccess">Successfully Removed</system:String>
|
||||
<system:String x:Key="plugin_explorer_removefilefoldersuccess_detail">Successfully removed from Quick Access</system:String>
|
||||
<system:String x:Key="plugin_explorer_contextmenu_titletooltip">Add to Quick Access so it can be opened with Explorer's Search Activation action keyword</system:String>
|
||||
<system:String x:Key="plugin_explorer_contextmenu_remove_titletooltip">Remove from Quick Access</system:String>
|
||||
<system:String x:Key="plugin_explorer_remove_from_quickaccess_title">Remove from Quick Access</system:String>
|
||||
<system:String x:Key="plugin_explorer_remove_from_quickaccess_subtitle">Remove the current {0} from Quick Access</system:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
|
@ -1,13 +1,17 @@
|
|||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Plugin.Explorer.Search;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
||||
using Flow.Launcher.Plugin.Explorer.ViewModels;
|
||||
using Flow.Launcher.Plugin.Explorer.Views;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer
|
||||
{
|
||||
public class Main : ISettingProvider, IPlugin, ISavable, IContextMenu, IPluginI18n
|
||||
public class Main : ISettingProvider, IAsyncPlugin, ISavable, IContextMenu, IPluginI18n
|
||||
{
|
||||
internal PluginInitContext Context { get; set; }
|
||||
|
||||
|
|
@ -17,17 +21,30 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
|
||||
private IContextMenu contextMenu;
|
||||
|
||||
private SearchManager searchManager;
|
||||
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
return new ExplorerSettings(viewModel);
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
public async Task InitAsync(PluginInitContext context)
|
||||
{
|
||||
Context = context;
|
||||
viewModel = new SettingsViewModel(context);
|
||||
await viewModel.LoadStorage();
|
||||
Settings = viewModel.Settings;
|
||||
contextMenu = new ContextMenu(Context, Settings);
|
||||
|
||||
// as at v1.7.0 this is to maintain backwards compatibility, need to be removed afterwards.
|
||||
if (Settings.QuickFolderAccessLinks.Any())
|
||||
{
|
||||
Settings.QuickAccessLinks = Settings.QuickFolderAccessLinks;
|
||||
Settings.QuickFolderAccessLinks = new List<AccessLink>();
|
||||
}
|
||||
|
||||
contextMenu = new ContextMenu(Context, Settings, viewModel);
|
||||
searchManager = new SearchManager(Settings, Context);
|
||||
ResultManager.Init(Context);
|
||||
}
|
||||
|
||||
public List<Result> LoadContextMenus(Result selectedResult)
|
||||
|
|
@ -35,9 +52,9 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
return contextMenu.LoadContextMenus(selectedResult);
|
||||
}
|
||||
|
||||
public List<Result> Query(Query query)
|
||||
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
||||
{
|
||||
return new SearchManager(Settings, Context).Search(query);
|
||||
return await searchManager.SearchAsync(query, token);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
|
|
|
|||