Merge branch 'dev' into fix_loading_uwp

This commit is contained in:
Jeremy Wu 2020-12-17 06:52:37 +11:00
commit 5bc9ce8198
280 changed files with 8248 additions and 3629 deletions

32
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View file

@ -0,0 +1,32 @@
---
name: "\U0001F41E Bug report"
about: Create a bug report to help us improve Flow Launcher
title: "[Describe Your Bug]"
labels: 'bug'
assignees: ''
---
**Describe the bug/issue**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. ...
2. ...
3. ...
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Your System**
```
Windows build number: (run "ver" at a command prompt)
Flow Launcher version: (Settings => About)
```
**Flow Launcher Error Log**
<!--
The latest log file can be found here: %AppData%\FlowLauncher\Logs\<version>\<date>.txt
or for portable mode: %LocalAppData%\FlowLauncher\<App-Version>\UserData\Logs\<version>\<date>.txt
Drag and drop the log file below this comment.
-->

21
.github/ISSUE_TEMPLATE/code-review.md vendored Normal file
View file

@ -0,0 +1,21 @@
---
name: "\U0001F50E Code Review"
about: Describe (bad) code that needs to be improved
title: "[Describe Problematic Code]"
labels: 'code review'
assignees: ''
---
**Point to the corresponding file and line number(s) which could be improved.**
Click on the line number in GitHub to create an anchored link to the corresponding code section. For example:
https://github.com/Flow-Launcher/Flow.Launcher/blob/master/Flow.Launcher/Storage/TopMostRecord.cs#L8
Provide your alternative implementation idea:
```
Code
```

14
.github/ISSUE_TEMPLATE/discussion.md vendored Normal file
View file

@ -0,0 +1,14 @@
---
name: "⁉️ Question/Discussion"
about: Create a report to help us improve
title: "[Ask a question or propose a change]"
labels: 'question/discussion'
assignees: ''
---
**Question**
Describe your question.
**Discussion**
Discuss a topic.

View file

@ -0,0 +1,24 @@
---
name: "⭐ Feature request"
about: Suggest a new idea or a feature enhacement
title: "[Describe Your Feature]"
labels: 'enhancement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

6
.gitignore vendored
View file

@ -300,4 +300,8 @@ migrateToAutomaticPackageRestore.ps1
*.pyc
*.diagsession
Output-Performance.txt
*.diff
*.diff
# vscode
.vscode
.history

15
Directory.Build.targets Normal file
View file

@ -0,0 +1,15 @@
<Project>
<Target Name="ExcludePluginProjectReferenceOutput"
AfterTargets="AssignProjectConfiguration"
BeforeTargets="ResolveProjectReferences"
Condition="'$(OutputType)' == 'Library' and '$(CopyLocalLockFileAssemblies)' == 'true' and $(AssemblyName.EndsWith('Tests')) == 'false' ">
<ItemGroup>
<ProjectReferenceWithConfiguration Update="@(ProjectReferenceWithConfiguration)" >
<Private>false</Private>
</ProjectReferenceWithConfiguration>
<ProjectReference Update="@(ProjectReference)" >
<Private>false</Private>
</ProjectReference>
</ItemGroup>
</Target>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 58 KiB

BIN
Doc/Logo/app_error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
Doc/Logo/logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
Doc/Logo/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

1
Doc/Logo/logo.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 382.6 382.5"><defs><linearGradient id="a" x2="249.9" y1="191.3" y2="191.3" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1b9de2"/><stop offset=".2" stop-color="#1595df"/><stop offset=".7" stop-color="#0680d7"/><stop offset="1" stop-color="#0078d4"/></linearGradient><linearGradient id="b" x1="697.3" x2="809.9" y1="499.2" y2="499.2" gradientTransform="rotate(49.2 809.6 -269.1)" xlink:href="#a"/><linearGradient id="c" x1="143.6" x2="393.5" y1="249.8" y2="249.8" gradientTransform="rotate(180 263.1 220.5)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0c59a4"/><stop offset="1" stop-color="#114a8b"/></linearGradient></defs><path fill="url(#a)" d="M117 382.5H0v-189C0 90.7 84 0 186.9 0h63v120.4h-54.6c-46.8 0-78 31.2-78 78l-.1 184.1z"/><path fill="url(#b)" d="M249.9 182.6l.2-46.8h-54.7c-39 0-62.4 23.4-62.4 62.4l-.2 47 54.6-.1c39 0 62.5-23.4 62.5-62.5z"/><path fill="url(#c)" d="M265.5 0h117v189c0 102.8-84 193.5-186.8 193.5h-63V262.1h54.6c46.9 0 78.1-31.2 78.1-78L265.5 0z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
Doc/Logo/logo128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
Doc/Logo/logo16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

BIN
Doc/Logo/logo256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
Doc/Logo/logo32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

BIN
Doc/Logo/logo48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,019 B

BIN
Doc/Logo/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
Doc/Logo/logo64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

