mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #101 from Flow-Launcher/dev
Release 1.1.0 | Plugin 1.1.0 | Explorer Plugin 1.1.0
This commit is contained in:
commit
d42d35e7e2
52 changed files with 753 additions and 597 deletions
32
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal 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
21
.github/ISSUE_TEMPLATE/code-review.md
vendored
Normal 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
14
.github/ISSUE_TEMPLATE/discussion.md
vendored
Normal 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.
|
||||
24
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal 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.
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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}>");
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Newtonsoft.Json;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
|
|
@ -13,28 +15,28 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
string tempFoler = Path.Combine(Path.GetTempPath(), "flowlauncher\\plugins");
|
||||
if (Directory.Exists(tempFoler))
|
||||
string tempFolder = Path.Combine(Path.GetTempPath(), "flowlauncher", "plugins");
|
||||
if (Directory.Exists(tempFolder))
|
||||
{
|
||||
Directory.Delete(tempFoler, true);
|
||||
Directory.Delete(tempFolder, true);
|
||||
}
|
||||
UnZip(path, tempFoler, true);
|
||||
UnZip(path, tempFolder, true);
|
||||
|
||||
string iniPath = Path.Combine(tempFoler, "plugin.json");
|
||||
if (!File.Exists(iniPath))
|
||||
string jsonPath = Path.Combine(tempFolder, Constant.PluginMetadataFileName);
|
||||
if (!File.Exists(jsonPath))
|
||||
{
|
||||
MessageBox.Show("Install failed: plugin config is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
PluginMetadata plugin = GetMetadataFromJson(tempFoler);
|
||||
PluginMetadata plugin = GetMetadataFromJson(tempFolder);
|
||||
if (plugin == null || plugin.Name == null)
|
||||
{
|
||||
MessageBox.Show("Install failed: plugin config is invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
string pluginFolerPath = Infrastructure.UserSettings.DataLocation.PluginsDirectory;
|
||||
string pluginFolderPath = Infrastructure.UserSettings.DataLocation.PluginsDirectory;
|
||||
|
||||
string newPluginName = plugin.Name
|
||||
.Replace("/", "_")
|
||||
|
|
@ -46,7 +48,9 @@ namespace Flow.Launcher.Core.Plugin
|
|||
.Replace("*", "_")
|
||||
.Replace("|", "_")
|
||||
+ "-" + Guid.NewGuid();
|
||||
string newPluginPath = Path.Combine(pluginFolerPath, newPluginName);
|
||||
|
||||
string newPluginPath = Path.Combine(pluginFolderPath, newPluginName);
|
||||
|
||||
string content = $"Do you want to install following plugin?{Environment.NewLine}{Environment.NewLine}" +
|
||||
$"Name: {plugin.Name}{Environment.NewLine}" +
|
||||
$"Version: {plugin.Version}{Environment.NewLine}" +
|
||||
|
|
@ -71,8 +75,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
File.Create(Path.Combine(existingPlugin.Metadata.PluginDirectory, "NeedDelete.txt")).Close();
|
||||
}
|
||||
|
||||
UnZip(path, newPluginPath, true);
|
||||
Directory.Delete(tempFoler, true);
|
||||
Directory.Move(tempFolder, newPluginPath);
|
||||
|
||||
//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
|
||||
|
|
@ -94,7 +97,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
private static PluginMetadata GetMetadataFromJson(string pluginDirectory)
|
||||
{
|
||||
string configPath = Path.Combine(pluginDirectory, "plugin.json");
|
||||
string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName);
|
||||
PluginMetadata metadata;
|
||||
|
||||
if (!File.Exists(configPath))
|
||||
|
|
@ -107,36 +110,20 @@ namespace Flow.Launcher.Core.Plugin
|
|||
metadata = JsonConvert.DeserializeObject<PluginMetadata>(File.ReadAllText(configPath));
|
||||
metadata.PluginDirectory = pluginDirectory;
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception e)
|
||||
{
|
||||
string error = $"Parse plugin config {configPath} failed: json format is not valid";
|
||||
#if (DEBUG)
|
||||
{
|
||||
throw new Exception(error);
|
||||
}
|
||||
#endif
|
||||
Log.Exception($"|PluginInstaller.GetMetadataFromJson|plugin config {configPath} failed: invalid json format", e);
|
||||
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
|
||||
Log.Error($"|PluginInstaller.GetMetadataFromJson|plugin config {configPath} failed: invalid language {metadata.Language}");
|
||||
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
|
||||
Log.Error($"|PluginInstaller.GetMetadataFromJson|plugin config {configPath} failed: file {metadata.ExecuteFilePath} doesn't exist");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -144,58 +131,38 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// unzip
|
||||
/// unzip plugin contents to the given directory.
|
||||
/// </summary>
|
||||
/// <param name="zipedFile">The ziped file.</param>
|
||||
/// <param name="strDirectory">The STR directory.</param>
|
||||
/// <param name="zipFile">The path to the zip file.</param>
|
||||
/// <param name="strDirectory">The output directory.</param>
|
||||
/// <param name="overWrite">overwirte</param>
|
||||
private static void UnZip(string zipedFile, string strDirectory, bool overWrite)
|
||||
private static void UnZip(string zipFile, string strDirectory, bool overWrite)
|
||||
{
|
||||
if (strDirectory == "")
|
||||
strDirectory = Directory.GetCurrentDirectory();
|
||||
if (!strDirectory.EndsWith("\\"))
|
||||
strDirectory = strDirectory + "\\";
|
||||
|
||||
using (ZipInputStream s = new ZipInputStream(File.OpenRead(zipedFile)))
|
||||
using (ZipInputStream zipStream = new ZipInputStream(File.OpenRead(zipFile)))
|
||||
{
|
||||
ZipEntry theEntry;
|
||||
|
||||
while ((theEntry = s.GetNextEntry()) != null)
|
||||
while ((theEntry = zipStream.GetNextEntry()) != null)
|
||||
{
|
||||
string directoryName = "";
|
||||
string pathToZip = "";
|
||||
pathToZip = theEntry.Name;
|
||||
var pathToZip = theEntry.Name;
|
||||
var directoryName = String.IsNullOrEmpty(pathToZip) ? "" : Path.GetDirectoryName(pathToZip);
|
||||
var fileName = Path.GetFileName(pathToZip);
|
||||
var destinationDir = Path.Combine(strDirectory, directoryName);
|
||||
var destinationFile = Path.Combine(destinationDir, fileName);
|
||||
|
||||
if (pathToZip != "")
|
||||
directoryName = Path.GetDirectoryName(pathToZip) + "\\";
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
|
||||
string fileName = Path.GetFileName(pathToZip);
|
||||
if (String.IsNullOrEmpty(fileName) || (File.Exists(destinationFile) && !overWrite))
|
||||
continue;
|
||||
|
||||
Directory.CreateDirectory(strDirectory + directoryName);
|
||||
|
||||
if (fileName != "")
|
||||
using (FileStream streamWriter = File.Create(destinationFile))
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
zipStream.CopyTo(streamWriter);
|
||||
}
|
||||
}
|
||||
|
||||
s.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
@ -164,9 +153,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 +164,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 +209,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 +274,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
GlobalPlugins.Remove(plugin);
|
||||
}
|
||||
|
||||
if(oldActionkeyword != Query.GlobalPluginWildcardSign)
|
||||
if (oldActionkeyword != Query.GlobalPluginWildcardSign)
|
||||
NonGlobalPlugins.Remove(oldActionkeyword);
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -46,75 +46,58 @@ namespace Flow.Launcher.Core.Plugin
|
|||
var type = types.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(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);
|
||||
|
||||
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)));
|
||||
var type = types.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(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);
|
||||
}
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,8 +86,8 @@ namespace Flow.Launcher.Core
|
|||
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
|
||||
FilesFolders.Copy(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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -83,9 +83,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 +156,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;
|
||||
|
|
@ -299,13 +324,13 @@ namespace Flow.Launcher.Infrastructure
|
|||
public class MatchOption
|
||||
{
|
||||
/// <summary>
|
||||
/// prefix of match char, use for hightlight
|
||||
/// prefix of match char, use for highlight
|
||||
/// </summary>
|
||||
[Obsolete("this is never used")]
|
||||
public string Prefix { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// suffix of match char, use for hightlight
|
||||
/// suffix of match char, use for highlight
|
||||
/// </summary>
|
||||
[Obsolete("this is never used")]
|
||||
public string Suffix { get; set; } = "";
|
||||
|
|
|
|||
|
|
@ -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.1.0</Version>
|
||||
<PackageVersion>1.1.0</PackageVersion>
|
||||
<AssemblyVersion>1.1.0</AssemblyVersion>
|
||||
<FileVersion>1.1.0</FileVersion>
|
||||
<PackageId>Flow.Launcher.Plugin</PackageId>
|
||||
<Authors>Flow-Launcher</Authors>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
|
|
|||
|
|
@ -130,5 +130,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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,9 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
public static class FilesFolders
|
||||
{
|
||||
private const string FileExplorerProgramName = "explorer";
|
||||
|
||||
private const string FileExplorerProgramEXE = "explorer.exe";
|
||||
|
||||
public static void Copy(this string sourcePath, string targetPath)
|
||||
{
|
||||
// Get the subdirectories for the specified directory.
|
||||
|
|
@ -128,6 +131,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.
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -62,18 +62,16 @@ 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
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -253,18 +251,6 @@ Global
|
|||
{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 +287,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,6 +299,18 @@ 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
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -339,12 +325,11 @@ Global
|
|||
{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}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {F26ACB50-3F6C-4907-B0C9-1ADACC1D0DED}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
</TextBlock>
|
||||
</StackPanel>
|
||||
<TextBlock Style="{DynamicResource ItemTitleStyle}" DockPanel.Dock="Left"
|
||||
VerticalAlignment="Center" ToolTip="{Binding Result.Title}" x:Name="Title"
|
||||
VerticalAlignment="Center" ToolTip="{Binding ShowTitleToolTip}" x:Name="Title"
|
||||
Text="{Binding Result.Title}">
|
||||
<vm:ResultsViewModel.FormattedText>
|
||||
<MultiBinding Converter="{StaticResource HighlightTextConverter}">
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
</MultiBinding>
|
||||
</vm:ResultsViewModel.FormattedText>
|
||||
</TextBlock>
|
||||
<TextBlock Style="{DynamicResource ItemSubTitleStyle}" ToolTip="{Binding Result.SubTitle}"
|
||||
<TextBlock Style="{DynamicResource ItemSubTitleStyle}" ToolTip="{Binding ShowSubTitleToolTip}"
|
||||
Grid.Row="1" x:Name="SubTitle" Text="{Binding Result.SubTitle}" MinWidth="750">
|
||||
<vm:ResultsViewModel.FormattedText>
|
||||
<MultiBinding Converter="{StaticResource HighlightTextConverter}">
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
</Style>
|
||||
|
||||
<Style x:Key="WindowStyle" BasedOn="{StaticResource BaseWindowStyle}" TargetType="{x:Type Window}">
|
||||
<Setter Property="Width" Value="750" /> <!-- This is used to set the blur width only -->
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="Black" Opacity="0.3"/>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
</Style>
|
||||
|
||||
<Style x:Key="WindowStyle" BasedOn="{StaticResource BaseWindowStyle}" TargetType="{x:Type Window}">
|
||||
<Setter Property="Width" Value="750" /> <!-- This is used to set the blur width only -->
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="White" Opacity="0.1"/>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,14 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
public string OpenResultModifiers => Settings.OpenResultModifiers;
|
||||
|
||||
public string ShowTitleToolTip => string.IsNullOrEmpty(Result.TitleToolTip)
|
||||
? Result.Title
|
||||
: Result.TitleToolTip;
|
||||
|
||||
public string ShowSubTitleToolTip => string.IsNullOrEmpty(Result.SubTitleToolTip)
|
||||
? Result.SubTitle
|
||||
: Result.SubTitleToolTip;
|
||||
|
||||
public ImageSource Image
|
||||
{
|
||||
get
|
||||
|
|
|
|||
|
|
@ -1,102 +0,0 @@
|
|||
<!--
|
||||
===================================================
|
||||
DO NOT DELETE UNTIL YOU HAVE READ THE FIRST SECTION
|
||||
===================================================
|
||||
|
||||
To be able to help you we need enough information to find
|
||||
what is causing your issue. Please follow the instructions
|
||||
and type the requested information where prompted.
|
||||
|
||||
===================================================
|
||||
中文版说明,请仔细阅读并在填写后删除此说明段落,无效的反馈会被直接关闭
|
||||
===================================================
|
||||
请遵循以下说明,提供我们所需要的信息来,这样我们才能找出导致你
|
||||
遇到的问题的原因,来更好的帮助你。
|
||||
如果可以的话,请附上一份英文的翻译(哪怕是直接使用谷歌翻译)
|
||||
请注意,无效的反馈(尤其是没有填写下面那些信息的反馈)会被直接关闭!
|
||||
-->
|
||||
|
||||
### Are you submitting a bug report?
|
||||
|
||||
(write your answer here)
|
||||
|
||||
<!--
|
||||
If your answer is "Yes", please follow the instructions below.
|
||||
|
||||
If your answer is "No", you may delete everything below and then proceed
|
||||
to write your concern.
|
||||
|
||||
你是否是在提交一个bug(软件缺陷)?
|
||||
如果你的答案是『是的』,请继续回答下面的提问。
|
||||
如果你的答案是『不是』,你可以删除下面的一切,然后说出你的想法。
|
||||
-->
|
||||
|
||||
<!--
|
||||
Some problems are known of and there are
|
||||
already workarounds or existing issues.
|
||||
|
||||
Known problems for 1.3.183:
|
||||
We are aware of the following issues and the following workarounds exist:
|
||||
1. `System.NullReferenceException`: https://github.com/Wox-launcher/Wox/releases/tag/v1.3.475
|
||||
2. `System.UriFormatException`: delete your old theme file
|
||||
3. `System.Threading.Tasks.TaskCanceledException`: https://github.com/Wox-launcher/Wox/releases/tag/v1.3.475
|
||||
4. `System.AggregateException`: see https://github.com/Wox-launcher/Wox/issues/1777
|
||||
|
||||
If none of them match your case, please continue.
|
||||
|
||||
已知问题及解决方案:(略,如果有任何问题请继续提问)
|
||||
-->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
<!--
|
||||
Write what steps you took in order for this problem to happen.
|
||||
If you do not provide any steps, we cannot replicate your problem
|
||||
and will not be able to help you.
|
||||
|
||||
复现问题的步骤:
|
||||
在此按顺序一步步说明在问题发生前你做了什么操作。
|
||||
如果你没法说明你的步骤,那么我们无法复现你的问题,也就无法帮助你了。
|
||||
-->
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
### Flow Launcher Error Window text
|
||||
<!--
|
||||
Paste below the logs generated by the Flow.Launcher error reporter.
|
||||
|
||||
请在此处粘贴 Flow Launcher 错误报告程序提供的日志。
|
||||
-->
|
||||
|
||||
(paste here)
|
||||
|
||||
|
||||
### Detailed logs
|
||||
<!--
|
||||
Please also provide detailed logs. The latest log file
|
||||
can be found here: %APPDATA%\Flow.Launcher\Logs\version\<date>.txt
|
||||
Drag and drop that file below this comment.
|
||||
In some cases you can skip uploading the the logs, but the chances
|
||||
of us being able to solve the problem will be higher if you do.
|
||||
|
||||
详细日志:
|
||||
请在此处提供详细日志。你可以在这个目录找到最新的日志:
|
||||
%APPDATA%\Flow.Launcher\Logs\version\<date>.txt
|
||||
直接拖放文件到这个评论就可以上传(国内大部分地区可能需要翻墙才能上传,作为备选方案可以考虑直接在此粘贴文件内容)
|
||||
-->
|
||||
|
||||
(drop your log file here)
|
||||
|
||||
### Screenshots (optional)
|
||||
<!--
|
||||
If you think it will be helpful to provide a screenshot
|
||||
to better describe your problem, upload it below
|
||||
|
||||
截图(可选)
|
||||
如果你认为截图能够有助于说明你的问题,请在下面上传它。
|
||||
-->
|
||||
|
||||
(drop your screenshot here)
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
{
|
||||
try
|
||||
{
|
||||
Process.Start("explorer.exe", $" /select,\"{record.FullPath}\"");
|
||||
FilesFolders.OpenContainingFolder(record.FullPath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
internal const string DifferentUserIconImagePath = "Images\\user.png";
|
||||
internal const string IndexingOptionsIconImagePath = "Images\\windowsindexingoptions.png";
|
||||
|
||||
internal const string DefaultFolderSubtitleString = "Ctrl + Enter to open the directory";
|
||||
internal const string ToolTipOpenDirectory = "Ctrl + Enter to open the directory";
|
||||
|
||||
internal const string ToolTipOpenContainingFolder = "Ctrl + Enter to open the containing folder";
|
||||
|
||||
internal const char AllFilesFolderSearchWildcard = '>';
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
|
||||
if (fileSystemInfo is System.IO.DirectoryInfo)
|
||||
{
|
||||
folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, Constants.DefaultFolderSubtitleString, fileSystemInfo.FullName, query, true, false));
|
||||
folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, fileSystemInfo.FullName, query, true, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
|
@ -18,12 +18,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
internal static Dictionary<string, string> LoadEnvironmentStringPaths()
|
||||
{
|
||||
var envStringPaths = new Dictionary<string, string>();
|
||||
var envStringPaths = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
foreach (DictionaryEntry special in Environment.GetEnvironmentVariables())
|
||||
{
|
||||
if (Directory.Exists(special.Value.ToString()))
|
||||
{
|
||||
// Variables are returned with a mixture of all upper/lower case.
|
||||
// Call ToLower() to make the results look consistent
|
||||
envStringPaths.Add(special.Key.ToString().ToLower(), special.Value.ToString());
|
||||
}
|
||||
}
|
||||
|
|
@ -82,11 +84,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
foreach (var p in environmentVariables)
|
||||
{
|
||||
if (p.Key.StartsWith(search))
|
||||
if (p.Key.StartsWith(search, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
results.Add(new ResultManager(context).CreateFolderResult($"%{p.Key}%", p.Value, p.Value, query));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks
|
|||
return folderLinks
|
||||
.Select(item =>
|
||||
new ResultManager(context)
|
||||
.CreateFolderResult(item.Nickname, Constants.DefaultFolderSubtitleString, item.Path, query))
|
||||
.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
|
||||
.ToList();
|
||||
|
||||
string search = query.Search.ToLower();
|
||||
|
|
@ -21,7 +21,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks
|
|||
|
||||
return queriedFolderLinks.Select(item =>
|
||||
new ResultManager(context)
|
||||
.CreateFolderResult(item.Nickname, Constants.DefaultFolderSubtitleString, item.Path, query))
|
||||
.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
|
@ -38,13 +39,15 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
string changeTo = path.EndsWith(Constants.DirectorySeperator) ? path : path + Constants.DirectorySeperator;
|
||||
context.API.ChangeQuery(string.IsNullOrEmpty(query.ActionKeyword) ?
|
||||
changeTo :
|
||||
query.ActionKeyword + " " + changeTo);
|
||||
return false;
|
||||
},
|
||||
TitleToolTip = Constants.ToolTipOpenDirectory,
|
||||
SubTitleToolTip = Constants.ToolTipOpenDirectory,
|
||||
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, ShowIndexState = showIndexState, WindowsIndexed = windowsIndexed }
|
||||
};
|
||||
}
|
||||
|
|
@ -85,6 +88,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
FilesFolders.OpenPath(retrievedDirectoryPath);
|
||||
return true;
|
||||
},
|
||||
TitleToolTip = retrievedDirectoryPath,
|
||||
SubTitleToolTip = retrievedDirectoryPath,
|
||||
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = retrievedDirectoryPath, ShowIndexState = true, WindowsIndexed = windowsIndexed }
|
||||
};
|
||||
}
|
||||
|
|
@ -101,7 +106,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
{
|
||||
try
|
||||
{
|
||||
FilesFolders.OpenPath(filePath);
|
||||
if (c.SpecialKeyState.CtrlPressed)
|
||||
{
|
||||
FilesFolders.OpenContainingFolder(filePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
FilesFolders.OpenPath(filePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -110,6 +122,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
return true;
|
||||
},
|
||||
TitleToolTip = Constants.ToolTipOpenContainingFolder,
|
||||
SubTitleToolTip = Constants.ToolTipOpenContainingFolder,
|
||||
ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, ShowIndexState = showIndexState, WindowsIndexed = windowsIndexed }
|
||||
};
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
if (dataReaderResults.GetString(2) == "Directory")
|
||||
{
|
||||
folderResults.Add(resultManager.CreateFolderResult(
|
||||
dataReaderResults.GetString(0),
|
||||
Constants.DefaultFolderSubtitleString,
|
||||
dataReaderResults.GetString(0),
|
||||
dataReaderResults.GetString(1),
|
||||
dataReaderResults.GetString(1),
|
||||
query, true, true));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Name": "Explorer",
|
||||
"Description": "Search and manage files and folders. Explorer utilises Windows Index Search",
|
||||
"Author": "Jeremy Wu",
|
||||
"Version": "1.0.0",
|
||||
"Version": "1.1.0",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
|
@ -17,7 +17,6 @@ namespace Flow.Launcher.Plugin.PluginManagement
|
|||
public class Main : IPlugin, IPluginI18n
|
||||
{
|
||||
private static string APIBASE = "http://api.wox.one";
|
||||
private static string PluginConfigName = "plugin.json";
|
||||
private static string pluginSearchUrl = APIBASE + "/plugin/search/";
|
||||
private const string ListCommand = "list";
|
||||
private const string InstallCommand = "install";
|
||||
|
|
|
|||
|
|
@ -1,55 +1,55 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<ProjectGuid>{03FFA443-5F50-48D5-8869-F3DF316803AA}</ProjectGuid>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>HelloWorldCSharp</RootNamespace>
|
||||
<AssemblyName>HelloWorldCSharp</AssemblyName>
|
||||
<AssemblyName>Flow.Launcher.Plugin.ProcessKiller</AssemblyName>
|
||||
<PackageId>Flow.Launcher.Plugin.ProcessKiller</PackageId>
|
||||
<Authors>Flow-Launcher</Authors>
|
||||
<PackageProjectUrl>https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller</RepositoryUrl>
|
||||
<PackageTags>flow-launcher flow-plugin</PackageTags>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<ApplicationIcon />
|
||||
<OutputType>Library</OutputType>
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\..\Output\Debug\Plugins\HelloWorldCSharp\</OutputPath>
|
||||
<OutputPath>..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.ProcessKiller\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\..\Output\Release\Plugins\HelloWorldCSharp\</OutputPath>
|
||||
<OutputPath>..\..\Output\Release\Plugins\Flow.Launcher.Plugin.ProcessKiller\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="plugin.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Images\app.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Languages\en.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="plugin.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
BIN
Plugins/Flow.Launcher.Plugin.ProcessKiller/Images/app.png
Normal file
BIN
Plugins/Flow.Launcher.Plugin.ProcessKiller/Images/app.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
11
Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/en.xaml
Normal file
11
Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/en.xaml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_processkiller_plugin_name">Process Killer</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_processkiller_plugin_description">Kill running processes from Flow Launcher</system:String>
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_processkiller_kill_all">kill all "{0}" processes</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_processkiller_kill_instances">kill all instances</system:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
123
Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs
Normal file
123
Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Dynamic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
|
||||
namespace Flow.Launcher.Plugin.ProcessKiller
|
||||
{
|
||||
public class Main : IPlugin, IPluginI18n, IContextMenu
|
||||
{
|
||||
private ProcessHelper processHelper = new ProcessHelper();
|
||||
|
||||
private static PluginInitContext _context;
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
var termToSearch = query.Terms.Length <= 1
|
||||
? null
|
||||
: string.Join(Plugin.Query.TermSeperater, query.Terms.Skip(1)).ToLower();
|
||||
|
||||
var processlist = processHelper.GetMatchingProcesses(termToSearch);
|
||||
|
||||
return !processlist.Any()
|
||||
? null
|
||||
: CreateResultsFromProcesses(processlist, termToSearch);
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginTitle()
|
||||
{
|
||||
return _context.API.GetTranslation("flowlauncher_plugin_processkiller_plugin_name");
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginDescription()
|
||||
{
|
||||
return _context.API.GetTranslation("flowlauncher_plugin_processkiller_plugin_description");
|
||||
}
|
||||
|
||||
public List<Result> LoadContextMenus(Result result)
|
||||
{
|
||||
var menuOptions = new List<Result>();
|
||||
var processPath = result.SubTitle;
|
||||
|
||||
// get all non-system processes whose file path matches that of the given result (processPath)
|
||||
var similarProcesses = processHelper.GetSimilarProcesses(processPath);
|
||||
|
||||
menuOptions.Add(new Result
|
||||
{
|
||||
Title = _context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_instances"),
|
||||
SubTitle = processPath,
|
||||
Action = _ =>
|
||||
{
|
||||
foreach (var p in similarProcesses)
|
||||
{
|
||||
processHelper.TryKill(p);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
IcoPath = processPath
|
||||
});
|
||||
|
||||
return menuOptions;
|
||||
}
|
||||
|
||||
private List<Result> CreateResultsFromProcesses(List<ProcessResult> processlist, string termToSearch)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
|
||||
foreach (var pr in processlist)
|
||||
{
|
||||
var p = pr.Process;
|
||||
var path = processHelper.TryGetProcessFilename(p);
|
||||
results.Add(new Result()
|
||||
{
|
||||
IcoPath = path,
|
||||
Title = p.ProcessName + " - " + p.Id,
|
||||
SubTitle = path,
|
||||
TitleHighlightData = StringMatcher.FuzzySearch(termToSearch, p.ProcessName).MatchData,
|
||||
Score = pr.Score,
|
||||
Action = (c) =>
|
||||
{
|
||||
processHelper.TryKill(p);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// When there are multiple results AND all of them are instances of the same executable
|
||||
// add a quick option to kill them all at the top of the results.
|
||||
var firstResult = results.FirstOrDefault()?.SubTitle;
|
||||
if (processlist.Count > 1 && !string.IsNullOrEmpty(termToSearch) && results.All(r => r.SubTitle == firstResult))
|
||||
{
|
||||
results.Insert(0, new Result()
|
||||
{
|
||||
IcoPath = "Images/app.png",
|
||||
Title = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all"), termToSearch),
|
||||
SubTitle = "",
|
||||
Score = 200,
|
||||
Action = (c) =>
|
||||
{
|
||||
foreach (var p in processlist)
|
||||
{
|
||||
processHelper.TryKill(p.Process);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs
Normal file
125
Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Flow.Launcher.Plugin.ProcessKiller
|
||||
{
|
||||
internal class ProcessHelper
|
||||
{
|
||||
private readonly HashSet<string> _systemProcessList = new HashSet<string>()
|
||||
{
|
||||
"conhost",
|
||||
"svchost",
|
||||
"idle",
|
||||
"system",
|
||||
"rundll32",
|
||||
"csrss",
|
||||
"lsass",
|
||||
"lsm",
|
||||
"smss",
|
||||
"wininit",
|
||||
"winlogon",
|
||||
"services",
|
||||
"spoolsv",
|
||||
"explorer"
|
||||
};
|
||||
|
||||
private bool IsSystemProcess(Process p) => _systemProcessList.Contains(p.ProcessName.ToLower());
|
||||
|
||||
/// <summary>
|
||||
/// Returns a ProcessResult for evey running non-system process whose name matches the given searchTerm
|
||||
/// </summary>
|
||||
public List<ProcessResult> GetMatchingProcesses(string searchTerm)
|
||||
{
|
||||
var processlist = new List<ProcessResult>();
|
||||
|
||||
foreach (var p in Process.GetProcesses())
|
||||
{
|
||||
if (IsSystemProcess(p)) continue;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
// show all non-system processes
|
||||
processlist.Add(new ProcessResult(p, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
var score = StringMatcher.FuzzySearch(searchTerm, p.ProcessName + p.Id).Score;
|
||||
if (score > 0)
|
||||
{
|
||||
processlist.Add(new ProcessResult(p, score));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return processlist;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all non-system processes whose file path matches the given processPath
|
||||
/// </summary>
|
||||
public IEnumerable<Process> GetSimilarProcesses(string processPath)
|
||||
{
|
||||
return Process.GetProcesses().Where(p => !IsSystemProcess(p) && TryGetProcessFilename(p) == processPath);
|
||||
}
|
||||
|
||||
public void TryKill(Process p)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!p.HasExited)
|
||||
{
|
||||
p.Kill();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|ProcessKiller.CreateResultsFromProcesses|Failed to kill process {p.ProcessName}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public string TryGetProcessFilename(Process p)
|
||||
{
|
||||
try
|
||||
{
|
||||
int capacity = 2000;
|
||||
StringBuilder builder = new StringBuilder(capacity);
|
||||
IntPtr ptr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, p.Id);
|
||||
if (!QueryFullProcessImageName(ptr, 0, builder, ref capacity))
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum ProcessAccessFlags : uint
|
||||
{
|
||||
QueryLimitedInformation = 0x00001000
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool QueryFullProcessImageName(
|
||||
[In] IntPtr hProcess,
|
||||
[In] int dwFlags,
|
||||
[Out] StringBuilder lpExeName,
|
||||
ref int lpdwSize);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr OpenProcess(
|
||||
ProcessAccessFlags processAccess,
|
||||
bool bInheritHandle,
|
||||
int processId);
|
||||
}
|
||||
}
|
||||
17
Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs
Normal file
17
Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessResult.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using System.Diagnostics;
|
||||
|
||||
namespace Flow.Launcher.Plugin.ProcessKiller
|
||||
{
|
||||
internal class ProcessResult
|
||||
{
|
||||
public ProcessResult(Process process, int score)
|
||||
{
|
||||
Process = process;
|
||||
Score = score;
|
||||
}
|
||||
|
||||
public Process Process { get; }
|
||||
|
||||
public int Score { get; }
|
||||
}
|
||||
}
|
||||
8
Plugins/Flow.Launcher.Plugin.ProcessKiller/Readme.md
Normal file
8
Plugins/Flow.Launcher.Plugin.ProcessKiller/Readme.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
Process Killer Plugin
|
||||
=====================
|
||||
|
||||
A Flow-Launcher plugin that allows you to kill running processes.
|
||||
|
||||
Based on Wox.Plugin.ProcessKiller, originally created by [cxfksword](https://github.com/cxfksword/Wox.Plugin.ProcessKiller) and improved by [theClueless](https://github.com/theClueless/Wox.Plugins/tree/master/Wox.Plugin.ProcessKiller).
|
||||
|
||||

|
||||
12
Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json
Normal file
12
Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"ID":"b64d0a79-329a-48b0-b53f-d658318a1bf6",
|
||||
"ActionKeyword":"kill",
|
||||
"Name":"Process Killer",
|
||||
"Description":"kill running processes from Flow",
|
||||
"Author":"Flow-Launcher",
|
||||
"Version":"1.0.0",
|
||||
"Language":"csharp",
|
||||
"Website":"https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller",
|
||||
"IcoPath":"Images\\app.png",
|
||||
"ExecuteFileName":"Flow.Launcher.Plugin.ProcessKiller.dll"
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.6 KiB |
|
|
@ -1,28 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace HelloWorldCSharp
|
||||
{
|
||||
class Main : IPlugin
|
||||
{
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
var result = new Result
|
||||
{
|
||||
Title = "Hello World from CSharp",
|
||||
SubTitle = $"Query: {query.Search}",
|
||||
IcoPath = "app.png"
|
||||
};
|
||||
return new List<Result> {result};
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"ID": "CEA0FDFC6D3B4085823D60DC76F28844",
|
||||
"ActionKeyword": "hc",
|
||||
"Name": "Hello World CSharp",
|
||||
"Description": "Hello World CSharp",
|
||||
"Author": "happlebao",
|
||||
"Version": "1.0.0",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "HelloWorldCSharp.dll",
|
||||
"IcoPath": "app.png",
|
||||
"Disabled": true
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<OutputPath>..\..\Output\Debug\Plugins\HelloWorldFSharp\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<OutputPath>..\..\Output\Release\Plugins\HelloWorldFSharp\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Images\app.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="plugin.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Compile Include="Main.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="FSharp.Core" Version="4.7.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.6 KiB |
|
|
@ -1,31 +0,0 @@
|
|||
namespace HelloWorldFSharp
|
||||
|
||||
open Flow.Launcher.Plugin
|
||||
open System.Collections.Generic
|
||||
|
||||
type HelloWorldFSharpPlugin() =
|
||||
|
||||
let mutable initContext = PluginInitContext()
|
||||
|
||||
interface IPlugin with
|
||||
member this.Init (context: PluginInitContext) =
|
||||
initContext <- context
|
||||
|
||||
member this.Query (query: Query) =
|
||||
List<Result> [
|
||||
Result (Title = "Hello World from F#",
|
||||
SubTitle = sprintf "Query: %s" query.Search)
|
||||
|
||||
Result (Title = "Browse source code of this plugin",
|
||||
SubTitle = "click to open in browser",
|
||||
Action = (fun ctx ->
|
||||
initContext.CurrentPluginMetadata.Website
|
||||
|> System.Diagnostics.Process.Start
|
||||
|> ignore
|
||||
true))
|
||||
|
||||
Result (Title = "Trigger a tray message",
|
||||
Action = (fun _ ->
|
||||
initContext.API.ShowMsg ("Sample tray message", "from the F# plugin")
|
||||
false))
|
||||
]
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"ID": "8FF5D5C1F8194958A12E8668FB7ECC04",
|
||||
"ActionKeyword": "hf",
|
||||
"Name": "Hello World FSharp",
|
||||
"Description": "Hello World FSharp",
|
||||
"Author": "Ioannis G.",
|
||||
"Version": "1.0.0",
|
||||
"Language": "fsharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "HelloWorldFSharp.dll",
|
||||
"IcoPath": "Images\\app.png"
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.6 KiB |
|
|
@ -1,27 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from flow.launcher import Flow.Launcher
|
||||
|
||||
class HelloWorld(FlowLauncher):
|
||||
|
||||
def query(self, query):
|
||||
results = []
|
||||
results.append({
|
||||
"Title": "Hello World",
|
||||
"SubTitle": "Query: {}".format(query),
|
||||
"IcoPath":"Images/app.ico",
|
||||
"ContextData": "ctxData"
|
||||
})
|
||||
return results
|
||||
|
||||
def context_menu(self, data):
|
||||
results = []
|
||||
results.append({
|
||||
"Title": "Context menu entry",
|
||||
"SubTitle": "Data: {}".format(data),
|
||||
"IcoPath":"Images/app.ico"
|
||||
})
|
||||
return results
|
||||
|
||||
if __name__ == "__main__":
|
||||
HelloWorld()
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"ID":"2f4e384e-76ce-45c3-aea2-b16f5e5c328f",
|
||||
"ActionKeyword":"h",
|
||||
"Name":"Hello World Python",
|
||||
"Description":"Hello World",
|
||||
"Author":"happlebao",
|
||||
"Version":"1.0",
|
||||
"Language":"python",
|
||||
"Website":"https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"IcoPath":"Images\\app.png",
|
||||
"ExecuteFileName":"main.py"
|
||||
}
|
||||
|
|
@ -36,7 +36,6 @@ function Copy-Resources ($path, $config) {
|
|||
$output = "$path\Output"
|
||||
$target = "$output\$config"
|
||||
Copy-Item -Recurse -Force $project\Images\* $target\Images\
|
||||
Copy-Item -Recurse -Force $path\Plugins\HelloWorldPython $target\Plugins\HelloWorldPython
|
||||
Copy-Item -Recurse -Force $path\JsonRPC $target\JsonRPC
|
||||
# making version static as multiple versions can exist in the nuget folder and in the case a breaking change is introduced.
|
||||
Copy-Item -Force $env:USERPROFILE\.nuget\packages\squirrel.windows\1.5.2\tools\Squirrel.exe $output\Update.exe
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: AssemblyVersion("1.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0")]
|
||||
[assembly: AssemblyInformationalVersion("1.0.0")]
|
||||
[assembly: AssemblyVersion("1.1.0")]
|
||||
[assembly: AssemblyFileVersion("1.1.0")]
|
||||
[assembly: AssemblyInformationalVersion("1.1.0")]
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
version: '0.9.0.{build}'
|
||||
version: '1.1.0.{build}'
|
||||
|
||||
init:
|
||||
- ps: |
|
||||
|
|
|
|||
Loading…
Reference in a new issue