diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 00000000000..23ced593e1b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -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** + diff --git a/.github/ISSUE_TEMPLATE/code-review.md b/.github/ISSUE_TEMPLATE/code-review.md new file mode 100644 index 00000000000..5cc53abec77 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/code-review.md @@ -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 +``` diff --git a/.github/ISSUE_TEMPLATE/discussion.md b/.github/ISSUE_TEMPLATE/discussion.md new file mode 100644 index 00000000000..8bd88c90fc8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/discussion.md @@ -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. diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 00000000000..5931632359d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -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. diff --git a/Flow.Launcher.Core/Configuration/Portable.cs b/Flow.Launcher.Core/Configuration/Portable.cs index c46a6102da2..44e4434bed7 100644 --- a/Flow.Launcher.Core/Configuration/Portable.cs +++ b/Flow.Launcher.Core/Configuration/Portable.cs @@ -32,10 +32,9 @@ public void DisablePortableMode() 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 @@ public void DisablePortableMode() } 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 @@ public void EnablePortableMode() 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 @@ public void EnablePortableMode() } 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 @@ public void CreateShortcuts() 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 @@ public void CreateUninstallerEntry() 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)) + { + } } /// @@ -152,21 +146,18 @@ internal void IndicateDeletion(string filePathTodelete) 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"); - - bool DataLocationPortableDeleteRequired = false; - bool DataLocationRoamingDeleteRequired = false; - - if ((roamingDataPath + "\\" + DataLocation.DeletionIndicatorFile).FileExits()) - DataLocationRoamingDeleteRequired = true; + var portableDataDir = Path.Combine(Directory.GetParent(Assembly.GetExecutingAssembly().Location.NonNull()).ToString(), "UserData"); + var roamingDataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FlowLauncher"); - if ((portableDataPath + "\\" + DataLocation.DeletionIndicatorFile).FileExits()) - DataLocationPortableDeleteRequired = true; + // 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 (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 @@ public void PreStartCleanUpAfterPortabilityUpdate() 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 @@ public bool CanUpdatePortability() 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.", diff --git a/Flow.Launcher.Core/Plugin/PluginConfig.cs b/Flow.Launcher.Core/Plugin/PluginConfig.cs index d12dee0c438..b946fa44d21 100644 --- a/Flow.Launcher.Core/Plugin/PluginConfig.cs +++ b/Flow.Launcher.Core/Plugin/PluginConfig.cs @@ -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 PluginMetadatas = new List(); - /// - /// Parse plugin metadata in giving directories + /// Parse plugin metadata in the given directories /// /// /// public static List Parse(string[] pluginDirectories) { - PluginMetadatas.Clear(); + var allPluginMetadata = new List(); var directories = pluginDirectories.SelectMany(Directory.GetDirectories); - ParsePluginConfigs(directories); - return PluginMetadatas; - } - private static void ParsePluginConfigs(IEnumerable 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 @@ private static void ParsePluginConfigs(IEnumerable directories) 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 @@ private static PluginMetadata GetPluginMetadata(string pluginDirectory) return null; } - if (!AllowedLanguage.IsAllowed(metadata.Language)) { Log.Error($"|PluginConfig.GetPluginMetadata|Invalid language <{metadata.Language}> for config <{configPath}>"); diff --git a/Flow.Launcher.Core/Plugin/PluginInstaller.cs b/Flow.Launcher.Core/Plugin/PluginInstaller.cs index df38ca608cd..7b980a3eea0 100644 --- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs +++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs @@ -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 @@ internal static void Install(string path) { 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 @@ internal static void Install(string path) .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 @@ internal static void Install(string path) 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 @@ internal static void Install(string path) 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 @@ private static PluginMetadata GetMetadataFromJson(string pluginDirectory) metadata = JsonConvert.DeserializeObject(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 @@ private static PluginMetadata GetMetadataFromJson(string pluginDirectory) } /// - /// unzip + /// unzip plugin contents to the given directory. /// - /// The ziped file. - /// The STR directory. + /// The path to the zip file. + /// The output directory. /// overwirte - 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(); } } } diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index b92226ccda5..5cde9de83a8 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -19,10 +19,6 @@ public static class PluginManager { private static IEnumerable _contextMenuPlugins; - /// - /// Directories that will hold Flow Launcher plugin directory - /// - public static List AllPlugins { get; private set; } public static readonly List GlobalPlugins = new List(); public static readonly Dictionary NonGlobalPlugins = new Dictionary(); @@ -32,27 +28,18 @@ public static class PluginManager // todo happlebao, this should not be public, the indicator function should be embeded public static PluginsSettings Settings; private static List _metadatas; - private static readonly string[] Directories = { Constant.PreinstalledDirectory, DataLocation.PluginsDirectory }; - private static void ValidateUserDirectory() - { - if (!Directory.Exists(DataLocation.PluginsDirectory)) - { - Directory.CreateDirectory(DataLocation.PluginsDirectory); - } - } + /// + /// Directories that will hold Flow Launcher plugin directory + /// + 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 @@ public static void ReloadData() static PluginManager() { - ValidateUserDirectory(); + // validate user directory + Directory.CreateDirectory(DataLocation.PluginsDirectory); // force old plugins use new python binding DeletePythonBinding(); } @@ -132,9 +120,10 @@ public static void InitializePlugins(IPublicAPI api) 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 @@ public static List ValidPluginsForQuery(Query query) public static List QueryForPlugin(PluginPair pair, Query query) { + var results = new List(); try { - List results = null; var metadata = pair.Metadata; var milliseconds = Stopwatch.Debug($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", () => { @@ -175,13 +164,12 @@ public static List QueryForPlugin(PluginPair pair, Query query) }); 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(); } + return results; } public static void UpdatePluginMetadata(List results, PluginMetadata metadata, Query query) @@ -221,47 +209,34 @@ public static IEnumerable GetPluginsForInterface() where T : IFea public static List GetContextMenusForPlugin(Result result) { + var results = new List(); 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(); + Log.Exception($"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{pluginPair.Metadata.Name}>", e); } } - else - { - return new List(); - } - + 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); } /// @@ -299,7 +274,7 @@ public static void RemoveActionKeyword(string id, string oldActionkeyword) GlobalPlugins.Remove(plugin); } - if(oldActionkeyword != Query.GlobalPluginWildcardSign) + if (oldActionkeyword != Query.GlobalPluginWildcardSign) NonGlobalPlugins.Remove(oldActionkeyword); diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 513d85c9670..1025f9bae34 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -21,7 +21,7 @@ public static class PluginsLoader public static List Plugins(List 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 @@ public static IEnumerable DotNetPlugins(List source) 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; - } + var types = assembly.GetTypes(); + var type = types.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(typeof(IPlugin))); - Type type; - try + plugin = (IPlugin)Activator.CreateInstance(type); + } + catch (Exception e) when (assembly == null) { - var types = assembly.GetTypes(); - - type = types.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(typeof(IPlugin))); + 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 + catch (Exception e) { - plugin = (IPlugin)Activator.CreateInstance(type); + Log.Exception($"|PluginsLoader.DotNetPlugins|The following plugin has errored and can not be loaded: <{metadata.Name}>", e); } - catch (Exception e) + + if (plugin == null) { erroredPlugins.Add(metadata.Name); - - Log.Exception($"|PluginsLoader.DotNetPlugins|The following plugin has errored and can not be loaded: <{metadata.Name}>", e); 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 @@ public static IEnumerable DotNetPlugins(List source) return plugins; } - public static IEnumerable PythonPlugins(List source, string pythonDirecotry) + public static IEnumerable PythonPlugins(List 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(); } } else { Log.Error("|PluginsLoader.PythonPlugins|PATH environment variable is not set."); - return new List(); } } 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 (); + 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(); + } + else + { + return source + .Where(o => o.Language.ToUpper() == AllowedLanguage.Python) + .Select(metadata => new PluginPair + { + Plugin = new PythonPlugin(Constant.PythonPath), + Metadata = metadata + }); + } } public static IEnumerable ExecutablePlugins(IEnumerable 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 + }); } } diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 3c4a15d3033..3036055ba23 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -86,8 +86,8 @@ public async Task UpdateApp(bool silentIfLatestVersion = true) 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 { diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index 0936ce8c44b..a96d0e5b3cd 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -8,6 +8,7 @@ public static class Constant { public const string FlowLauncher = "Flow.Launcher"; public const string Plugins = "Plugins"; + public const string PluginMetadataFileName = "plugin.json"; public const string ApplicationFileName = FlowLauncher + ".exe"; diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index aef8bf7ed43..79ccfd7af44 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -83,9 +83,18 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption bool allSubstringsContainedInCompareString = true; var indexList = new List(); + List spaceIndices = new List(); 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 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption // 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); + } + + // To get the index of the closest space which preceeds the first matching index + private int CalculateClosestSpaceIndex(List 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, + private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex, string fullStringToCompareWithoutCase, string currentQuerySubstring) { var allMatch = true; @@ -299,13 +324,13 @@ private int ScoreAfterSearchPrecisionFilter(int rawScore) public class MatchOption { /// - /// prefix of match char, use for hightlight + /// prefix of match char, use for highlight /// [Obsolete("this is never used")] public string Prefix { get; set; } = ""; /// - /// suffix of match char, use for hightlight + /// suffix of match char, use for highlight /// [Obsolete("this is never used")] public string Suffix { get; set; } = ""; diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index ff4700e944c..0c69a6b9bb7 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -14,10 +14,10 @@ - 1.0.0 - 1.0.0-beta3 - 1.0.0 - 1.0.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 Flow.Launcher.Plugin Flow-Launcher MIT diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index bfd2fb0a4fa..d210ba1d828 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -130,5 +130,15 @@ public Result() { } /// Plugin ID that generated this result /// public string PluginID { get; internal set; } + + /// + /// Show message as ToolTip on result Title hover over + /// + public string TitleToolTip { get; set; } + + /// + /// Show message as ToolTip on result SubTitle hover over + /// + public string SubTitleToolTip { get; set; } } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index ce067e8a0f9..98cd777aac4 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -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 @@ public static void OpenPath(string fileOrFolderPath) } } + public static void OpenContainingFolder(string path) + { + Process.Start(FileExplorerProgramEXE, $" /select,\"{path}\""); + } + /// /// This checks whether a given string is a directory path or network location string. /// It does not check if location actually exists. diff --git a/Flow.Launcher.Test/FuzzyMatcherTest.cs b/Flow.Launcher.Test/FuzzyMatcherTest.cs index 68d5d43ace3..468b944573e 100644 --- a/Flow.Launcher.Test/FuzzyMatcherTest.cs +++ b/Flow.Launcher.Test/FuzzyMatcherTest.cs @@ -77,7 +77,7 @@ public void MatchTest() } [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 @@ public void WhenGivenNotAllCharactersFoundInSearchStringThenShouldReturnZeroScor [TestCase("cand")] [TestCase("cpywa")] [TestCase("ccs")] - public void WhenGivenStringsAndAppliedPrecisionFilteringThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm) + public void GivenQueryString_WhenAppliedPrecisionFiltering_ThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm) { var results = new List(); var matcher = new StringMatcher(); @@ -107,7 +107,10 @@ public void WhenGivenStringsAndAppliedPrecisionFilteringThenShouldReturnGreaterT 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 @@ public void WhenGivenStringsAndAppliedPrecisionFilteringThenShouldReturnGreaterT } [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 @@ public void WhenGivenQueryStringThenShouldReturnCurrentScoring(string queryStrin [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 @@ public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual( [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 @@ public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual( [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 @@ public void WhenGivenQueryShouldReturnResultsContainingAllQuerySubstrings( $"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}"); + } } } \ No newline at end of file diff --git a/Flow.Launcher.sln b/Flow.Launcher.sln index b244c97defd..43c012b5bb4 100644 --- a/Flow.Launcher.sln +++ b/Flow.Launcher.sln @@ -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} diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index a8560c26347..3280dc457a1 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -62,7 +62,7 @@ @@ -71,7 +71,7 @@ - diff --git a/Flow.Launcher/Themes/BlurBlack.xaml b/Flow.Launcher/Themes/BlurBlack.xaml index 488048cf44d..40ecbc778d0 100644 --- a/Flow.Launcher/Themes/BlurBlack.xaml +++ b/Flow.Launcher/Themes/BlurBlack.xaml @@ -21,6 +21,7 @@