BIN
Doc/app_missing_img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -32,10 +32,9 @@ namespace Flow.Launcher.Core.Configuration
try
{
MoveUserDataFolder(DataLocation.PortableDataPath, DataLocation.RoamingDataPath);
#if DEBUG
#if !DEBUG
// Create shortcuts and uninstaller are not required in debug mode,
// otherwise will repoint the path of the actual installed production version to the debug version
#else
CreateShortcuts();
CreateUninstallerEntry();
#endif
@ -48,10 +47,7 @@ namespace Flow.Launcher.Core.Configuration
}
catch (Exception e)
{
#if !DEBUG
Log.Exception("Portable", "Error occured while disabling portable mode", e);
#endif
throw;
Log.Exception("|Portable.DisablePortableMode|Error occured while disabling portable mode", e);
}
}
@ -60,10 +56,9 @@ namespace Flow.Launcher.Core.Configuration
try
{
MoveUserDataFolder(DataLocation.RoamingDataPath, DataLocation.PortableDataPath);
#if DEBUG
#if !DEBUG
// Remove shortcuts and uninstaller are not required in debug mode,
// otherwise will delete the actual installed production version
#else
RemoveShortcuts();
RemoveUninstallerEntry();
#endif
@ -76,10 +71,7 @@ namespace Flow.Launcher.Core.Configuration
}
catch (Exception e)
{
#if !DEBUG
Log.Exception("Portable", "Error occured while enabling portable mode", e);
#endif
throw;
Log.Exception("|Portable.EnablePortableMode|Error occured while enabling portable mode", e);
}
}
@ -103,7 +95,7 @@ namespace Flow.Launcher.Core.Configuration
public void MoveUserDataFolder(string fromLocation, string toLocation)
{
FilesFolders.Copy(fromLocation, toLocation);
FilesFolders.CopyAll(fromLocation, toLocation);
VerifyUserDataAfterMove(fromLocation, toLocation);
}
@ -125,14 +117,13 @@ namespace Flow.Launcher.Core.Configuration
public void CreateUninstallerEntry()
{
var uninstallRegSubKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall";
// NB: Sometimes the Uninstall key doesn't exist
using (var parentKey =
RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default)
.CreateSubKey("Uninstall", RegistryKeyPermissionCheck.ReadWriteSubTree)) {; }
var key = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default)
.CreateSubKey(uninstallRegSubKey + "\\" + Constant.FlowLauncher, RegistryKeyPermissionCheck.ReadWriteSubTree);
key.SetValue("DisplayIcon", Constant.ApplicationDirectory + "\\app.ico", RegistryValueKind.String);
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default))
using (var subKey1 = baseKey.CreateSubKey(uninstallRegSubKey, RegistryKeyPermissionCheck.ReadWriteSubTree))
using (var subKey2 = subKey1.CreateSubKey(Constant.FlowLauncher, RegistryKeyPermissionCheck.ReadWriteSubTree))
{
subKey2.SetValue("DisplayIcon", Path.Combine(Constant.ApplicationDirectory, "app.ico"), RegistryValueKind.String);
}
using (var portabilityUpdater = NewUpdateManager())
{
@ -142,7 +133,10 @@ namespace Flow.Launcher.Core.Configuration
internal void IndicateDeletion(string filePathTodelete)
{
using (StreamWriter sw = File.CreateText(filePathTodelete + "\\" + DataLocation.DeletionIndicatorFile)){}
var deleteFilePath = Path.Combine(filePathTodelete, DataLocation.DeletionIndicatorFile);
using (var _ = File.CreateText(deleteFilePath))
{
}
}
///<summary>
@ -152,21 +146,18 @@ namespace Flow.Launcher.Core.Configuration
public void PreStartCleanUpAfterPortabilityUpdate()
{
// Specify here so this method does not rely on other environment variables to initialise
var portableDataPath = Path.Combine(Directory.GetParent(Assembly.GetExecutingAssembly().Location.NonNull()).ToString(), "UserData");
var roamingDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FlowLauncher");
var portableDataDir = Path.Combine(Directory.GetParent(Assembly.GetExecutingAssembly().Location.NonNull()).ToString(), "UserData");
var roamingDataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FlowLauncher");
bool DataLocationPortableDeleteRequired = false;
bool DataLocationRoamingDeleteRequired = false;
// Get full path to the .dead files for each case
var portableDataDeleteFilePath = Path.Combine(portableDataDir, DataLocation.DeletionIndicatorFile);
var roamingDataDeleteFilePath = Path.Combine(roamingDataDir, DataLocation.DeletionIndicatorFile);
if ((roamingDataPath + "\\" + DataLocation.DeletionIndicatorFile).FileExits())
DataLocationRoamingDeleteRequired = true;
if ((portableDataPath + "\\" + DataLocation.DeletionIndicatorFile).FileExits())
DataLocationPortableDeleteRequired = true;
if (DataLocationRoamingDeleteRequired)
// If the data folder in %appdata% is marked for deletion,
// delete it and prompt the user to pick the portable data location
if (File.Exists(roamingDataDeleteFilePath))
{
FilesFolders.RemoveFolderIfExists(roamingDataPath);
FilesFolders.RemoveFolderIfExists(roamingDataDir);
if (MessageBox.Show("Flow Launcher has detected you enabled portable mode, " +
"would you like to move it to a different location?", string.Empty,
@ -176,18 +167,15 @@ namespace Flow.Launcher.Core.Configuration
Environment.Exit(0);
}
return;
}
if(DataLocationPortableDeleteRequired)
// Otherwise, if the portable data folder is marked for deletion,
// delete it and notify the user about it.
else if (File.Exists(portableDataDeleteFilePath))
{
FilesFolders.RemoveFolderIfExists(portableDataPath);
FilesFolders.RemoveFolderIfExists(portableDataDir);
MessageBox.Show("Flow Launcher has detected you disabled portable mode, " +
"the relevant shortcuts and uninstaller entry have been created");
return;
}
}
@ -196,7 +184,7 @@ namespace Flow.Launcher.Core.Configuration
var roamingLocationExists = DataLocation.RoamingDataPath.LocationExists();
var portableLocationExists = DataLocation.PortableDataPath.LocationExists();
if(roamingLocationExists && portableLocationExists)
if (roamingLocationExists && portableLocationExists)
{
MessageBox.Show(string.Format("Flow Launcher detected your user data exists both in {0} and " +
"{1}. {2}{2}Please delete {1} in order to proceed. No changes have occured.",

View file

@ -54,12 +54,7 @@
<ItemGroup>
<PackageReference Include="FSharp.Core" Version="4.7.1" />
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="squirrel.windows" Version="1.5.2" />
<PackageReference Include="PropertyChanged.Fody" Version="2.5.13" />
<PackageReference Include="SharpZipLib" Version="1.2.0" />
<PackageReference Include="System.Runtime" Version="4.3.1" />
</ItemGroup>
<ItemGroup>

View file

@ -0,0 +1,57 @@
using Flow.Launcher.Infrastructure;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
namespace Flow.Launcher.Core.Plugin
{
internal class PluginAssemblyLoader : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver dependencyResolver;
private readonly AssemblyDependencyResolver referencedPluginPackageDependencyResolver;
private readonly AssemblyName assemblyName;
internal PluginAssemblyLoader(string assemblyFilePath)
{
dependencyResolver = new AssemblyDependencyResolver(assemblyFilePath);
assemblyName = new AssemblyName(Path.GetFileNameWithoutExtension(assemblyFilePath));
referencedPluginPackageDependencyResolver =
new AssemblyDependencyResolver(Path.Combine(Constant.ProgramDirectory, "Flow.Launcher.Plugin.dll"));
}
internal Assembly LoadAssemblyAndDependencies()
{
return LoadFromAssemblyName(assemblyName);
}
protected override Assembly Load(AssemblyName assemblyName)
{
string assemblyPath = dependencyResolver.ResolveAssemblyToPath(assemblyName);
// When resolving dependencies, ignore assembly depenedencies that already exits with Flow.Launcher.Plugin
// Otherwise will get unexpected behaviour with plugins, e.g. JsonIgnore attribute not honored in WebSearch or other plugins
// that use Newtonsoft.Json
if (assemblyPath == null || ExistsInReferencedPluginPackage(assemblyName))
return null;
return LoadFromAssemblyPath(assemblyPath);
}
internal Type FromAssemblyGetTypeOfInterface(Assembly assembly, Type type)
{
var allTypes = assembly.ExportedTypes;
return allTypes.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(type));
}
internal bool ExistsInReferencedPluginPackage(AssemblyName assemblyName)
{
return referencedPluginPackageDependencyResolver.ResolveAssemblyToPath(assemblyName) != null;
}
}
}

View file

@ -2,9 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Flow.Launcher.Infrastructure.Exception;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin;
@ -13,24 +12,16 @@ namespace Flow.Launcher.Core.Plugin
internal abstract class PluginConfig
{
private const string PluginConfigName = "plugin.json";
private static readonly List<PluginMetadata> PluginMetadatas = new List<PluginMetadata>();
/// <summary>
/// Parse plugin metadata in giving directories
/// Parse plugin metadata in the given directories
/// </summary>
/// <param name="pluginDirectories"></param>
/// <returns></returns>
public static List<PluginMetadata> Parse(string[] pluginDirectories)
{
PluginMetadatas.Clear();
var allPluginMetadata = new List<PluginMetadata>();
var directories = pluginDirectories.SelectMany(Directory.GetDirectories);
ParsePluginConfigs(directories);
return PluginMetadatas;
}
private static void ParsePluginConfigs(IEnumerable<string> directories)
{
// todo use linq when diable plugin is implmented since parallel.foreach + list is not thread saft
foreach (var directory in directories)
{
@ -50,15 +41,17 @@ namespace Flow.Launcher.Core.Plugin
PluginMetadata metadata = GetPluginMetadata(directory);
if (metadata != null)
{
PluginMetadatas.Add(metadata);
allPluginMetadata.Add(metadata);
}
}
}
return allPluginMetadata;
}
private static PluginMetadata GetPluginMetadata(string pluginDirectory)
{
string configPath = Path.Combine(pluginDirectory, PluginConfigName);
string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName);
if (!File.Exists(configPath))
{
Log.Error($"|PluginConfig.GetPluginMetadata|Didn't find config file <{configPath}>");
@ -81,7 +74,6 @@ namespace Flow.Launcher.Core.Plugin
return null;
}
if (!AllowedLanguage.IsAllowed(metadata.Language))
{
Log.Error($"|PluginConfig.GetPluginMetadata|Invalid language <{metadata.Language}> for config <{configPath}>");

View file

@ -1,202 +0,0 @@
using System;
using System.IO;
using System.Windows;
using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Plugin
{
internal class PluginInstaller
{
internal static void Install(string path)
{
if (File.Exists(path))
{
string tempFoler = Path.Combine(Path.GetTempPath(), "flowlauncher\\plugins");
if (Directory.Exists(tempFoler))
{
Directory.Delete(tempFoler, true);
}
UnZip(path, tempFoler, true);
string iniPath = Path.Combine(tempFoler, "plugin.json");
if (!File.Exists(iniPath))
{
MessageBox.Show("Install failed: plugin config is missing");
return;
}
PluginMetadata plugin = GetMetadataFromJson(tempFoler);
if (plugin == null || plugin.Name == null)
{
MessageBox.Show("Install failed: plugin config is invalid");
return;
}
string pluginFolerPath = Infrastructure.UserSettings.DataLocation.PluginsDirectory;
string newPluginName = plugin.Name
.Replace("/", "_")
.Replace("\\", "_")
.Replace(":", "_")
.Replace("<", "_")
.Replace(">", "_")
.Replace("?", "_")
.Replace("*", "_")
.Replace("|", "_")
+ "-" + Guid.NewGuid();
string newPluginPath = Path.Combine(pluginFolerPath, newPluginName);
string content = $"Do you want to install following plugin?{Environment.NewLine}{Environment.NewLine}" +
$"Name: {plugin.Name}{Environment.NewLine}" +
$"Version: {plugin.Version}{Environment.NewLine}" +
$"Author: {plugin.Author}";
PluginPair existingPlugin = PluginManager.GetPluginForId(plugin.ID);
if (existingPlugin != null)
{
content = $"Do you want to update following plugin?{Environment.NewLine}{Environment.NewLine}" +
$"Name: {plugin.Name}{Environment.NewLine}" +
$"Old Version: {existingPlugin.Metadata.Version}" +
$"{Environment.NewLine}New Version: {plugin.Version}" +
$"{Environment.NewLine}Author: {plugin.Author}";
}
var result = MessageBox.Show(content, "Install plugin", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
if (existingPlugin != null && Directory.Exists(existingPlugin.Metadata.PluginDirectory))
{
//when plugin is in use, we can't delete them. That's why we need to make plugin folder a random name
File.Create(Path.Combine(existingPlugin.Metadata.PluginDirectory, "NeedDelete.txt")).Close();
}
UnZip(path, newPluginPath, true);
Directory.Delete(tempFoler, true);
//exsiting plugins may be has loaded by application,
//if we try to delelte those kind of plugins, we will get a error that indicate the
//file is been used now.
//current solution is to restart Flow Launcher. Ugly.
//if (MainWindow.Initialized)
//{
// Plugins.Initialize();
//}
if (MessageBox.Show($"You have installed plugin {plugin.Name} successfully.{Environment.NewLine}" +
"Restart Flow Launcher to take effect?",
"Install plugin", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
{
PluginManager.API.RestartApp();
}
}
}
}
private static PluginMetadata GetMetadataFromJson(string pluginDirectory)
{
string configPath = Path.Combine(pluginDirectory, "plugin.json");
PluginMetadata metadata;
if (!File.Exists(configPath))
{
return null;
}
try
{
metadata = JsonConvert.DeserializeObject<PluginMetadata>(File.ReadAllText(configPath));
metadata.PluginDirectory = pluginDirectory;
}
catch (Exception)
{
string error = $"Parse plugin config {configPath} failed: json format is not valid";
#if (DEBUG)
{
throw new Exception(error);
}
#endif
return null;
}
if (!AllowedLanguage.IsAllowed(metadata.Language))
{
string error = $"Parse plugin config {configPath} failed: invalid language {metadata.Language}";
#if (DEBUG)
{
throw new Exception(error);
}
#endif
return null;
}
if (!File.Exists(metadata.ExecuteFilePath))
{
string error = $"Parse plugin config {configPath} failed: ExecuteFile {metadata.ExecuteFilePath} didn't exist";
#if (DEBUG)
{
throw new Exception(error);
}
#endif
return null;
}
return metadata;
}
/// <summary>
/// unzip
/// </summary>
/// <param name="zipedFile">The ziped file.</param>
/// <param name="strDirectory">The STR directory.</param>
/// <param name="overWrite">overwirte</param>
private static void UnZip(string zipedFile, string strDirectory, bool overWrite)
{
if (strDirectory == "")
strDirectory = Directory.GetCurrentDirectory();
if (!strDirectory.EndsWith("\\"))
strDirectory = strDirectory + "\\";
using (ZipInputStream s = new ZipInputStream(File.OpenRead(zipedFile)))
{
ZipEntry theEntry;
while ((theEntry = s.GetNextEntry()) != null)
{
string directoryName = "";
string pathToZip = "";
pathToZip = theEntry.Name;
if (pathToZip != "")
directoryName = Path.GetDirectoryName(pathToZip) + "\\";
string fileName = Path.GetFileName(pathToZip);
Directory.CreateDirectory(strDirectory + directoryName);
if (fileName != "")
{
if ((File.Exists(strDirectory + directoryName + fileName) && overWrite) || (!File.Exists(strDirectory + directoryName + fileName)))
{
using (FileStream streamWriter = File.Create(strDirectory + directoryName + fileName))
{
byte[] data = new byte[2048];
while (true)
{
int size = s.Read(data, 0, data.Length);
if (size > 0)
streamWriter.Write(data, 0, size);
else
break;
}
streamWriter.Close();
}
}
}
}
s.Close();
}
}
}
}

View file

@ -19,10 +19,6 @@ namespace Flow.Launcher.Core.Plugin
{
private static IEnumerable<PluginPair> _contextMenuPlugins;
/// <summary>
/// Directories that will hold Flow Launcher plugin directory
/// </summary>
public static List<PluginPair> AllPlugins { get; private set; }
public static readonly List<PluginPair> GlobalPlugins = new List<PluginPair>();
public static readonly Dictionary<string, PluginPair> NonGlobalPlugins = new Dictionary<string, PluginPair>();
@ -32,27 +28,18 @@ namespace Flow.Launcher.Core.Plugin
// todo happlebao, this should not be public, the indicator function should be embeded
public static PluginsSettings Settings;
private static List<PluginMetadata> _metadatas;
private static readonly string[] Directories = { Constant.PreinstalledDirectory, DataLocation.PluginsDirectory };
private static void ValidateUserDirectory()
{
if (!Directory.Exists(DataLocation.PluginsDirectory))
{
Directory.CreateDirectory(DataLocation.PluginsDirectory);
}
}
/// <summary>
/// Directories that will hold Flow Launcher plugin directory
/// </summary>
private static readonly string[] Directories = { Constant.PreinstalledDirectory, DataLocation.PluginsDirectory };
private static void DeletePythonBinding()
{
const string binding = "flowlauncher.py";
var directory = DataLocation.PluginsDirectory;
foreach (var subDirectory in Directory.GetDirectories(directory))
foreach (var subDirectory in Directory.GetDirectories(DataLocation.PluginsDirectory))
{
var path = Path.Combine(subDirectory, binding);
if (File.Exists(path))
{
File.Delete(path);
}
File.Delete(Path.Combine(subDirectory, binding));
}
}
@ -76,7 +63,8 @@ namespace Flow.Launcher.Core.Plugin
static PluginManager()
{
ValidateUserDirectory();
// validate user directory
Directory.CreateDirectory(DataLocation.PluginsDirectory);
// force old plugins use new python binding
DeletePythonBinding();
}
@ -132,9 +120,10 @@ namespace Flow.Launcher.Core.Plugin
GlobalPlugins.Add(plugin);
// Plugins may have multiple ActionKeywords, eg. WebSearch
plugin.Metadata.ActionKeywords.Where(x => x != Query.GlobalPluginWildcardSign)
.ToList()
.ForEach(x => NonGlobalPlugins[x] = plugin);
plugin.Metadata.ActionKeywords
.Where(x => x != Query.GlobalPluginWildcardSign)
.ToList()
.ForEach(x => NonGlobalPlugins[x] = plugin);
}
if (failedPlugins.Any())
@ -144,11 +133,6 @@ namespace Flow.Launcher.Core.Plugin
}
}
public static void InstallPlugin(string path)
{
PluginInstaller.Install(path);
}
public static List<PluginPair> ValidPluginsForQuery(Query query)
{
if (NonGlobalPlugins.ContainsKey(query.ActionKeyword))
@ -164,9 +148,9 @@ namespace Flow.Launcher.Core.Plugin
public static List<Result> QueryForPlugin(PluginPair pair, Query query)
{
var results = new List<Result>();
try
{
List<Result> results = null;
var metadata = pair.Metadata;
var milliseconds = Stopwatch.Debug($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", () =>
{
@ -175,13 +159,12 @@ namespace Flow.Launcher.Core.Plugin
});
metadata.QueryCount += 1;
metadata.AvgQueryTime = metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2;
return results;
}
catch (Exception e)
{
Log.Exception($"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e);
return new List<Result>();
}
return results;
}
public static void UpdatePluginMetadata(List<Result> results, PluginMetadata metadata, Query query)
@ -221,47 +204,34 @@ namespace Flow.Launcher.Core.Plugin
public static List<Result> GetContextMenusForPlugin(Result result)
{
var results = new List<Result>();
var pluginPair = _contextMenuPlugins.FirstOrDefault(o => o.Metadata.ID == result.PluginID);
if (pluginPair != null)
{
var metadata = pluginPair.Metadata;
var plugin = (IContextMenu)pluginPair.Plugin;
try
{
var results = plugin.LoadContextMenus(result);
results = plugin.LoadContextMenus(result);
foreach (var r in results)
{
r.PluginDirectory = metadata.PluginDirectory;
r.PluginID = metadata.ID;
r.PluginDirectory = pluginPair.Metadata.PluginDirectory;
r.PluginID = pluginPair.Metadata.ID;
r.OriginQuery = result.OriginQuery;
}
return results;
}
catch (Exception e)
{
Log.Exception($"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{metadata.Name}>", e);
return new List<Result>();
Log.Exception($"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{pluginPair.Metadata.Name}>", e);
}
}
else
{
return new List<Result>();
}
return results;
}
public static bool ActionKeywordRegistered(string actionKeyword)
{
if (actionKeyword != Query.GlobalPluginWildcardSign &&
NonGlobalPlugins.ContainsKey(actionKeyword))
{
return true;
}
else
{
return false;
}
return actionKeyword != Query.GlobalPluginWildcardSign
&& NonGlobalPlugins.ContainsKey(actionKeyword);
}
/// <summary>
@ -299,7 +269,7 @@ namespace Flow.Launcher.Core.Plugin
GlobalPlugins.Remove(plugin);
}
if(oldActionkeyword != Query.GlobalPluginWildcardSign)
if (oldActionkeyword != Query.GlobalPluginWildcardSign)
NonGlobalPlugins.Remove(oldActionkeyword);

View file

@ -21,7 +21,7 @@ namespace Flow.Launcher.Core.Plugin
public static List<PluginPair> Plugins(List<PluginMetadata> metadatas, PluginsSettings settings)
{
var dotnetPlugins = DotNetPlugins(metadatas).ToList();
var dotnetPlugins = DotNetPlugins(metadatas);
var pythonPlugins = PythonPlugins(metadatas, settings.PythonDirectory);
var executablePlugins = ExecutablePlugins(metadatas);
var plugins = dotnetPlugins.Concat(pythonPlugins).Concat(executablePlugins).ToList();
@ -41,80 +41,63 @@ namespace Flow.Launcher.Core.Plugin
{
#if DEBUG
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(metadata.ExecuteFilePath);
var types = assembly.GetTypes();
var type = types.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(typeof(IPlugin)));
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;
Assembly assembly = null;
IPlugin plugin = null;
try
{
assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(metadata.ExecuteFilePath);
}
catch (Exception e)
{
erroredPlugins.Add(metadata.Name);
var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath);
assembly = assemblyLoader.LoadAssemblyAndDependencies();
var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, typeof(IPlugin));
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);
return;
}
Type type;
try
{
var types = assembly.GetTypes();
type = types.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(typeof(IPlugin)));
}
catch (InvalidOperationException e)
{
erroredPlugins.Add(metadata.Name);
Log.Exception($"|PluginsLoader.DotNetPlugins|Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
return;
}
catch (ReflectionTypeLoadException e)
{
erroredPlugins.Add(metadata.Name);
Log.Exception($"|PluginsLoader.DotNetPlugins|The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
return;
}
IPlugin plugin;
try
{
plugin = (IPlugin)Activator.CreateInstance(type);
}
catch (Exception e)
{
erroredPlugins.Add(metadata.Name);
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
PluginPair pair = new PluginPair
plugins.Add(new PluginPair
{
Plugin = plugin,
Metadata = metadata
};
plugins.Add(pair);
});
});
metadata.InitTime += milliseconds;
}
if (erroredPlugins.Count > 0)
{
var errorPluginString = "";
var errorPluginString = String.Join(Environment.NewLine, erroredPlugins);
var errorMessage = "The following "
+ (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ")
+ "errored and cannot be loaded:";
erroredPlugins.ForEach(x => errorPluginString += x + Environment.NewLine);
Task.Run(() =>
{
MessageBox.Show($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
@ -127,65 +110,74 @@ namespace Flow.Launcher.Core.Plugin
return plugins;
}
public static IEnumerable<PluginPair> PythonPlugins(List<PluginMetadata> source, string pythonDirecotry)
public static IEnumerable<PluginPair> PythonPlugins(List<PluginMetadata> source, string pythonDirectory)
{
var metadatas = source.Where(o => o.Language.ToUpper() == AllowedLanguage.Python);
string filename;
if (string.IsNullOrEmpty(pythonDirecotry))
// try to set Constant.PythonPath, either from
// PATH or from the given pythonDirectory
if (string.IsNullOrEmpty(pythonDirectory))
{
var paths = Environment.GetEnvironmentVariable(PATH);
if (paths != null)
{
var pythonPaths = paths.Split(';').Where(p => p.ToLower().Contains(Python));
if (pythonPaths.Any())
var pythonInPath = paths
.Split(';')
.Where(p => p.ToLower().Contains(Python))
.Any();
if (pythonInPath)
{
filename = PythonExecutable;
Constant.PythonPath = PythonExecutable;
}
else
{
Log.Error("|PluginsLoader.PythonPlugins|Python can't be found in PATH.");
return new List<PluginPair>();
}
}
else
{
Log.Error("|PluginsLoader.PythonPlugins|PATH environment variable is not set.");
return new List<PluginPair>();
}
}
else
{
var path = Path.Combine(pythonDirecotry, PythonExecutable);
var path = Path.Combine(pythonDirectory, PythonExecutable);
if (File.Exists(path))
{
filename = path;
Constant.PythonPath = path;
}
else
{
Log.Error("|PluginsLoader.PythonPlugins|Can't find python executable in <b ");
return new List<PluginPair>();
Log.Error($"|PluginsLoader.PythonPlugins|Can't find python executable in {path}");
}
}
Constant.PythonPath = filename;
var plugins = metadatas.Select(metadata => new PluginPair
// if we have a path to the python executable,
// load every python plugin pair.
if (String.IsNullOrEmpty(Constant.PythonPath))
{
Plugin = new PythonPlugin(filename),
Metadata = metadata
});
return plugins;
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
});
}
}
public static IEnumerable<PluginPair> ExecutablePlugins(IEnumerable<PluginMetadata> source)
{
var metadatas = source.Where(o => o.Language.ToUpper() == AllowedLanguage.Executable);
var plugins = metadatas.Select(metadata => new PluginPair
{
Plugin = new ExecutablePlugin(metadata.ExecuteFilePath),
Metadata = metadata
});
return plugins;
return source
.Where(o => o.Language.ToUpper() == AllowedLanguage.Executable)
.Select(metadata => new PluginPair
{
Plugin = new ExecutablePlugin(metadata.ExecuteFilePath),
Metadata = metadata
});
}
}

View file

@ -29,7 +29,6 @@ namespace Flow.Launcher.Core.Plugin
else
{ // non action keyword
actionKeyword = string.Empty;
actionParameters = terms.ToList();
search = rawQuery;
}
@ -38,10 +37,7 @@ namespace Flow.Launcher.Core.Plugin
Terms = terms,
RawQuery = rawQuery,
ActionKeyword = actionKeyword,
Search = search,
// Obsolete value initialisation
ActionName = actionKeyword,
ActionParameters = actionParameters
Search = search
};
return query;

View file

@ -88,7 +88,6 @@ namespace Flow.Launcher.Core.Resource
{
language = language.NonNull();
Settings.Language = language.LanguageCode;
RemoveOldLanguageFiles();
if (language != AvailableLanguages.English)
@ -96,6 +95,7 @@ namespace Flow.Launcher.Core.Resource
LoadLanguage(language);
}
UpdatePluginMetadataTranslations();
Settings.Language = language.LanguageCode;
}

View file

@ -21,7 +21,7 @@ namespace Flow.Launcher.Core.Resource
private ResourceDictionary _oldResource;
private string _oldTheme;
public Settings Settings { get; set; }
private const string Folder = "Themes";
private const string Folder = Constant.Themes;
private const string Extension = ".xaml";
private string DirectoryPath => Path.Combine(Constant.ProgramDirectory, Folder);
private string UserDirectoryPath => Path.Combine(DataLocation.DataDirectory(), Folder);
@ -137,18 +137,31 @@ namespace Flow.Launcher.Core.Resource
var dict = CurrentThemeResourceDictionary();
Style queryBoxStyle = dict["QueryBoxStyle"] as Style;
if (queryBoxStyle != null)
Style querySuggestionBoxStyle = dict["QuerySuggestionBoxStyle"] as Style;
if (queryBoxStyle != null && querySuggestionBoxStyle != null)
{
queryBoxStyle.Setters.Add(new Setter(TextBox.FontFamilyProperty, new FontFamily(Settings.QueryBoxFont)));
queryBoxStyle.Setters.Add(new Setter(TextBox.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(Settings.QueryBoxFontStyle)));
queryBoxStyle.Setters.Add(new Setter(TextBox.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(Settings.QueryBoxFontWeight)));
queryBoxStyle.Setters.Add(new Setter(TextBox.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(Settings.QueryBoxFontStretch)));
var fontFamily = new FontFamily(Settings.QueryBoxFont);
var fontStyle = FontHelper.GetFontStyleFromInvariantStringOrNormal(Settings.QueryBoxFontStyle);
var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(Settings.QueryBoxFontWeight);
var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(Settings.QueryBoxFontStretch);
queryBoxStyle.Setters.Add(new Setter(TextBox.FontFamilyProperty, fontFamily));
queryBoxStyle.Setters.Add(new Setter(TextBox.FontStyleProperty, fontStyle));
queryBoxStyle.Setters.Add(new Setter(TextBox.FontWeightProperty, fontWeight));
queryBoxStyle.Setters.Add(new Setter(TextBox.FontStretchProperty, fontStretch));
var caretBrushPropertyValue = queryBoxStyle.Setters.OfType<Setter>().Any(x => x.Property.Name == "CaretBrush");
var foregroundPropertyValue = queryBoxStyle.Setters.OfType<Setter>().Where(x => x.Property.Name == "Foreground")
.Select(x => x.Value).FirstOrDefault();
if (!caretBrushPropertyValue && foregroundPropertyValue != null) //otherwise BaseQueryBoxStyle will handle styling
queryBoxStyle.Setters.Add(new Setter(TextBox.CaretBrushProperty, foregroundPropertyValue));
// Query suggestion box's font style is aligned with query box
querySuggestionBoxStyle.Setters.Add(new Setter(TextBox.FontFamilyProperty, fontFamily));
querySuggestionBoxStyle.Setters.Add(new Setter(TextBox.FontStyleProperty, fontStyle));
querySuggestionBoxStyle.Setters.Add(new Setter(TextBox.FontWeightProperty, fontWeight));
querySuggestionBoxStyle.Setters.Add(new Setter(TextBox.FontStretchProperty, fontStretch));
}
Style resultItemStyle = dict["ItemTitleStyle"] as Style;

View file

@ -16,6 +16,7 @@ using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Logger;
using System.IO;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core
{
@ -28,11 +29,14 @@ namespace Flow.Launcher.Core
GitHubRepository = gitHubRepository;
}
public async Task UpdateApp(bool silentIfLatestVersion = 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);
@ -62,12 +66,15 @@ namespace Flow.Launcher.Core
if (newReleaseVersion <= currentVersion)
{
if (!silentIfLatestVersion)
if (!silentUpdate)
MessageBox.Show("You already have the latest Flow Launcher version");
updateManager.Dispose();
return;
}
if (!silentUpdate)
api.ShowMsg("Update found", "Updating...");
try
{
await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply);
@ -84,10 +91,10 @@ namespace Flow.Launcher.Core
if (DataLocation.PortableDataLocationInUse())
{
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
FilesFolders.Copy(DataLocation.PortableDataPath, targetDestination);
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination);
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination))
MessageBox.Show(string.Format("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}", 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
{
@ -96,11 +103,15 @@ namespace Flow.Launcher.Core
var newVersionTips = NewVersinoTips(newReleaseVersion.ToString());
MessageBox.Show(newVersionTips);
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]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -1,199 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using hyjiacan.util.p4n;
using hyjiacan.util.p4n.format;
using JetBrains.Annotations;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
namespace Flow.Launcher.Infrastructure
{
public interface IAlphabet
{
string Translate(string stringToTranslate);
}
public class Alphabet : IAlphabet
{
private readonly HanyuPinyinOutputFormat Format = new HanyuPinyinOutputFormat();
private ConcurrentDictionary<string, string[][]> PinyinCache;
private BinaryStorage<Dictionary<string, string[][]>> _pinyinStorage;
private Settings _settings;
public void Initialize([NotNull] Settings settings)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
InitializePinyinHelpers();
}
private void InitializePinyinHelpers()
{
Format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
Stopwatch.Normal("|Flow Launcher.Infrastructure.Alphabet.Initialize|Preload pinyin cache", () =>
{
_pinyinStorage = new BinaryStorage<Dictionary<string, string[][]>>("Pinyin");
lock(_pinyinStorage)
{
var loaded = _pinyinStorage.TryLoad(new Dictionary<string, string[][]>());
PinyinCache = new ConcurrentDictionary<string, string[][]>(loaded);
}
// force pinyin library static constructor initialize
PinyinHelper.toHanyuPinyinStringArray('T', Format);
});
Log.Info($"|Flow Launcher.Infrastructure.Alphabet.Initialize|Number of preload pinyin combination<{PinyinCache.Count}>");
}
public string Translate(string str)
{
return ConvertChineseCharactersToPinyin(str);
}
public string ConvertChineseCharactersToPinyin(string source)
{
if (!_settings.ShouldUsePinyin)
return source;
if (string.IsNullOrEmpty(source))
return source;
if (!ContainsChinese(source))
return source;
var combination = PinyinCombination(source);
var pinyinArray=combination.Select(x => string.Join("", x));
var acronymArray = combination.Select(Acronym).Distinct();
var joinedSingleStringCombination = new StringBuilder();
var all = acronymArray.Concat(pinyinArray);
all.ToList().ForEach(x => joinedSingleStringCombination.Append(x));
return joinedSingleStringCombination.ToString();
}
public void Save()
{
if (!_settings.ShouldUsePinyin)
{
return;
}
lock(_pinyinStorage)
{
_pinyinStorage.Save(PinyinCache.ToDictionary(i => i.Key, i => i.Value));
}
}
private static string[] EmptyStringArray = new string[0];
private static string[][] Empty2DStringArray = new string[0][];
[Obsolete("Not accurate, eg 音乐 will not return yinyue but returns yinle ")]
/// <summary>
/// replace chinese character with pinyin, non chinese character won't be modified
/// <param name="word"> should be word or sentence, instead of single character. e.g. 微软 </param>
/// </summary>
public string[] Pinyin(string word)
{
if (!_settings.ShouldUsePinyin)
{
return EmptyStringArray;
}
var pinyin = word.Select(c =>
{
var pinyins = PinyinHelper.toHanyuPinyinStringArray(c);
var result = pinyins == null ? c.ToString() : pinyins[0];
return result;
}).ToArray();
return pinyin;
}
/// <summmary>
/// replace chinese character with pinyin, non chinese character won't be modified
/// Because we don't have words dictionary, so we can only return all possiblie pinyin combination
/// e.g. 音乐 will return yinyue and yinle
/// <param name="characters"> should be word or sentence, instead of single character. e.g. 微软 </param>
/// </summmary>
public string[][] PinyinCombination(string characters)
{
if (!_settings.ShouldUsePinyin || string.IsNullOrEmpty(characters))
{
return Empty2DStringArray;
}
if (!PinyinCache.ContainsKey(characters))
{
var allPinyins = new List<string[]>();
foreach (var c in characters)
{
var pinyins = PinyinHelper.toHanyuPinyinStringArray(c, Format);
if (pinyins != null)
{
var r = pinyins.Distinct().ToArray();
allPinyins.Add(r);
}
else
{
var r = new[] { c.ToString() };
allPinyins.Add(r);
}
}
var combination = allPinyins.Aggregate(Combination).Select(c => c.Split(';')).ToArray();
PinyinCache[characters] = combination;
return combination;
}
else
{
return PinyinCache[characters];
}
}
public string Acronym(string[] pinyin)
{
var acronym = string.Join("", pinyin.Select(p => p[0]));
return acronym;
}
public bool ContainsChinese(string word)
{
if (!_settings.ShouldUsePinyin)
{
return false;
}
if (word.Length > 40)
{
//Skip strings that are too long string for Pinyin conversion.
return false;
}
var chinese = word.Select(PinyinHelper.toHanyuPinyinStringArray)
.Any(p => p != null);
return chinese;
}
private string[] Combination(string[] array1, string[] array2)
{
if (!_settings.ShouldUsePinyin)
{
return EmptyStringArray;
}
var combination = (
from a1 in array1
from a2 in array2
select $"{a1};{a2}"
).ToArray();
return combination;
}
}
}

View file

@ -8,6 +8,7 @@ namespace Flow.Launcher.Infrastructure
{
public const string FlowLauncher = "Flow.Launcher";
public const string Plugins = "Plugins";
public const string PluginMetadataFileName = "plugin.json";
public const string ApplicationFileName = FlowLauncher + ".exe";
@ -22,14 +23,17 @@ namespace Flow.Launcher.Infrastructure
public static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location.NonNull()).ProductVersion;
public static readonly int ThumbnailSize = 64;
public static readonly string DefaultIcon = Path.Combine(ProgramDirectory, "Images", "app.png");
public static readonly string ErrorIcon = Path.Combine(ProgramDirectory, "Images", "app_error.png");
private static readonly string ImagesDirectory = Path.Combine(ProgramDirectory, "Images");
public static readonly string DefaultIcon = Path.Combine(ImagesDirectory, "app.png");
public static readonly string ErrorIcon = Path.Combine(ImagesDirectory, "app_error.png");
public static readonly string MissingImgIcon = Path.Combine(ImagesDirectory, "app_missing_img.png");
public static string PythonPath;
public static string EverythingSDKPath;
public static readonly string QueryTextBoxIconImagePath = $"{ProgramDirectory}\\Images\\mainsearch.png";
public const string DefaultTheme = "Darker";
public const string Themes = "Themes";
}
}

View file

@ -67,7 +67,6 @@ namespace Flow.Launcher.Infrastructure.Exception
sb.AppendLine($"* IntPtr Length: {IntPtr.Size}");
sb.AppendLine($"* x64: {Environment.Is64BitOperatingSystem}");
sb.AppendLine($"* Python Path: {Constant.PythonPath}");
sb.AppendLine($"* Everything SDK Path: {Constant.EverythingSDKPath}");
sb.AppendLine($"* CLR Version: {Environment.Version}");
sb.AppendLine($"* Installed .NET Framework: ");
foreach (var result in GetFrameworkVersionFromRegistry())

View file

@ -38,6 +38,7 @@
<ItemGroup>
<Compile Include="..\SolutionAssemblyInfo.cs" Link="Properties\SolutionAssemblyInfo.cs" />
<None Include="FodyWeavers.xml" />
</ItemGroup>
<ItemGroup>
@ -48,13 +49,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
<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="Pinyin4DotNet" Version="2016.4.23.4" />
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
<PackageReference Include="System.Runtime" Version="4.3.1" />
<PackageReference Include="ToolGood.Words.Pinyin" Version="3.0.1.4" />
</ItemGroup>
<ItemGroup>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<PropertyChanged />
</Weavers>

View file

@ -1,32 +0,0 @@
using System;
namespace Flow.Launcher.Infrastructure
{
[Obsolete("This class is obsolete and should not be used. Please use the static function StringMatcher.FuzzySearch")]
public class FuzzyMatcher
{
private string query;
private MatchOption opt;
private FuzzyMatcher(string query, MatchOption opt)
{
this.query = query.Trim();
this.opt = opt;
}
public static FuzzyMatcher Create(string query)
{
return new FuzzyMatcher(query, new MatchOption());
}
public static FuzzyMatcher Create(string query, MatchOption opt)
{
return new FuzzyMatcher(query, opt);
}
public MatchResult Evaluate(string str)
{
return StringMatcher.Instance.FuzzyMatch(query, str, opt);
}
}
}

View file

@ -66,18 +66,12 @@ namespace Flow.Launcher.Infrastructure.Http
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)
{
return content;
}
else
{
throw new HttpRequestException($"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>");
}
}
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;
}
}
}

View file

@ -2,44 +2,84 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Media;
namespace Flow.Launcher.Infrastructure.Image
{
[Serializable]
public class ImageUsage
{
public int usage;
public ImageSource imageSource;
public ImageUsage(int usage, ImageSource image)
{
this.usage = usage;
imageSource = image;
}
}
public class ImageCache
{
private const int MaxCached = 5000;
public ConcurrentDictionary<string, int> Usage = new ConcurrentDictionary<string, int>();
private readonly ConcurrentDictionary<string, ImageSource> _data = new ConcurrentDictionary<string, ImageSource>();
private const int MaxCached = 50;
public ConcurrentDictionary<string, ImageUsage> Data { get; private set; } = new ConcurrentDictionary<string, ImageUsage>();
private const int permissibleFactor = 2;
public void Initialization(Dictionary<string, int> usage)
{
foreach (var key in usage.Keys)
{
Data[key] = new ImageUsage(usage[key], null);
}
}
public ImageSource this[string path]
{
get
{
Usage.AddOrUpdate(path, 1, (k, v) => v + 1);
var i = _data[path];
return i;
}
set { _data[path] = value; }
}
if (Data.TryGetValue(path, out var value))
{
value.usage++;
return value.imageSource;
}
public Dictionary<string, int> CleanupAndToDictionary()
=> Usage
.OrderByDescending(o => o.Value)
.Take(MaxCached)
.ToDictionary(i => i.Key, i => i.Value);
return null;
}
set
{
Data.AddOrUpdate(
path,
new ImageUsage(0, value),
(k, v) =>
{
v.imageSource = value;
v.usage++;
return v;
}
);
// To prevent the dictionary from drastically increasing in size by caching images, the dictionary size is not allowed to grow more than the permissibleFactor * maxCached size
// This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time
if (Data.Count > permissibleFactor * MaxCached)
{
// To delete the images from the data dictionary based on the resizing of the Usage Dictionary.
foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key))
Data.TryRemove(key, out _);
}
}
}
public bool ContainsKey(string key)
{
var contains = _data.ContainsKey(key);
var contains = Data.ContainsKey(key) && Data[key] != null;
return contains;
}
public int CacheSize()
{
return _data.Count;
return Data.Count;
}
/// <summary>
@ -47,8 +87,7 @@ namespace Flow.Launcher.Infrastructure.Image
/// </summary>
public int UniqueImagesInCache()
{
return _data.Values.Distinct().Count();
return Data.Values.Select(x => x.imageSource).Distinct().Count();
}
}
}
}

View file

@ -13,12 +13,13 @@ namespace Flow.Launcher.Infrastructure.Image
{
public static class ImageLoader
{
private static readonly ImageCache _imageCache = new ImageCache();
private static readonly ConcurrentDictionary<string, string> _guidToKey = new ConcurrentDictionary<string, string>();
private static readonly bool _enableHashImage = true;
private static readonly ImageCache ImageCache = new ImageCache();
private static BinaryStorage<Dictionary<string, int>> _storage;
private static readonly ConcurrentDictionary<string, string> GuidToKey = new ConcurrentDictionary<string, string>();
private static IImageHashGenerator _hashGenerator;
private static bool EnableImageHash = true;
public static ImageSource DefaultImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon));
private static readonly string[] ImageExtensions =
{
@ -36,25 +37,25 @@ namespace Flow.Launcher.Infrastructure.Image
_storage = new BinaryStorage<Dictionary<string, int>>("Image");
_hashGenerator = new ImageHashGenerator();
_imageCache.Usage = LoadStorageToConcurrentDictionary();
var usage = LoadStorageToConcurrentDictionary();
foreach (var icon in new[] { Constant.DefaultIcon, Constant.ErrorIcon })
foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon })
{
ImageSource img = new BitmapImage(new Uri(icon));
img.Freeze();
_imageCache[icon] = img;
ImageCache[icon] = img;
}
Task.Run(() =>
{
Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () =>
{
_imageCache.Usage.AsParallel().ForAll(x =>
ImageCache.Data.AsParallel().ForAll(x =>
{
Load(x.Key);
});
});
Log.Info($"|ImageLoader.Initialize|Number of preload images is <{_imageCache.Usage.Count}>, Images Number: {_imageCache.CacheSize()}, Unique Items {_imageCache.UniqueImagesInCache()}");
Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
});
}
@ -62,13 +63,13 @@ namespace Flow.Launcher.Infrastructure.Image
{
lock (_storage)
{
_storage.Save(_imageCache.CleanupAndToDictionary());
_storage.Save(ImageCache.Data.Select(x => (x.Key, x.Value.usage)).ToDictionary(x => x.Key, x => x.usage));
}
}
private static ConcurrentDictionary<string, int> LoadStorageToConcurrentDictionary()
{
lock(_storage)
lock (_storage)
{
var loaded = _storage.TryLoad(new Dictionary<string, int>());
@ -106,11 +107,11 @@ namespace Flow.Launcher.Infrastructure.Image
{
if (string.IsNullOrEmpty(path))
{
return new ImageResult(_imageCache[Constant.ErrorIcon], ImageType.Error);
return new ImageResult(ImageCache[Constant.MissingImgIcon], ImageType.Error);
}
if (_imageCache.ContainsKey(path))
if (ImageCache.ContainsKey(path))
{
return new ImageResult(_imageCache[path], ImageType.Cache);
return new ImageResult(ImageCache[path], ImageType.Cache);
}
if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
@ -139,8 +140,8 @@ namespace Flow.Launcher.Infrastructure.Image
Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path} on first try", e);
Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path} on second try", e2);
ImageSource image = _imageCache[Constant.ErrorIcon];
_imageCache[path] = image;
ImageSource image = ImageCache[Constant.MissingImgIcon];
ImageCache[path] = image;
imageResult = new ImageResult(image, ImageType.Error);
}
}
@ -156,9 +157,8 @@ namespace Flow.Launcher.Infrastructure.Image
if (Directory.Exists(path))
{
/* Directories can also have thumbnails instead of shell icons.
* Generating thumbnails for a bunch of folders while scrolling through
* results from Everything makes a big impact on performance and
* Flow.Launcher responsibility.
* Generating thumbnails for a bunch of folder results while scrolling
* could have a big impact on performance and Flow.Launcher responsibility.
* - Solution: just load the icon
*/
type = ImageType.Folder;
@ -192,8 +192,8 @@ namespace Flow.Launcher.Infrastructure.Image
}
else
{
image = _imageCache[Constant.ErrorIcon];
path = Constant.ErrorIcon;
image = ImageCache[Constant.MissingImgIcon];
path = Constant.MissingImgIcon;
}
if (type != ImageType.Error)
@ -213,33 +213,37 @@ namespace Flow.Launcher.Infrastructure.Image
option);
}
public static bool CacheContainImage(string path)
{
return ImageCache.ContainsKey(path) && ImageCache[path] != null;
}
public static ImageSource Load(string path, bool loadFullImage = false)
{
var imageResult = LoadInternal(path, loadFullImage);
var img = imageResult.ImageSource;
if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache)
{
// we need to get image hash
string hash = _enableHashImage ? _hashGenerator.GetHashFromImage(img) : null;
{ // we need to get image hash
string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null;
if (hash != null)
{
if (_guidToKey.TryGetValue(hash, out string key))
{
// image already exists
img = _imageCache[key];
if (GuidToKey.TryGetValue(hash, out string key))
{ // image already exists
img = ImageCache[key] ?? img;
}
else
{
// new guid
_guidToKey[hash] = path;
{ // new guid
GuidToKey[hash] = path;
}
}
// update cache
_imageCache[path] = img;
ImageCache[path] = img;
}
return img;
}

View file

@ -0,0 +1,85 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Flow.Launcher.Infrastructure.UserSettings;
using ToolGood.Words.Pinyin;
namespace Flow.Launcher.Infrastructure
{
public interface IAlphabet
{
string Translate(string stringToTranslate);
}
public class PinyinAlphabet : IAlphabet
{
private ConcurrentDictionary<string, string> _pinyinCache = new ConcurrentDictionary<string, string>();
private Settings _settings;
public void Initialize([NotNull] Settings settings)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
}
public string Translate(string content)
{
if (_settings.ShouldUsePinyin)
{
if (!_pinyinCache.ContainsKey(content))
{
if (WordsHelper.HasChinese(content))
{
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(' ');
bool pre = false;
for (int i = 0; i < resultList.Length; i++)
{
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
{
resultBuilder.Append(' ');
resultBuilder.Append(resultList[i]);
pre = true;
}
else
{
if (pre)
{
pre = false;
resultBuilder.Append(' ');
}
resultBuilder.Append(resultList[i]);
}
}
return _pinyinCache[content] = resultBuilder.ToString();
}
else
{
return content;
}
}
else
{
return _pinyinCache[content];
}
}
else
{
return content;
}
}
}
}

View file

@ -21,18 +21,6 @@ namespace Flow.Launcher.Infrastructure
public static StringMatcher Instance { get; internal set; }
[Obsolete("This method is obsolete and should not be used. Please use the static function StringMatcher.FuzzySearch")]
public static int Score(string source, string target)
{
return FuzzySearch(target, source).Score;
}
[Obsolete("This method is obsolete and should not be used. Please use the static function StringMatcher.FuzzySearch")]
public static bool IsMatch(string source, string target)
{
return Score(source, target) > 0;
}
public static MatchResult FuzzySearch(string query, string stringToCompare)
{
return Instance.FuzzyMatch(query, stringToCompare);
@ -83,9 +71,18 @@ namespace Flow.Launcher.Infrastructure
bool allSubstringsContainedInCompareString = true;
var indexList = new List<int>();
List<int> spaceIndices = new List<int>();
for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++)
{
// 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)
{
spaceIndices.Add(compareStringIndex);
}
if (fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex])
{
matchFoundInPreviousLoop = false;
@ -147,15 +144,31 @@ namespace Flow.Launcher.Infrastructure
// proceed to calculate score if every char or substring without whitespaces matched
if (allQuerySubstringsMatched)
{
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
var nearestSpaceIndex = CalculateClosestSpaceIndex(spaceIndices, firstMatchIndex);
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
return new MatchResult(true, UserSettingSearchPrecision, indexList, score);
}
return new MatchResult (false, UserSettingSearchPrecision);
return new MatchResult(false, UserSettingSearchPrecision);
}
private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex,
// 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)
{
return -1;
}
else
{
int? ind = spaceIndices.OrderBy(item => (firstMatchIndex - item)).Where(item => firstMatchIndex > item).FirstOrDefault();
int closestSpaceIndex = ind ?? -1;
return closestSpaceIndex;
}
}
private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex,
string fullStringToCompareWithoutCase, string currentQuerySubstring)
{
var allMatch = true;
@ -298,18 +311,6 @@ namespace Flow.Launcher.Infrastructure
public class MatchOption
{
/// <summary>
/// prefix of match char, use for hightlight
/// </summary>
[Obsolete("this is never used")]
public string Prefix { get; set; } = "";
/// <summary>
/// suffix of match char, use for hightlight
/// </summary>
[Obsolete("this is never used")]
public string Suffix { get; set; } = "";
public bool IgnoreCase { get; set; } = true;
}
}

View file

@ -30,5 +30,6 @@ namespace Flow.Launcher.Infrastructure.UserSettings
}
public static readonly string PluginsDirectory = Path.Combine(DataDirectory(), Constant.Plugins);
public static readonly string PluginSettingsDirectory = Path.Combine(DataDirectory(), "Settings", Constant.Plugins);
}
}

View file

@ -15,6 +15,16 @@ namespace Flow.Launcher.Infrastructure.UserSettings
if (Plugins.ContainsKey(metadata.ID))
{
var settings = Plugins[metadata.ID];
// TODO: Remove. This is one off for 1.2.0 release.
// Introduced a new action keyword in Explorer, so need to update plugin setting in the UserData folder.
// This kind of plugin meta update should be handled by a dedicated method trigger by version bump.
if (metadata.ID == "572be03c74c642baae319fc283e561a8" && metadata.ActionKeywords.Count != settings.ActionKeywords.Count)
settings.ActionKeywords = metadata.ActionKeywords;
if (string.IsNullOrEmpty(settings.Version))
settings.Version = metadata.Version;
if (settings.ActionKeywords?.Count > 0)
{
metadata.ActionKeywords = settings.ActionKeywords;
@ -28,6 +38,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
ID = metadata.ID,
Name = metadata.Name,
Version = metadata.Version,
ActionKeywords = metadata.ActionKeywords,
Disabled = metadata.Disabled
};
@ -39,6 +50,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
public string ID { get; set; }
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
/// <summary>

View file

@ -9,10 +9,18 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
public class Settings : BaseModel
{
private string language = "en";
public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}";
public string OpenResultModifiers { get; set; } = KeyConstant.Alt;
public bool ShowOpenResultHotkey { get; set; } = true;
public string Language { get; set; } = "en";
public string Language
{
get => language; set {
language = value;
OnPropertyChanged();
}
}
public string Theme { get; set; } = Constant.DefaultTheme;
public bool UseDropShadowEffect { get; set; } = false;
public string QueryBoxFont { get; set; } = FontFamily.GenericSansSerif.Name;
@ -70,12 +78,6 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public PluginsSettings PluginSettings { get; set; } = new PluginsSettings();
public ObservableCollection<CustomPluginHotkey> CustomPluginHotkeys { get; set; } = new ObservableCollection<CustomPluginHotkey>();
[Obsolete]
public double Opacity { get; set; } = 1;
[Obsolete]
public OpacityMode OpacityMode { get; set; } = OpacityMode.Normal;
public bool DontPromptUpdateMsg { get; set; }
public bool EnableUpdateLog { get; set; }
@ -108,12 +110,4 @@ namespace Flow.Launcher.Infrastructure.UserSettings
Empty,
Preserved
}
[Obsolete]
public enum OpacityMode
{
Normal = 0,
LayeredWindow = 1,
DWM = 2
}
}

View file

@ -11,23 +11,6 @@ namespace Flow.Launcher.Plugin
List<Result> LoadContextMenus(Result selectedResult);
}
[Obsolete("If a plugin has a action keyword, then it is exclusive. This interface will be remove in v1.3.0")]
public interface IExclusiveQuery : IFeatures
{
[Obsolete("If a plugin has a action keyword, then it is exclusive. This method will be remove in v1.3.0")]
bool IsExclusiveQuery(Query query);
}
/// <summary>
/// Represent plugin query will be executed in UI thread directly. Don't do long-running operation in Query method if you implement this interface
/// <remarks>This will improve the performance of instant search like websearch or cmd plugin</remarks>
/// </summary>
[Obsolete("Flow Launcher is fast enough now, executed on ui thread is no longer needed")]
public interface IInstantQuery : IFeatures
{
bool IsInstantQuery(string query);
}
/// <summary>
/// Represent plugins that support internationalization
/// </summary>

View file

@ -1,9 +0,0 @@
using System;
namespace Flow.Launcher.Plugin.Features
{
[Obsolete("Delete Flow.Launcher.Plugin.Features using directive, " +
"and use Flow.Launcher.Plugin.Feature.IContextMenu instead, " +
"this method will be removed in v1.3.0")]
public interface IContextMenu { }
}

View file

@ -1,9 +0,0 @@
using System;
namespace Flow.Launcher.Plugin.Features
{
[Obsolete("Delete Flow.Launcher.Plugin.Features using directive, " +
"and use Flow.Launcher.Plugin.Feature.IInstantQuery instead, " +
"this method will be removed in v1.3.0")]
public interface IExclusiveQuery { }
}

View file

@ -1,9 +0,0 @@
using System;
namespace Flow.Launcher.Plugin.Features
{
[Obsolete("Delete Flow.Launcher.Plugin.Features using directive, " +
"and use Flow.Launcher.Plugin.Feature.IInstantQuery instead, " +
"this method will be removed in v1.3.0")]
public interface IInstantQuery { }
}

View file

@ -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.0.0</Version>
<PackageVersion>1.0.0-beta3</PackageVersion>
<AssemblyVersion>1.0.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion>
<Version>1.3.0</Version>
<PackageVersion>1.3.0</PackageVersion>
<AssemblyVersion>1.3.0</AssemblyVersion>
<FileVersion>1.3.0</FileVersion>
<PackageId>Flow.Launcher.Plugin</PackageId>
<Authors>Flow-Launcher</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
@ -60,12 +60,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
<PackageReference Include="Mono.Cecil" Version="0.11.2" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="PropertyChanged.Fody" Version="2.5.13" />
<PackageReference Include="System.Runtime" Version="4.3.1" />
</ItemGroup>
</Project>

View file

@ -8,15 +8,6 @@ namespace Flow.Launcher.Plugin
/// </summary>
public interface IPublicAPI
{
/// <summary>
/// Push result to query box
/// </summary>
/// <param name="query"></param>
/// <param name="plugin"></param>
/// <param name="results"></param>
[Obsolete("This method will be removed in Flow Launcher 1.3")]
void PushResults(Query query, PluginMetadata plugin, List<Result> results);
/// <summary>
/// Change Flow.Launcher query
/// </summary>
@ -27,42 +18,11 @@ namespace Flow.Launcher.Plugin
/// </param>
void ChangeQuery(string query, bool requery = false);
/// <summary>
/// Just change the query text, this won't raise search
/// </summary>
/// <param name="query"></param>
[Obsolete]
void ChangeQueryText(string query, bool selectAll = false);
/// <summary>
/// Close Flow Launcher
/// </summary>
[Obsolete]
void CloseApp();
/// <summary>
/// Restart Flow Launcher
/// </summary>
void RestartApp();
/// <summary>
/// Restart Flow Launcher
/// </summary>
[Obsolete("Use RestartApp instead. This method will be removed in Flow Launcher 1.3")]
void RestarApp();
/// <summary>
/// Hide Flow Launcher
/// </summary>
[Obsolete]
void HideApp();
/// <summary>
/// Show Flow Launcher
/// </summary>
[Obsolete]
void ShowApp();
/// <summary>
/// Save all Flow Launcher settings
/// </summary>
@ -103,24 +63,6 @@ namespace Flow.Launcher.Plugin
/// </summary>
void OpenSettingDialog();
/// <summary>
/// Show loading animation
/// </summary>
[Obsolete("automatically start")]
void StartLoadingBar();
/// <summary>
/// Stop loading animation
/// </summary>
[Obsolete("automatically stop")]
void StopLoadingBar();
/// <summary>
/// Install Flow Launcher plugin
/// </summary>
/// <param name="path">Plugin path (ends with .flowlauncher)</param>
void InstallPlugin(string path);
/// <summary>
/// Get translation of current language
/// You need to implement IPluginI18n if you want to support multiple languages for your plugin

View file

@ -43,9 +43,6 @@ namespace Flow.Launcher.Plugin
return Name;
}
[Obsolete("Use IcoPath")]
public string FullIcoPath => IcoPath;
/// <summary>
/// Init time include both plugin load time and init time
/// </summary>

View file

@ -94,14 +94,5 @@ namespace Flow.Launcher.Plugin
}
public override string ToString() => RawQuery;
[Obsolete("Use ActionKeyword, this property will be removed in v1.3.0")]
public string ActionName { get; internal set; }
[Obsolete("Use Search instead, this property will be removed in v1.3.0")]
public List<string> ActionParameters { get; internal set; }
[Obsolete("Use Search instead, this method will be removed in v1.3.0")]
public string GetAllRemainingParameter() => Search;
}
}

View file

@ -104,21 +104,6 @@ namespace Flow.Launcher.Plugin
return Title + SubTitle;
}
/// <summary>
/// Context menus associate with this result
/// </summary>
[Obsolete("Use IContextMenu instead")]
public List<Result> ContextMenu { get; set; }
[Obsolete("Use Object initializer instead")]
public Result(string Title, string IcoPath, string SubTitle = null)
{
this.Title = Title;
this.IcoPath = IcoPath;
this.SubTitle = SubTitle;
}
public Result() { }
/// <summary>
@ -130,5 +115,15 @@ namespace Flow.Launcher.Plugin
/// Plugin ID that generated this result
/// </summary>
public string PluginID { get; internal set; }
/// <summary>
/// Show message as ToolTip on result Title hover over
/// </summary>
public string TitleToolTip { get; set; }
/// <summary>
/// Show message as ToolTip on result SubTitle hover over
/// </summary>
public string SubTitleToolTip { get; set; }
}
}

View file

@ -8,7 +8,16 @@ namespace Flow.Launcher.Plugin.SharedCommands
public static class FilesFolders
{
private const string FileExplorerProgramName = "explorer";
public static void Copy(this string sourcePath, string targetPath)
private const string FileExplorerProgramEXE = "explorer.exe";
/// <summary>
/// Copies the folder and all of its files and folders
/// including subfolders to the target location
/// </summary>
/// <param name="sourcePath"></param>
/// <param name="targetPath"></param>
public static void CopyAll(this string sourcePath, string targetPath)
{
// Get the subdirectories for the specified directory.
DirectoryInfo dir = new DirectoryInfo(sourcePath);
@ -41,7 +50,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
foreach (DirectoryInfo subdir in dirs)
{
string temppath = Path.Combine(targetPath, subdir.Name);
Copy(subdir.FullName, temppath);
CopyAll(subdir.FullName, temppath);
}
}
catch (Exception e)
@ -105,7 +114,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
return Directory.Exists(path);
}
public static bool FileExits(this string filePath)
public static bool FileExists(this string filePath)
{
return File.Exists(filePath);
}
@ -115,7 +124,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
var psi = new ProcessStartInfo { FileName = FileExplorerProgramName, UseShellExecute = true, Arguments = fileOrFolderPath };
try
{
if (LocationExists(fileOrFolderPath) || FileExits(fileOrFolderPath))
if (LocationExists(fileOrFolderPath) || FileExists(fileOrFolderPath))
Process.Start(psi);
}
catch (Exception e)
@ -128,6 +137,11 @@ namespace Flow.Launcher.Plugin.SharedCommands
}
}
public static void OpenContainingFolder(string path)
{
Process.Start(FileExplorerProgramEXE, $" /select,\"{path}\"");
}
///<summary>
/// This checks whether a given string is a directory path or network location string.
/// It does not check if location actually exists.
@ -164,7 +178,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
///<summary>
/// Gets the previous level directory from a path string.
/// Checks that previous level directory exists and returns it
/// as a path string, or empty string if doesn't exit
/// as a path string, or empty string if doesn't exist
///</summary>
public static string GetPreviousExistingDirectory(Func<string, bool> locationExists, string path)
{

View file

@ -1,3 +1,4 @@
using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.IO;
@ -7,12 +8,37 @@ namespace Flow.Launcher.Plugin.SharedCommands
{
public static class SearchWeb
{
private static string GetDefaultBrowserPath()
{
string name = string.Empty;
try
{
using var regDefault = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice", false);
var stringDefault = regDefault.GetValue("ProgId");
using var regKey = Registry.ClassesRoot.OpenSubKey(stringDefault + "\\shell\\open\\command", false);
name = regKey.GetValue(null).ToString().ToLower().Replace("\"", "");
if (!name.EndsWith("exe"))
name = name.Substring(0, name.LastIndexOf(".exe") + 4);
}
catch
{
return string.Empty;
}
return name;
}
/// <summary>
/// Opens search in a new browser. If no browser path is passed in then Chrome is used.
/// Leave browser path blank to use Chrome.
/// </summary>
public static void NewBrowserWindow(this string url, string browserPath = "")
{
browserPath = string.IsNullOrEmpty(browserPath) ? GetDefaultBrowserPath() : browserPath;
var browserExecutableName = browserPath?
.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None)
.Last();
@ -44,7 +70,9 @@ namespace Flow.Launcher.Plugin.SharedCommands
/// </summary>
public static void NewTabInBrowser(this string url, string browserPath = "")
{
var psi = new ProcessStartInfo() { UseShellExecute = true};
browserPath = string.IsNullOrEmpty(browserPath) ? GetDefaultBrowserPath() : browserPath;
var psi = new ProcessStartInfo() { UseShellExecute = true };
try
{
if (!string.IsNullOrEmpty(browserPath))

View file

@ -77,7 +77,7 @@ namespace Flow.Launcher.Test
}
[TestCase("Chrome")]
public void WhenGivenNotAllCharactersFoundInSearchStringThenShouldReturnZeroScore(string searchString)
public void WhenNotAllCharactersFoundInSearchString_ThenShouldReturnZeroScore(string searchString)
{
var compareString = "Can have rum only in my glass";
var matcher = new StringMatcher();
@ -92,7 +92,7 @@ namespace Flow.Launcher.Test
[TestCase("cand")]
[TestCase("cpywa")]
[TestCase("ccs")]
public void WhenGivenStringsAndAppliedPrecisionFilteringThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm)
public void GivenQueryString_WhenAppliedPrecisionFiltering_ThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm)
{
var results = new List<Result>();
var matcher = new StringMatcher();
@ -107,7 +107,10 @@ 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();
var filteredResult = results.Where(result => result.Score >= precisionScore)
.Select(result => result)
.OrderByDescending(x => x.Score)
.ToList();
Debug.WriteLine("");
Debug.WriteLine("###############################################");
@ -124,20 +127,22 @@ namespace Flow.Launcher.Test
}
[TestCase(Chrome, Chrome, 157)]
[TestCase(Chrome, LastIsChrome, 103)]
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 21)]
[TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 15)]
[TestCase(Chrome, LastIsChrome, 147)]
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 25)]
[TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 21)]
[TestCase(Chrome, CandyCrushSagaFromKing, 0)]
[TestCase("sql", MicrosoftSqlServerManagementStudio, 56)]
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, 99)]//double spacing intended
public void WhenGivenQueryStringThenShouldReturnCurrentScoring(string queryString, string compareString, int expectedScore)
[TestCase("sql", MicrosoftSqlServerManagementStudio, 110)]
[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 rawScore = matcher.FuzzyMatch(queryString, compareString).RawScore;
// Should
Assert.AreEqual(expectedScore, rawScore, $"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}");
Assert.AreEqual(expectedScore, rawScore,
$"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}");
}
[TestCase("goo", "Google Chrome", StringMatcher.SearchPrecisionScore.Regular, true)]
@ -150,7 +155,7 @@ namespace Flow.Launcher.Test
[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)]
public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual(
public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual(
string queryString,
string compareString,
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
@ -185,8 +190,8 @@ namespace Flow.Launcher.Test
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql serv", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sqlserv", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql servman", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[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)]
@ -199,7 +204,7 @@ namespace Flow.Launcher.Test
[TestCase("cod", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("code", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("codes", "Visual Studio Codes", StringMatcher.SearchPrecisionScore.Regular, true)]
public void WhenGivenQueryShouldReturnResultsContainingAllQuerySubstrings(
public void WhenGivenQuery_ShouldReturnResults_ContainingAllQuerySubstrings(
string queryString,
string compareString,
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
@ -225,5 +230,60 @@ namespace Flow.Launcher.Test
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
$"Precision Score: {(int)expectedPrecisionScore}");
}
[TestCase("man", "Task Manager", "eManual")]
[TestCase("term", "Windows Terminal", "Character Map")]
[TestCase("winterm", "Windows Terminal", "Cygwin64 Terminal")]
public void WhenGivenAQuery_Scoring_ShouldGiveMoreWeightToStartOfNewWord(
string queryString, string compareString1, string compareString2)
{
// When
var matcher = new StringMatcher { UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular };
// Given
var compareString1Result = matcher.FuzzyMatch(queryString, compareString1);
var compareString2Result = matcher.FuzzyMatch(queryString, compareString2);
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("###############################################");
Debug.WriteLine("");
// Should
Assert.True(compareString1Result.Score > compareString2Result.Score,
$"Query: \"{queryString}\"{Environment.NewLine} " +
$"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{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 secondName, string secondDescription, string secondExecutableName)
{
// Act
var matcher = new StringMatcher();
var firstNameMatch = matcher.FuzzyMatch(queryString, firstName).RawScore;
var firstDescriptionMatch = matcher.FuzzyMatch(queryString, firstDescription).RawScore;
var firstExecutableNameMatch = matcher.FuzzyMatch(queryString, firstExecutableName).RawScore;
var secondNameMatch = matcher.FuzzyMatch(queryString, secondName).RawScore;
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();
// Assert
Assert.IsTrue(firstScore > secondScore,
$"Query: \"{queryString}\"{Environment.NewLine} " +
$"Name of first: \"{firstName}\", Final Score: {firstScore}{Environment.NewLine}" +
$"Should be greater than{ Environment.NewLine}" +
$"Name of second: \"{secondName}\", Final Score: {secondScore}{Environment.NewLine}");
}
}
}

View file

@ -58,8 +58,8 @@ namespace Flow.Launcher.Test.Plugins
$"Actual: {result}{Environment.NewLine}");
}
[TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType FROM SystemIndex WHERE directory='file:C:\\'")]
[TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, 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:\\'")]
[TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\'")]
public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString)
{
// Given
@ -74,7 +74,7 @@ namespace Flow.Launcher.Test.Plugins
$"Actual string was: {queryString}{Environment.NewLine}");
}
[TestCase("C:\\SomeFolder\\flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType " +
[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'")]
@ -126,7 +126,7 @@ namespace Flow.Launcher.Test.Plugins
$"Actual string was: {resultString}{Environment.NewLine}");
}
[TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemPathDisplay\", \"System.ItemType\" " +
[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:'")]
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString(
@ -184,6 +184,55 @@ namespace Flow.Launcher.Test.Plugins
$"Actual number of results is {results.Count} {Environment.NewLine}");
}
[TestCase(@"some words", @"FREETEXT('some words')")]
public void GivenWindowsIndexSearch_WhenQueryWhereRestrictionsIsForFileContentSearch_ThenShouldReturnFreeTextString(
string querySearchString, string expectedString)
{
// Given
var queryConstructor = new QueryConstructor(new Settings());
//When
var resultString = queryConstructor.QueryWhereRestrictionsForFileContentSearch(querySearchString);
// Then
Assert.IsTrue(resultString == expectedString,
$"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
$"Actual string was: {resultString}{Environment.NewLine}");
}
[TestCase("some words", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " +
"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:'")]
public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString(
string userSearchString, string expectedString)
{
// Given
var queryConstructor = new QueryConstructor(new Settings());
//When
var resultString = queryConstructor.QueryForFileContentSearch(userSearchString);
// Then
Assert.IsTrue(resultString == expectedString,
$"Expected query string: {expectedString}{Environment.NewLine} " +
$"Actual string was: {resultString}{Environment.NewLine}");
}
public void GivenQuery_WhenActionKeywordForFileContentSearchExists_ThenFileContentSearchRequiredShouldReturnTrue()
{
// Given
var query = new Query { ActionKeyword = "doc:", Search = "search term" };
var searchManager = new SearchManager(new Settings(), new PluginInitContext());
// When
var result = searchManager.IsFileContentSearch(query.ActionKeyword);
// Then
Assert.IsTrue(result,
$"Expected True for file content search. {Environment.NewLine} " +
$"Actual result was: {result}{Environment.NewLine}");
}
[TestCase(@"c:\\", false)]
[TestCase(@"i:\", true)]
[TestCase(@"\c:\", false)]

View file

@ -13,9 +13,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{3A73
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher", "Flow.Launcher\Flow.Launcher.csproj", "{DB90F671-D861-46BB-93A3-F1304F5BA1C5}"
ProjectSection(ProjectDependencies) = postProject
{230AE83F-E92E-4E69-8355-426B305DA9C0} = {230AE83F-E92E-4E69-8355-426B305DA9C0}
{1EE20B48-82FB-48A2-8086-675D6DDAB4F0} = {1EE20B48-82FB-48A2-8086-675D6DDAB4F0}
{0B9DE348-9361-4940-ADB6-F5953BFFCCEC} = {0B9DE348-9361-4940-ADB6-F5953BFFCCEC}
{4792A74A-0CEA-4173-A8B2-30E6764C6217} = {4792A74A-0CEA-4173-A8B2-30E6764C6217}
{FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4}
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}
{59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E}
@ -24,14 +24,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher", "Flow.Launc
{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}
{049490F0-ECD2-4148-9B39-2135EC346EBE} = {049490F0-ECD2-4148-9B39-2135EC346EBE}
{403B57F2-1856-4FC7-8A24-36AB346B763E} = {403B57F2-1856-4FC7-8A24-36AB346B763E}
{588088F4-3262-4F9F-9663-A05DE12534C3} = {588088F4-3262-4F9F-9663-A05DE12534C3}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Infrastructure", "Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj", "{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.PluginManagement", "Plugins\Flow.Launcher.Plugin.PluginManagement\Flow.Launcher.Plugin.PluginManagement.csproj", "{049490F0-ECD2-4148-9B39-2135EC346EBE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Core", "Flow.Launcher.Core\Flow.Launcher.Core.csproj", "{B749F0DB-8E75-47DB-9E5E-265D16D0C0D2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Program", "Plugins\Flow.Launcher.Plugin.Program\Flow.Launcher.Plugin.Program.csproj", "{FDB3555B-58EF-4AE6-B5F1-904719637AB4}"
@ -48,13 +46,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Url",
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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Everything", "Plugins\Flow.Launcher.Plugin.Everything\Flow.Launcher.Plugin.Everything.csproj", "{230AE83F-E92E-4E69-8355-426B305DA9C0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FFD651C7-0546-441F-BC8C-D4EE8FD01EA7}"
ProjectSection(SolutionItems) = preProject
.gitattributes = .gitattributes
.gitignore = .gitignore
appveyor.yml = appveyor.yml
Directory.Build.targets = Directory.Build.targets
Scripts\flowlauncher.nuspec = Scripts\flowlauncher.nuspec
LICENSE = LICENSE
Scripts\post_build.ps1 = Scripts\post_build.ps1
@ -62,18 +59,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
SolutionAssemblyInfo.cs = SolutionAssemblyInfo.cs
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloWorldCSharp", "Plugins\HelloWorldCSharp\HelloWorldCSharp.csproj", "{03FFA443-5F50-48D5-8869-F3DF316803AA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Shell", "Plugins\Flow.Launcher.Plugin.Shell\Flow.Launcher.Plugin.Shell.csproj", "{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.BrowserBookmark", "Plugins\Flow.Launcher.Plugin.BrowserBookmark\Flow.Launcher.Plugin.BrowserBookmark.csproj", "{9B130CC5-14FB-41FF-B310-0A95B6894C37}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Calculator", "Plugins\Flow.Launcher.Plugin.Calculator\Flow.Launcher.Plugin.Calculator.csproj", "{59BD9891-3837-438A-958D-ADC7F91F6F7E}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "HelloWorldFSharp", "Plugins\HelloWorldFSharp\HelloWorldFSharp.fsproj", "{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Explorer", "Plugins\Flow.Launcher.Plugin.Explorer\Flow.Launcher.Plugin.Explorer.csproj", "{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.ProcessKiller", "Plugins\Flow.Launcher.Plugin.ProcessKiller\Flow.Launcher.Plugin.ProcessKiller.csproj", "{588088F4-3262-4F9F-9663-A05DE12534C3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.PluginsManager", "Plugins\Flow.Launcher.Plugin.PluginsManager\Flow.Launcher.Plugin.PluginsManager.csproj", "{4792A74A-0CEA-4173-A8B2-30E6764C6217}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -132,18 +129,6 @@ Global
{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|x64.Build.0 = Release|Any CPU
{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|x86.ActiveCfg = Release|Any CPU
{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|x86.Build.0 = Release|Any CPU
{049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|x64.ActiveCfg = Debug|Any CPU
{049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|x64.Build.0 = Debug|Any CPU
{049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|x86.ActiveCfg = Debug|Any CPU
{049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|x86.Build.0 = Debug|Any CPU
{049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|Any CPU.Build.0 = Release|Any CPU
{049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|x64.ActiveCfg = Release|Any CPU
{049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|x64.Build.0 = Release|Any CPU
{049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|x86.ActiveCfg = Release|Any CPU
{049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|x86.Build.0 = Release|Any CPU
{B749F0DB-8E75-47DB-9E5E-265D16D0C0D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B749F0DB-8E75-47DB-9E5E-265D16D0C0D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B749F0DB-8E75-47DB-9E5E-265D16D0C0D2}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -241,30 +226,6 @@ Global
{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
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Debug|x64.ActiveCfg = Debug|Any CPU
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Debug|x64.Build.0 = Debug|Any CPU
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Debug|x86.ActiveCfg = Debug|Any CPU
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Debug|x86.Build.0 = Debug|Any CPU
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Release|Any CPU.Build.0 = Release|Any CPU
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Release|x64.ActiveCfg = Release|Any CPU
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Release|x64.Build.0 = Release|Any CPU
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Release|x86.ActiveCfg = Release|Any CPU
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Release|x86.Build.0 = Release|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Debug|x64.ActiveCfg = Debug|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Debug|x64.Build.0 = Debug|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Debug|x86.ActiveCfg = Debug|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Debug|x86.Build.0 = Debug|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Release|Any CPU.Build.0 = Release|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Release|x64.ActiveCfg = Release|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Release|x64.Build.0 = Release|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Release|x86.ActiveCfg = Release|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.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
@ -301,18 +262,6 @@ Global
{59BD9891-3837-438A-958D-ADC7F91F6F7E}.Release|x64.Build.0 = Release|Any CPU
{59BD9891-3837-438A-958D-ADC7F91F6F7E}.Release|x86.ActiveCfg = Release|Any CPU
{59BD9891-3837-438A-958D-ADC7F91F6F7E}.Release|x86.Build.0 = Release|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Debug|x64.ActiveCfg = Debug|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Debug|x64.Build.0 = Debug|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Debug|x86.ActiveCfg = Debug|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Debug|x86.Build.0 = Debug|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|Any CPU.Build.0 = Release|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|x64.ActiveCfg = Release|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|x64.Build.0 = Release|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|x86.ActiveCfg = Release|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|x86.Build.0 = Release|Any CPU
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -325,12 +274,35 @@ Global
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|x64.Build.0 = Release|Any CPU
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|x86.ActiveCfg = Release|Any CPU
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|x86.Build.0 = Release|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Debug|x64.ActiveCfg = Debug|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Debug|x64.Build.0 = Debug|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Debug|x86.ActiveCfg = Debug|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Debug|x86.Build.0 = Debug|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|Any CPU.Build.0 = Release|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|x64.ActiveCfg = Release|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|x64.Build.0 = Release|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|x86.ActiveCfg = Release|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|x86.Build.0 = Release|Any CPU
{4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|x64.ActiveCfg = Debug|Any CPU
{4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|x64.Build.0 = Debug|Any CPU
{4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|x86.ActiveCfg = Debug|Any CPU
{4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|x86.Build.0 = Debug|Any CPU
{4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|Any CPU.Build.0 = Release|Any CPU
{4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|x64.ActiveCfg = Release|Any CPU
{4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|x64.Build.0 = Release|Any CPU
{4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|x86.ActiveCfg = Release|Any CPU
{4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{049490F0-ECD2-4148-9B39-2135EC346EBE} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{403B57F2-1856-4FC7-8A24-36AB346B763E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{1EE20B48-82FB-48A2-8086-675D6DDAB4F0} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
@ -338,13 +310,12 @@ Global
{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}
{230AE83F-E92E-4E69-8355-426B305DA9C0} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{03FFA443-5F50-48D5-8869-F3DF316803AA} = {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}
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{588088F4-3262-4F9F-9663-A05DE12534C3} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{4792A74A-0CEA-4173-A8B2-30E6764C6217} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F26ACB50-3F6C-4907-B0C9-1ADACC1D0DED}

View file

@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
@ -28,7 +29,7 @@ namespace Flow.Launcher
private SettingWindowViewModel _settingsVM;
private readonly Updater _updater = new Updater(Flow.Launcher.Properties.Settings.Default.GithubRepo);
private readonly Portable _portable = new Portable();
private readonly Alphabet _alphabet = new Alphabet();
private readonly PinyinAlphabet _alphabet = new PinyinAlphabet();
private StringMatcher _stringMatcher;
[STAThread]
@ -85,6 +86,8 @@ namespace Flow.Launcher
Http.Proxy = _settings.Proxy;
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
RegisterExitEvents();
AutoStartup();
@ -118,12 +121,12 @@ namespace Flow.Launcher
var timer = new Timer(1000 * 60 * 60 * 5);
timer.Elapsed += async (s, e) =>
{
await _updater.UpdateApp();
await _updater.UpdateApp(API);
};
timer.Start();
// check updates on startup
await _updater.UpdateApp();
await _updater.UpdateApp(API);
}
});
}

View file

@ -57,7 +57,6 @@ namespace Flow.Launcher
App.API.ChangeQuery(pluginHotkey.ActionKeyword);
Application.Current.MainWindow.Visibility = Visibility.Visible;
});
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("success"));
}
else
{
@ -76,7 +75,6 @@ namespace Flow.Launcher
App.API.ChangeQuery(updateCustomHotkey.ActionKeyword);
Application.Current.MainWindow.Visibility = Visibility.Visible;
});
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("success"));
}
Close();

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>
@ -72,23 +72,13 @@
<ItemGroup>
<PackageReference Include="InputSimulator" Version="1.0.4" />
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
<PackageReference Include="Mages" Version="1.6.0" />
<PackageReference Include="ModernWpfUI" Version="0.8.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NHotkey.Wpf" Version="1.2.1" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.9.0" />
<PackageReference Include="NuGet.CommandLine" Version="5.4.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="PropertyChanged.Fody" Version="2.5.13" />
<PackageReference Include="System.Data.OleDb" Version="4.7.1" />
<PackageReference Include="System.Data.SQLite" Version="1.0.112" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.112" />
<PackageReference Include="System.Runtime" Version="4.3.1" />
<PackageReference Include="tlbimp-Microsoft.Search.Interop" Version="1.0.0" />
<PackageReference Include="UnidecodeSharp" Version="1.0.0" />
</ItemGroup>
<ItemGroup>

View file

@ -41,8 +41,7 @@ namespace Flow.Launcher.Helper
public static string DependenciesInfo()
{
var info = $"\nPython Path: {Constant.PythonPath}" +
$"\nEverything SDK Path: {Constant.EverythingSDKPath}";
var info = $"\nPython Path: {Constant.PythonPath}";
return info;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -96,7 +96,7 @@
<system:String x:Key="version">Version</system:String>
<system:String x:Key="about_activate_times">You have activated Flow Launcher {0} times</system:String>
<system:String x:Key="checkUpdates">Check for Updates</system:String>
<system:String x:Key="newVersionTips">New version {0} is available, please restart Flow Launcher.</system:String>
<system:String x:Key="newVersionTips">New version {0} is available, would you like to restart Flow Launcher to use the update?</system:String>
<system:String x:Key="checkUpdatesFailed">Check updates failed, please check your connection and proxy settings to api.github.com.</system:String>
<system:String x:Key="downloadUpdatesFailed">
Download updates failed, please check your connection and proxy settings to github-cloud.s3.amazonaws.com,
@ -111,7 +111,7 @@
<system:String x:Key="done">Done</system:String>
<system:String x:Key="cannotFindSpecifiedPlugin">Can't find specified plugin</system:String>
<system:String x:Key="newActionKeywordsCannotBeEmpty">New Action Keyword can't be empty</system:String>
<system:String x:Key="newActionKeywordsHasBeenAssigned">New Action Keywords have been assigned to another plugin, please assign other new action keyword</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="actionkeyword_tips">Use * if you don't want to specify an action keyword</system:String>

View file

@ -1,12 +1,12 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
<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">
<!--MainWindow-->
<system:String x:Key="registerHotkeyFailed">Nepodarilo sa registrovať klávesovú skratku {0}</system:String>
<system:String x:Key="couldnotStartCmd">Nepodarilo sa spustiť {0}</system:String>
<system:String x:Key="invalidFlowLauncherPluginFileFormat">Neplatný formát súboru pre plugin Flow Launcher</system:String>
<system:String x:Key="setAsTopMostInThisQuery">Pri tomto dopyte umiestniť navrchu</system:String>
<system:String x:Key="cancelTopMostInThisQuery">Zrušiť umiestnenie navrchu pri tomto dopyte</system:String>
<system:String x:Key="invalidFlowLauncherPluginFileFormat">Neplatný formát súboru pre plugin Flow Launchera</system:String>
<system:String x:Key="setAsTopMostInThisQuery">Pri tomto zadaní umiestniť navrchu</system:String>
<system:String x:Key="cancelTopMostInThisQuery">Zrušiť umiestnenie navrchu pri tomto zadaní</system:String>
<system:String x:Key="executeQuery">Spustiť dopyt: {0}</system:String>
<system:String x:Key="lastExecuteTime">Posledný čas realizácie: {0}</system:String>
<system:String x:Key="iconTrayOpen">Otvoriť</system:String>
@ -15,61 +15,71 @@
<system:String x:Key="iconTrayExit">Ukončiť</system:String>
<!--Setting General-->
<system:String x:Key="flowlauncher_settings">Nastavenia Flow Launcher</system:String>
<system:String x:Key="flowlauncher_settings">Nastavenia Flow Launchera</system:String>
<system:String x:Key="general">Všeobecné</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>
<system:String x:Key="rememberLastLocation">Zapamätať si posledné umiestnenie</system:String>
<system:String x:Key="language">Jazyk</system:String>
<system:String x:Key="lastQueryMode">Posledný dopyt</system:String>
<system:String x:Key="lastQueryMode">Posledné vyhľadávanie</system:String>
<system:String x:Key="LastQueryPreserved">Ponechať</system:String>
<system:String x:Key="LastQuerySelected">Označiť posledný dopyt</system:String>
<system:String x:Key="LastQueryEmpty">Prázdne</system:String>
<system:String x:Key="LastQuerySelected">Označiť</system:String>
<system:String x:Key="LastQueryEmpty">Vymazať</system:String>
<system:String x:Key="maxShowResults">Max. výsledkov</system:String>
<system:String x:Key="ignoreHotkeysOnFullscreen">Ignorovať klávesové skraty v režime na celú obrazovku</system:String>
<system:String x:Key="ignoreHotkeysOnFullscreen">Ignorovať klávesové skratky v režime na celú obrazovku</system:String>
<system:String x:Key="pythonDirectory">Priečinok s Pythonom</system:String>
<system:String x:Key="autoUpdates">Automatická aktualizácia</system:String>
<system:String x:Key="selectPythonDirectory">Vybrať</system:String>
<system:String x:Key="hideOnStartup">Schovať Flow Launcher po spustení</system:String>
<system:String x:Key="hideNotifyIcon">Schovať ikonu v oblasti oznámení</system:String>
<system:String x:Key="hideNotifyIcon">Schovať ikonu z oblasti oznámení</system:String>
<system:String x:Key="querySearchPrecision">Presnosť vyhľadávania</system:String>
<system:String x:Key="ShouldUsePinyin">Použiť Pinyin</system:String>
<!--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="actionKeywords">Skratka akcie</system:String>
<system:String x:Key="pluginDirectory">Priečinok s pluginmy</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="pluginDirectory">Priečinok s pluginmi</system:String>
<system:String x:Key="author">Autor</system:String>
<system:String x:Key="plugin_init_time">Čas inic.:</system:String>
<system:String x:Key="plugin_query_time">Čas dopytu:</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>
<!--Setting Theme-->
<system:String x:Key="theme">Motív</system:String>
<system:String x:Key="browserMoreThemes">Prehliadať viac motívov</system:String>
<system:String x:Key="queryBoxFont">Písmo poľa pre dopyt</system:String>
<system:String x:Key="hiThere">Ahojte</system:String>
<system:String x:Key="queryBoxFont">Písmo vyhľadávacieho poľa</system:String>
<system:String x:Key="resultItemFont">Písmo výsledkov</system:String>
<system:String x:Key="windowMode">Režim okno</system:String>
<system:String x:Key="opacity">Nepriehľadnosť</system:String>
<system:String x:Key="theme_load_failure_path_not_exists">Motív {0} neexistuje, návrat na predvolený motív</system:String>
<system:String x:Key="theme_load_failure_parse_error">Nepodarilo sa nečítať motív {0}, návrat na predvolený motív</system:String>
<!--Setting Hotkey-->
<system:String x:Key="hotkey">Klávesová skratka</system:String>
<system:String x:Key="hotkey">Klávesové skratky</system:String>
<system:String x:Key="flowlauncherHotkey">Klávesová skratka pre Flow Launcher</system:String>
<system:String x:Key="openResultModifiers">Otvorte modifikátory výsledkov</system:String>
<system:String x:Key="customQueryHotkey">Vlastná klávesová skratka pre dopyt</system:String>
<system:String x:Key="openResultModifiers">Modifikáčné klávesy na otvorenie výsledkov</system:String>
<system:String x:Key="showOpenResultHotkey">Zobraziť klávesovú skratku</system:String>
<system:String x:Key="customQueryHotkey">Vlastná klávesová skratka na vyhľadávanie</system:String>
<system:String x:Key="delete">Odstrániť</system:String>
<system:String x:Key="edit">Upraviť</system:String>
<system:String x:Key="add">Pridať</system:String>
<system:String x:Key="pleaseSelectAnItem">Vyberte položku, prosím</system:String>
<system:String x:Key="deleteCustomHotkeyWarning">Ste si istý, že chcete odstrániť klávesovú skratku {0} pre plugin?</system:String>
<system:String x:Key="queryWindowShadowEffect">Tieňový efekt v poli vyhľadávania</system:String>
<system:String x:Key="shadowEffectCPUUsage">Tieňový efekt významne využíva GPU.</system:String>
<system:String x:Key="shadowEffectPerformance">Neodporúča sa, ak je výkon počítača obmedzený.</system:String>
<!--Setting Proxy-->
<system:String x:Key="proxy">HTTP Proxy</system:String>
<system:String x:Key="enableProxy">Povoliť HTTP Proxy</system:String>
<system:String x:Key="server">HTTP Server</system:String>
<system:String x:Key="port">Port</system:String>
<system:String x:Key="userName">Používateľské meno</system:String>
<system:String x:Key="userName">Použív. meno</system:String>
<system:String x:Key="password">Heslo</system:String>
<system:String x:Key="testProxy">Test Proxy</system:String>
<system:String x:Key="save">Uložiť</system:String>
@ -86,13 +96,13 @@
<system:String x:Key="version">Verzia</system:String>
<system:String x:Key="about_activate_times">Flow Launcher bol aktivovaný {0}-krát</system:String>
<system:String x:Key="checkUpdates">Skontrolovať aktualizácie</system:String>
<system:String x:Key="newVersionTips">Je dostupná nová verzia {0}, prosím, reštartujte Flow Launcher.</system:String>
<system:String x:Key="newVersionTips">Je dostupná nová verzia {0}, chcete reštartovať Flow Launcher, aby sa mohol aktualizovať?</system:String>
<system:String x:Key="checkUpdatesFailed">Kontrola aktualizácií zlyhala, prosím, skontrolujte pripojenie na internet a nastavenie proxy k api.github.com.</system:String>
<system:String x:Key="downloadUpdatesFailed">
Sťahovanie aktualizácií zlyhalo, skontrolujte pripojenie na internet a nastavenie proxy k github-cloud.s3.amazonaws.com,
alebo prejdite na https://github.com/Flow-Launcher/Flow.Launcher/releases pre manuálne stiahnutie aktualizácií.
alebo prejdite na https://github.com/Flow-Launcher/Flow.Launcher/releases pre manuálne stiahnutie aktualizácie.
</system:String>
<system:String x:Key="releaseNotes">Poznámky k vydaniu:</system:String>
<system:String x:Key="releaseNotes">Poznámky k vydaniu</system:String>
<!--Action Keyword Setting Dialog-->
<system:String x:Key="oldActionKeywords">Stará skratka akcie</system:String>
@ -131,7 +141,7 @@
<system:String x:Key="reportWindow_flowlauncher_got_an_error">Flow Launcher zaznamenal chybu</system:String>
<!--update-->
<system:String x:Key="update_flowlauncher_update_new_version_available">Je dostupné nové vydanie Flow Launcher {0}</system:String>
<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_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>

View file

@ -22,7 +22,6 @@
Loaded="OnLoaded"
Initialized="OnInitialized"
Closing="OnClosing"
Drop="OnDrop"
LocationChanged="OnLocationChanged"
Deactivated="OnDeactivated"
PreviewKeyDown="OnKeyDown"
@ -64,8 +63,7 @@
<StackPanel Orientation="Vertical">
<Grid>
<TextBox x:Name="QueryTextSuggestionBox"
Style="{DynamicResource QueryBoxStyle}"
Foreground="DarkGray"
Style="{DynamicResource QuerySuggestionBoxStyle}"
IsEnabled="False"
Margin="18,0,56,0">
<TextBox.Text>

View file

@ -52,12 +52,14 @@ namespace Flow.Launcher
private void OnInitialized(object sender, EventArgs e)
{
// show notify icon when flowlauncher is hided
InitializeNotifyIcon();
}
private void OnLoaded(object sender, RoutedEventArgs _)
{
// show notify icon when flowlauncher is hidden
InitializeNotifyIcon();
// todo is there a way to set blur only once?
ThemeManager.Instance.SetBlurForWindow();
WindowsInteropHelper.DisableControlBox(this);
@ -87,11 +89,17 @@ namespace Flow.Launcher
};
_settings.PropertyChanged += (o, e) =>
{
if (e.PropertyName == nameof(Settings.HideNotifyIcon))
switch (e.PropertyName)
{
_notifyIcon.Visible = !_settings.HideNotifyIcon;
case nameof(Settings.HideNotifyIcon):
_notifyIcon.Visible = !_settings.HideNotifyIcon;
break;
case nameof(Settings.Language):
UpdateNotifyIconText();
break;
}
};
InitializePosition();
}
@ -103,6 +111,18 @@ namespace Flow.Launcher
_settings.WindowLeft = Left;
}
private void UpdateNotifyIconText()
{
var menu = _notifyIcon.ContextMenuStrip;
var open = menu.Items[0];
var setting = menu.Items[1];
var exit = menu.Items[2];
open.Text = InternationalizationManager.Instance.GetTranslation("iconTrayOpen");
setting.Text = InternationalizationManager.Instance.GetTranslation("iconTraySettings");
exit.Text = InternationalizationManager.Instance.GetTranslation("iconTrayExit");
}
private void InitializeNotifyIcon()
{
_notifyIcon = new NotifyIcon
@ -179,25 +199,6 @@ namespace Flow.Launcher
}
}
private void OnDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
// Note that you can have more than one file.
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files[0].ToLower().EndsWith(".flowlauncher"))
{
PluginManager.InstallPlugin(files[0]);
}
else
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("invalidFlowLauncherPluginFileFormat"));
}
}
e.Handled = false;
}
private void OnPreviewDragOver(object sender, DragEventArgs e)
{
e.Handled = true;
@ -294,7 +295,5 @@ namespace Flow.Launcher
_viewModel.QueryTextCursorMovedToEnd = false;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show more