diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 04042a3a9..0df5228ba 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -62,6 +62,8 @@ TobiasSekan Img img resx +bak +tmp directx mvvm dlg @@ -83,4 +85,6 @@ btn otf searchplugin Noresult -wpftk \ No newline at end of file +wpftk +mkv +flac diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 28d57501b..f3b2eed87 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -259,9 +259,16 @@ namespace Flow.Launcher.Core.Plugin await using var registeredEvent = token.Register(() => { - if (!process.HasExited) - process.Kill(); - sourceBuffer.Dispose(); + try + { + if (!process.HasExited) + process.Kill(); + sourceBuffer.Dispose(); + } + catch (Exception e) + { + Log.Exception("|JsonRPCPlugin.ExecuteAsync|Exception when kill process", e); + } }); try @@ -288,7 +295,7 @@ namespace Flow.Launcher.Core.Plugin } sourceBuffer.Seek(0, SeekOrigin.Begin); - + return sourceBuffer; } @@ -376,7 +383,8 @@ namespace Flow.Launcher.Core.Plugin sep.SetResourceReference(Separator.BackgroundProperty, "Color03B"); /* for theme change */ var panel = new StackPanel { - Orientation = Orientation.Vertical, VerticalAlignment = VerticalAlignment.Center, + Orientation = Orientation.Vertical, + VerticalAlignment = VerticalAlignment.Center, Margin = settingLabelPanelMargin }; RowDefinition gridRow = new RowDefinition(); @@ -390,8 +398,10 @@ namespace Flow.Launcher.Core.Plugin }; var desc = new TextBlock() { - Text = attribute.Description, FontSize = 12, - VerticalAlignment = VerticalAlignment.Center,Margin = settingDescMargin, + Text = attribute.Description, + FontSize = 12, + VerticalAlignment = VerticalAlignment.Center, + Margin = settingDescMargin, TextWrapping = TextWrapping.WrapWithOverflow }; desc.SetResourceReference(TextBlock.ForegroundProperty, "Color04B"); @@ -405,7 +415,7 @@ namespace Flow.Launcher.Core.Plugin panel.Children.Add(name); panel.Children.Add(desc); } - + Grid.SetColumn(panel, 0); Grid.SetRow(panel, rowCount); @@ -420,20 +430,20 @@ namespace Flow.Launcher.Core.Plugin { Text = attribute.Description.Replace("\\r\\n", "\r\n"), Margin = settingTextBlockMargin, - Padding = new Thickness(0,0,0,0), + Padding = new Thickness(0, 0, 0, 0), HorizontalAlignment = System.Windows.HorizontalAlignment.Left, TextAlignment = TextAlignment.Left, TextWrapping = TextWrapping.Wrap }; - Grid.SetColumn(contentControl, 0); - Grid.SetColumnSpan(contentControl, 2); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; + Grid.SetColumn(contentControl, 0); + Grid.SetColumnSpan(contentControl, 2); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + break; } case "input": { @@ -449,50 +459,49 @@ namespace Flow.Launcher.Core.Plugin Settings[attribute.Name] = textBox.Text; }; contentControl = textBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + break; } case "inputWithFileBtn": + { + var textBox = new TextBox() { - var textBox = new TextBox() - { - Margin = new Thickness(10, 0, 0, 0), - Text = Settings[attribute.Name] as string ?? string.Empty, - HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, - ToolTip = attribute.Description - }; - textBox.TextChanged += (_, _) => - { - Settings[attribute.Name] = textBox.Text; - }; - var Btn = new System.Windows.Controls.Button() - { - Margin = new Thickness(10,0,0,0), - Content = "Browse" - }; - var dockPanel = new DockPanel() - { - Margin = settingControlMargin - }; - DockPanel.SetDock(Btn, Dock.Right); - dockPanel.Children.Add(Btn); - dockPanel.Children.Add(textBox); - contentControl = dockPanel; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; - } + Margin = new Thickness(10, 0, 0, 0), + Text = Settings[attribute.Name] as string ?? string.Empty, + HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, + ToolTip = attribute.Description + }; + textBox.TextChanged += (_, _) => + { + Settings[attribute.Name] = textBox.Text; + }; + var Btn = new System.Windows.Controls.Button() + { + Margin = new Thickness(10, 0, 0, 0), Content = "Browse" + }; + var dockPanel = new DockPanel() + { + Margin = settingControlMargin + }; + DockPanel.SetDock(Btn, Dock.Right); + dockPanel.Children.Add(Btn); + dockPanel.Children.Add(textBox); + contentControl = dockPanel; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + break; + } case "textarea": { var textBox = new TextBox() @@ -511,14 +520,14 @@ namespace Flow.Launcher.Core.Plugin Settings[attribute.Name] = ((TextBox)sender).Text; }; contentControl = textBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + break; } case "passwordBox": { @@ -535,14 +544,14 @@ namespace Flow.Launcher.Core.Plugin Settings[attribute.Name] = ((PasswordBox)sender).Password; }; contentControl = passwordBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + break; } case "dropdown": { @@ -559,14 +568,14 @@ namespace Flow.Launcher.Core.Plugin Settings[attribute.Name] = (string)((System.Windows.Controls.ComboBox)sender).SelectedItem; }; contentControl = comboBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + break; } case "checkbox": var checkBox = new CheckBox @@ -592,13 +601,11 @@ namespace Flow.Launcher.Core.Plugin case "hyperlink": var hyperlink = new Hyperlink { - ToolTip = attribute.Description, - NavigateUri = attribute.url + ToolTip = attribute.Description, NavigateUri = attribute.url }; var linkbtn = new System.Windows.Controls.Button { - HorizontalAlignment = System.Windows.HorizontalAlignment.Right, - Margin = settingControlMargin + HorizontalAlignment = System.Windows.HorizontalAlignment.Right, Margin = settingControlMargin }; linkbtn.Content = attribute.urlLabel; @@ -619,7 +626,7 @@ namespace Flow.Launcher.Core.Plugin mainPanel.Children.Add(panel); mainPanel.Children.Add(contentControl); rowCount++; - + } return settingWindow; } diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 91de8298c..3f64b273e 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -33,7 +33,7 @@ namespace Flow.Launcher.Core public async Task UpdateAppAsync(IPublicAPI api, bool silentUpdate = true) { - await UpdateLock.WaitAsync(); + await UpdateLock.WaitAsync().ConfigureAwait(false); try { if (!silentUpdate) @@ -88,9 +88,13 @@ namespace Flow.Launcher.Core UpdateManager.RestartApp(Constant.ApplicationFileName); } } - catch (Exception e) when (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException) + catch (Exception e) { - Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e); + if ((e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)) + Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e); + else + Log.Exception($"|Updater.UpdateApp|Error Occurred", e); + if (!silentUpdate) api.ShowMsg(api.GetTranslation("update_flowlauncher_fail"), api.GetTranslation("update_flowlauncher_check_connection")); diff --git a/Flow.Launcher.Infrastructure/Helper.cs b/Flow.Launcher.Infrastructure/Helper.cs index faa4c93b5..db575de90 100644 --- a/Flow.Launcher.Infrastructure/Helper.cs +++ b/Flow.Launcher.Infrastructure/Helper.cs @@ -1,4 +1,6 @@ -using System; +#nullable enable + +using System; using System.IO; using System.Runtime.CompilerServices; using System.Text.Json; @@ -16,7 +18,7 @@ namespace Flow.Launcher.Infrastructure /// /// http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy /// - public static T NonNull(this T obj) + public static T NonNull(this T? obj) { if (obj == null) { diff --git a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs index 5bd97714c..b92bc0207 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Windows.Input; +using System.Windows.Navigation; namespace Flow.Launcher.Infrastructure.Hotkey { @@ -11,10 +12,10 @@ namespace Flow.Launcher.Infrastructure.Hotkey public bool Shift { get; set; } public bool Win { get; set; } public bool Ctrl { get; set; } - public Key CharKey { get; set; } + public Key CharKey { get; set; } = Key.None; - Dictionary specialSymbolDictionary = new Dictionary + private static readonly Dictionary specialSymbolDictionary = new Dictionary { {Key.Space, "Space"}, {Key.Oem3, "~"} @@ -27,19 +28,19 @@ namespace Flow.Launcher.Infrastructure.Hotkey ModifierKeys modifierKeys = ModifierKeys.None; if (Alt) { - modifierKeys = ModifierKeys.Alt; + modifierKeys |= ModifierKeys.Alt; } if (Shift) { - modifierKeys = modifierKeys | ModifierKeys.Shift; + modifierKeys |= ModifierKeys.Shift; } if (Win) { - modifierKeys = modifierKeys | ModifierKeys.Windows; + modifierKeys |= ModifierKeys.Windows; } if (Ctrl) { - modifierKeys = modifierKeys | ModifierKeys.Control; + modifierKeys |= ModifierKeys.Control; } return modifierKeys; } @@ -86,7 +87,7 @@ namespace Flow.Launcher.Infrastructure.Hotkey Ctrl = true; keys.Remove("Ctrl"); } - if (keys.Count > 0) + if (keys.Count == 1) { string charKey = keys[0]; KeyValuePair? specialSymbolPair = specialSymbolDictionary.FirstOrDefault(pair => pair.Value == charKey); @@ -110,36 +111,75 @@ namespace Flow.Launcher.Infrastructure.Hotkey public override string ToString() { - string text = string.Empty; + List keys = new List(); if (Ctrl) { - text += "Ctrl + "; + keys.Add("Ctrl"); } if (Alt) { - text += "Alt + "; + keys.Add("Alt"); } if (Shift) { - text += "Shift + "; + keys.Add("Shift"); } if (Win) { - text += "Win + "; + keys.Add("Win"); } if (CharKey != Key.None) { - text += specialSymbolDictionary.ContainsKey(CharKey) + keys.Add(specialSymbolDictionary.ContainsKey(CharKey) ? specialSymbolDictionary[CharKey] - : CharKey.ToString(); - } - else if (!string.IsNullOrEmpty(text)) - { - text = text.Remove(text.Length - 3); + : CharKey.ToString()); } + return string.Join(" + ", keys); + } - return text; + public bool Validate() + { + switch (CharKey) + { + case Key.LeftAlt: + case Key.RightAlt: + case Key.LeftCtrl: + case Key.RightCtrl: + case Key.LeftShift: + case Key.RightShift: + case Key.LWin: + case Key.RWin: + return false; + default: + if (ModifierKeys == ModifierKeys.None) + { + return !((CharKey >= Key.A && CharKey <= Key.Z) || + (CharKey >= Key.D0 && CharKey <= Key.D9) || + (CharKey >= Key.NumPad0 && CharKey <= Key.NumPad9)); + } + else + { + return CharKey != Key.None; + } + } + } + + public override bool Equals(object obj) + { + if (obj is HotkeyModel other) + { + return ModifierKeys == other.ModifierKeys && CharKey == other.CharKey; + } + else + { + return false; + } + } + + public override int GetHashCode() + { + return HashCode.Combine(ModifierKeys, CharKey); } } } diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs index 0083ccb87..43e7ddab7 100644 --- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Globalization; using System.IO; using System.Text.Json; @@ -11,62 +12,82 @@ namespace Flow.Launcher.Infrastructure.Storage /// public class JsonStorage where T : new() { - protected T _data; + protected T? Data; + // need a new directory name public const string DirectoryName = "Settings"; public const string FileSuffix = ".json"; - public string FilePath { get; set; } - public string DirectoryPath { get; set; } + + protected string FilePath { get; init; } = null!; + + private string TempFilePath => $"{FilePath}.tmp"; + + private string BackupFilePath => $"{FilePath}.bak"; + + protected string DirectoryPath { get; init; } = null!; public T Load() { + string? serialized = null; + if (File.Exists(FilePath)) { - var serialized = File.ReadAllText(FilePath); - if (!string.IsNullOrWhiteSpace(serialized)) + serialized = File.ReadAllText(FilePath); + } + + if (!string.IsNullOrEmpty(serialized)) + { + try { - Deserialize(serialized); + Data = JsonSerializer.Deserialize(serialized) ?? TryLoadBackup() ?? LoadDefault(); } - else + catch (JsonException) { - LoadDefault(); + Data = TryLoadBackup() ?? LoadDefault(); } } else { - LoadDefault(); + Data = TryLoadBackup() ?? LoadDefault(); } - return _data.NonNull(); + + return Data.NonNull(); } - private void Deserialize(string serialized) - { - try - { - _data = JsonSerializer.Deserialize(serialized); - } - catch (JsonException e) - { - LoadDefault(); - Log.Exception($"|JsonStorage.Deserialize|Deserialize error for json <{FilePath}>", e); - } - - if (_data == null) - { - LoadDefault(); - } - } - - private void LoadDefault() + private T LoadDefault() { if (File.Exists(FilePath)) { BackupOriginFile(); } - _data = new T(); - Save(); + return new T(); + } + + private T? TryLoadBackup() + { + if (!File.Exists(BackupFilePath)) + return default; + + try + { + var data = JsonSerializer.Deserialize(File.ReadAllText(BackupFilePath)); + + if (data != null) + { + Log.Info($"|JsonStorage.Load|Failed to load settings.json, {BackupFilePath} restored successfully"); + File.Replace(BackupFilePath, FilePath, null); + + return data; + } + + return default; + } + catch (JsonException) + { + return default; + } } private void BackupOriginFile() @@ -82,13 +103,22 @@ namespace Flow.Launcher.Infrastructure.Storage public void Save() { - string serialized = JsonSerializer.Serialize(_data, new JsonSerializerOptions() { WriteIndented = true }); + string serialized = JsonSerializer.Serialize(Data, + new JsonSerializerOptions + { + WriteIndented = true + }); - File.WriteAllText(FilePath, serialized); + File.WriteAllText(TempFilePath, serialized); + + if (!File.Exists(FilePath)) + { + File.Move(TempFilePath, FilePath); + } + else + { + File.Replace(TempFilePath, FilePath, BackupFilePath); + } } } - - [Obsolete("Deprecated as of Flow Launcher v1.8.0, on 2021.06.21. " + - "This is used only for Everything plugin v1.4.9 or below backwards compatibility")] - public class JsonStrorage : JsonStorage where T : new() { } } diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs index 923a1a6b5..abe3f55b5 100644 --- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs @@ -18,7 +18,7 @@ namespace Flow.Launcher.Infrastructure.Storage public PluginJsonStorage(T data) : this() { - _data = data; + Data = data; } } } diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 22c47ae34..fb07f8255 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -14,10 +14,10 @@ - 3.0.1 - 3.0.1 - 3.0.1 - 3.0.1 + 3.1.0 + 3.1.0 + 3.1.0 + 3.1.0 Flow.Launcher.Plugin Flow-Launcher MIT diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 5cb3a171a..bd8d32ff5 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -1,7 +1,9 @@ using System; using System.Diagnostics; using System.IO; +#pragma warning disable IDE0005 using System.Windows; +#pragma warning restore IDE0005 namespace Flow.Launcher.Plugin.SharedCommands { @@ -206,22 +208,16 @@ namespace Flow.Launcher.Plugin.SharedCommands /// public static string GetPreviousExistingDirectory(Func locationExists, string path) { - var previousDirectoryPath = ""; var index = path.LastIndexOf('\\'); if (index > 0 && index < (path.Length - 1)) { - previousDirectoryPath = path.Substring(0, index + 1); - if (!locationExists(previousDirectoryPath)) - { - return ""; - } + string previousDirectoryPath = path.Substring(0, index + 1); + return locationExists(previousDirectoryPath) ? previousDirectoryPath : ""; } else { return ""; } - - return previousDirectoryPath; } /// @@ -241,5 +237,33 @@ namespace Flow.Launcher.Plugin.SharedCommands return path; } + + /// + /// Returns if contains . + /// From https://stackoverflow.com/a/66877016 + /// + /// Parent path + /// Sub path + /// If , when and are equal, returns + /// + public static bool PathContains(string parentPath, string subPath, bool allowEqual = false) + { + var rel = Path.GetRelativePath(parentPath.EnsureTrailingSlash(), subPath); + return (rel != "." || allowEqual) + && rel != ".." + && !rel.StartsWith("../") + && !rel.StartsWith(@"..\") + && !Path.IsPathRooted(rel); + } + + /// + /// Returns path ended with "\" + /// + /// + /// + public static string EnsureTrailingSlash(this string path) + { + return path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; + } } } diff --git a/Flow.Launcher.Test/FilesFoldersTest.cs b/Flow.Launcher.Test/FilesFoldersTest.cs new file mode 100644 index 000000000..d16826053 --- /dev/null +++ b/Flow.Launcher.Test/FilesFoldersTest.cs @@ -0,0 +1,53 @@ +using Flow.Launcher.Plugin.SharedCommands; +using NUnit.Framework; + +namespace Flow.Launcher.Test +{ + [TestFixture] + + public class FilesFoldersTest + { + // Testcases from https://stackoverflow.com/a/31941905/20703207 + // Disk + [TestCase(@"c:", @"c:\foo", true)] + [TestCase(@"c:\", @"c:\foo", true)] + // Slash + [TestCase(@"c:\foo\bar\", @"c:\foo\", false)] + [TestCase(@"c:\foo\bar", @"c:\foo\", false)] + [TestCase(@"c:\foo", @"c:\foo\bar", true)] + [TestCase(@"c:\foo\", @"c:\foo\bar", true)] + // File + [TestCase(@"c:\foo", @"c:\foo\a.txt", true)] + [TestCase(@"c:\foo", @"c:/foo/a.txt", true)] + [TestCase(@"c:\FOO\a.txt", @"c:\foo", false)] + [TestCase(@"c:\foo\a.txt", @"c:\foo\", false)] + [TestCase(@"c:\foobar\a.txt", @"c:\foo", false)] + [TestCase(@"c:\foobar\a.txt", @"c:\foo\", false)] + [TestCase(@"c:\foo\", @"c:\foo.txt", false)] + // Prefix + [TestCase(@"c:\foo", @"c:\foobar", false)] + [TestCase(@"C:\Program", @"C:\Program Files\", false)] + [TestCase(@"c:\foobar", @"c:\foo\a.txt", false)] + [TestCase(@"c:\foobar\", @"c:\foo\a.txt", false)] + // Edge case + [TestCase(@"c:\foo", @"c:\foo\..\bar\baz", false)] + [TestCase(@"c:\bar", @"c:\foo\..\bar\baz", true)] + [TestCase(@"c:\barr", @"c:\foo\..\bar\baz", false)] + // Equality + [TestCase(@"c:\foo", @"c:\foo", false)] + [TestCase(@"c:\foo\", @"c:\foo", false)] + [TestCase(@"c:\foo", @"c:\foo\", false)] + public void GivenTwoPaths_WhenCheckPathContains_ThenShouldBeExpectedResult(string parentPath, string path, bool expectedResult) + { + Assert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path)); + } + + [TestCase(@"c:\foo", @"c:\foo", true)] + [TestCase(@"c:\foo\", @"c:\foo", true)] + [TestCase(@"c:\foo", @"c:\foo\", true)] + public void GivenTwoPathsAreTheSame_WhenCheckPathContains_ThenShouldBeTrue(string parentPath, string path, bool expectedResult) + { + Assert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path, true)); + } + } +} diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 36f0294a9..e9d37433f 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -7,9 +7,11 @@ using Flow.Launcher.Plugin.SharedCommands; using NUnit.Framework; using System; using System.Collections.Generic; +using System.IO; using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; +using static Flow.Launcher.Plugin.Explorer.Search.SearchManager; namespace Flow.Launcher.Test.Plugins { @@ -176,7 +178,7 @@ namespace Flow.Launcher.Test.Plugins var searchManager = new SearchManager(new Settings(), new PluginInitContext()); // When - var result = SearchManager.IsFileContentSearch(query.ActionKeyword); + var result = searchManager.IsFileContentSearch(query.ActionKeyword); // Then Assert.IsTrue(result, @@ -193,6 +195,7 @@ namespace Flow.Launcher.Test.Plugins [TestCase(@"c:\>*", true)] [TestCase(@"c:\>", true)] [TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)] + [TestCase(@"c:\SomeLocation\SomeOtherLocation", true)] public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult) { // When, Given @@ -393,5 +396,68 @@ namespace Flow.Launcher.Test.Plugins // Then Assert.AreEqual(result, expectedResult); } + + [TestCase(@"c:\foo", @"c:\foo", true)] + [TestCase(@"C:\Foo\", @"c:\foo\", true)] + [TestCase(@"c:\foo", @"c:\foo\", false)] + public void GivenTwoPaths_WhenCompared_ThenShouldBeExpectedSameOrDifferent(string path1, string path2, bool expectedResult) + { + // Given + var comparator = PathEqualityComparator.Instance; + var result1 = new Result + { + Title = Path.GetFileName(path1), + SubTitle = path1 + }; + var result2 = new Result + { + Title = Path.GetFileName(path2), + SubTitle = path2 + }; + + // When, Then + Assert.AreEqual(expectedResult, comparator.Equals(result1, result2)); + } + + [TestCase(@"c:\foo\", @"c:\foo\")] + [TestCase(@"C:\Foo\", @"c:\foo\")] + public void GivenTwoPaths_WhenComparedHasCode_ThenShouldBeSame(string path1, string path2) + { + // Given + var comparator = PathEqualityComparator.Instance; + var result1 = new Result + { + Title = Path.GetFileName(path1), + SubTitle = path1 + }; + var result2 = new Result + { + Title = Path.GetFileName(path2), + SubTitle = path2 + }; + + var hash1 = comparator.GetHashCode(result1); + var hash2 = comparator.GetHashCode(result2); + + // When, Then + Assert.IsTrue(hash1 == hash2); + } + + [TestCase(@"%appdata%", true)] + [TestCase(@"%appdata%\123", true)] + [TestCase(@"c:\foo %appdata%\", false)] + [TestCase(@"c:\users\%USERNAME%\downloads", true)] + [TestCase(@"c:\downloads", false)] + [TestCase(@"%", false)] + [TestCase(@"%%", false)] + [TestCase(@"%bla%blabla%", false)] + public void GivenPath_WhenHavingEnvironmentVariableOrNot_ThenShouldBeExpected(string path, bool expectedResult) + { + // When + var result = EnvironmentVariables.HasEnvironmentVar(path); + + // Then + Assert.AreEqual(result, expectedResult); + } } } diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 43fa0eddb..1d398276d 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Timers; using System.Windows; @@ -84,14 +85,14 @@ namespace Flow.Launcher Current.MainWindow = window; Current.MainWindow.Title = Constant.FlowLauncher; - + HotKeyMapper.Initialize(_mainVM); - // happlebao todo temp fix for instance code logic + // todo temp fix for instance code logic // load plugin before change language, because plugin language also needs be changed InternationalizationManager.Instance.Settings = _settings; InternationalizationManager.Instance.ChangeLanguage(_settings.Language); - // main windows needs initialized before theme change because of blur settigns + // main windows needs initialized before theme change because of blur settings ThemeManager.Instance.Settings = _settings; ThemeManager.Instance.ChangeTheme(_settings.Theme); @@ -130,20 +131,17 @@ namespace Flow.Launcher //[Conditional("RELEASE")] private void AutoUpdates() { - Task.Run(async () => + _ = Task.Run(async () => { if (_settings.AutoUpdates) { - // check udpate every 5 hours - var timer = new Timer(1000 * 60 * 60 * 5); - timer.Elapsed += async (s, e) => - { - await _updater.UpdateAppAsync(API); - }; - timer.Start(); - - // check updates on startup + // check update every 5 hours + var timer = new PeriodicTimer(TimeSpan.FromHours(5)); await _updater.UpdateAppAsync(API); + + while (await timer.WaitForNextTickAsync()) + // check updates on startup + await _updater.UpdateAppAsync(API); } }); } diff --git a/Flow.Launcher/HotkeyControl.xaml b/Flow.Launcher/HotkeyControl.xaml index 5a593d20a..acf4a21ec 100644 --- a/Flow.Launcher/HotkeyControl.xaml +++ b/Flow.Launcher/HotkeyControl.xaml @@ -48,6 +48,7 @@ Margin="0,0,18,0" VerticalContentAlignment="Center" input:InputMethod.IsInputMethodEnabled="False" + GotFocus="tbHotkey_GotFocus" LostFocus="tbHotkey_LostFocus" PreviewKeyDown="TbHotkey_OnPreviewKeyDown" TabIndex="100" /> diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index d746c8fd2..b71df9758 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -9,16 +9,11 @@ using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Plugin; using System.Threading; -using System.Windows.Interop; namespace Flow.Launcher { public partial class HotkeyControl : UserControl { - private Brush tbMsgForegroundColorOriginal; - - private string tbMsgTextOriginal; - public HotkeyModel CurrentHotkey { get; private set; } public bool CurrentHotkeyAvailable { get; private set; } @@ -29,8 +24,6 @@ namespace Flow.Launcher public HotkeyControl() { InitializeComponent(); - tbMsgTextOriginal = tbMsg.Text; - tbMsgForegroundColorOriginal = tbMsg.Foreground; } private CancellationTokenSource hotkeyUpdateSource; @@ -55,9 +48,7 @@ namespace Flow.Launcher specialKeyState.CtrlPressed, key); - var hotkeyString = hotkeyModel.ToString(); - - if (hotkeyString == tbHotkey.Text) + if (hotkeyModel.Equals(CurrentHotkey)) { return; } @@ -72,33 +63,32 @@ namespace Flow.Launcher public async Task SetHotkeyAsync(HotkeyModel keyModel, bool triggerValidate = true) { - CurrentHotkey = keyModel; - - tbHotkey.Text = CurrentHotkey.ToString(); + tbHotkey.Text = keyModel.ToString(); tbHotkey.Select(tbHotkey.Text.Length, 0); if (triggerValidate) { - CurrentHotkeyAvailable = CheckHotkeyAvailability(); - if (!CurrentHotkeyAvailable) - { - tbMsg.Foreground = new SolidColorBrush(Colors.Red); - tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable"); - } - else - { - tbMsg.Foreground = new SolidColorBrush(Colors.Green); - tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success"); - } - tbMsg.Visibility = Visibility.Visible; + bool hotkeyAvailable = CheckHotkeyAvailability(keyModel); + CurrentHotkeyAvailable = hotkeyAvailable; + SetMessage(hotkeyAvailable); OnHotkeyChanged(); var token = hotkeyUpdateSource.Token; await Task.Delay(500, token); if (token.IsCancellationRequested) return; - FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), null); - Keyboard.ClearFocus(); + + if (CurrentHotkeyAvailable) + { + CurrentHotkey = keyModel; + // To trigger LostFocus + FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), null); + Keyboard.ClearFocus(); + } + } + else + { + CurrentHotkey = keyModel; } } @@ -107,14 +97,40 @@ namespace Flow.Launcher return SetHotkeyAsync(new HotkeyModel(keyStr), triggerValidate); } - private bool CheckHotkeyAvailability() => HotKeyMapper.CheckAvailability(CurrentHotkey); + private static bool CheckHotkeyAvailability(HotkeyModel hotkey) => hotkey.Validate() && HotKeyMapper.CheckAvailability(hotkey); public new bool IsFocused => tbHotkey.IsFocused; private void tbHotkey_LostFocus(object sender, RoutedEventArgs e) { - tbMsg.Text = tbMsgTextOriginal; + tbHotkey.Text = CurrentHotkey?.ToString() ?? ""; + tbHotkey.Select(tbHotkey.Text.Length, 0); + } + + private void tbHotkey_GotFocus(object sender, RoutedEventArgs e) + { + ResetMessage(); + } + + private void ResetMessage() + { + tbMsg.Text = InternationalizationManager.Instance.GetTranslation("flowlauncherPressHotkey"); tbMsg.SetResourceReference(TextBox.ForegroundProperty, "Color05B"); } + + private void SetMessage(bool hotkeyAvailable) + { + if (!hotkeyAvailable) + { + tbMsg.Foreground = new SolidColorBrush(Colors.Red); + tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable"); + } + else + { + tbMsg.Foreground = new SolidColorBrush(Colors.Green); + tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success"); + } + tbMsg.Visibility = Visibility.Visible; + } } } diff --git a/Flow.Launcher/PriorityChangeWindow.xaml b/Flow.Launcher/PriorityChangeWindow.xaml index d6aadead9..c917eeffc 100644 --- a/Flow.Launcher/PriorityChangeWindow.xaml +++ b/Flow.Launcher/PriorityChangeWindow.xaml @@ -85,6 +85,8 @@ + + + F1 M12000,12000z M0,0z M10354,10962C10326,10951 10279,10927 10249,10907 10216,10886 9476,10153 8370,9046 7366,8042 6541,7220 6536,7220 6532,7220 6498,7242 6461,7268 6213,7447 5883,7619 5592,7721 5194,7860 4802,7919 4360,7906 3612,7886 2953,7647 2340,7174 2131,7013 1832,6699 1664,6465 1394,6088 1188,5618 1097,5170 1044,4909 1030,4764 1030,4470 1030,4130 1056,3914 1135,3609 1263,3110 1511,2633 1850,2235 1936,2134 2162,1911 2260,1829 2781,1395 3422,1120 4090,1045 4271,1025 4667,1025 4848,1045 5505,1120 6100,1368 6630,1789 6774,1903 7081,2215 7186,2355 7362,2588 7467,2759 7579,2990 7802,3455 7911,3937 7911,4460 7911,4854 7861,5165 7737,5542 7684,5702 7675,5724 7602,5885 7517,6071 7390,6292 7270,6460 7242,6499 7220,6533 7220,6538 7220,6542 8046,7371 9055,8380 10441,9766 10898,10229 10924,10274 10945,10308 10966,10364 10976,10408 10990,10472 10991,10493 10980,10554 10952,10717 10840,10865 10690,10937 10621,10971 10607,10974 10510,10977 10425,10980 10395,10977 10354,10962z M4685,7050C5214,7001 5694,6809 6100,6484 6209,6396 6396,6209 6484,6100 7151,5267 7246,4110 6721,3190 6369,2571 5798,2137 5100,1956 4706,1855 4222,1855 3830,1957 3448,2056 3140,2210 2838,2453 2337,2855 2010,3427 1908,4080 1877,4274 1877,4656 1908,4850 1948,5105 2028,5370 2133,5590 2459,6272 3077,6782 3810,6973 3967,7014 4085,7034 4290,7053 4371,7061 4583,7059 4685,7050z diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 6905acf4d..b29f37bff 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -841,15 +841,20 @@ namespace Flow.Launcher.ViewModel queryBuilder.Replace('@' + shortcut.Key, shortcut.Expand()); } + string customExpanded = queryBuilder.ToString(); + Application.Current.Dispatcher.Invoke(() => { foreach (var shortcut in builtInShortcuts) { try { - var expansion = shortcut.Expand(); - queryBuilder.Replace(shortcut.Key, expansion); - queryBuilderTmp.Replace(shortcut.Key, expansion); + if (customExpanded.Contains(shortcut.Key)) + { + var expansion = shortcut.Expand(); + queryBuilder.Replace(shortcut.Key, expansion); + queryBuilderTmp.Replace(shortcut.Key, expansion); + } } catch (Exception e) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Exceptions/EngineNotAvailableException.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Exceptions/EngineNotAvailableException.cs index 1a48892f5..a6315fb4b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Exceptions/EngineNotAvailableException.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Exceptions/EngineNotAvailableException.cs @@ -3,8 +3,6 @@ using System; using System.Threading.Tasks; using System.Windows; -using Flow.Launcher.Plugin.Explorer.Search.IProvider; -using JetBrains.Annotations; namespace Flow.Launcher.Plugin.Explorer.Exceptions; @@ -20,7 +18,7 @@ public class EngineNotAvailableException : Exception string engineName, string resolution, string message, - Func> action = null) : base(message) + Func>? action = null) : base(message) { EngineName = engineName; Resolution = resolution; @@ -40,6 +38,23 @@ public class EngineNotAvailableException : Exception EngineName = engineName; Resolution = resolution; } + + public EngineNotAvailableException( + string engineName, + string resolution, + string message, + string errorIconPath, + Func>? action = null) : base(message) + { + EngineName = engineName; + Resolution = resolution; + ErrorIcon = errorIconPath; + Action = action ?? (_ => + { + Clipboard.SetDataObject(this.ToString()); + return ValueTask.FromResult(true); + }); + } public override string ToString() { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj index 62cb599a1..29acf0eb7 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -45,7 +45,7 @@ - + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index 2ed421ca9..15456079a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -9,6 +9,7 @@ Are you sure you want to delete {0}? Are you sure you want to permanently delete this folder? Are you sure you want to permanently delete this file? + Are you sure you want to permanently delete this file/folder? Deletion successful Successfully deleted {0} Assigning the global action keyword could bring up too many results during search. Please choose a specific action keyword diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs index 2918cb61f..2174aeee6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs @@ -32,7 +32,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search internal const string DefaultContentSearchActionKeyword = "doc:"; - internal const char DirectorySeperator = '\\'; + internal const char DirectorySeparator = '\\'; internal const string WindowsIndexingOptions = "srchadmin.dll"; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index df27c3dfa..9fd495f49 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -15,7 +15,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo var criteria = ConstructSearchCriteria(search); if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > - search.LastIndexOf(Constants.DirectorySeperator)) + search.LastIndexOf(Constants.DirectorySeparator)) return DirectorySearch(new EnumerationOptions { RecurseSubdirectories = true @@ -29,9 +29,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { string incompleteName = ""; - if (!search.EndsWith(Constants.DirectorySeperator)) + if (!search.EndsWith(Constants.DirectorySeparator)) { - var indexOfSeparator = search.LastIndexOf(Constants.DirectorySeperator); + var indexOfSeparator = search.LastIndexOf(Constants.DirectorySeparator); incompleteName = search[(indexOfSeparator + 1)..].ToLower(); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs index 595ac3610..e526fb85a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs @@ -1,70 +1,75 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.IO; -using System.Text; +using System.Linq; +using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Plugin.Explorer.Search { public static class EnvironmentVariables { - internal static bool IsEnvironmentVariableSearch(string search) + private static Dictionary _envStringPaths = null; + private static Dictionary EnvStringPaths { - return search.StartsWith("%") - && search != "%%" - && !search.Contains("\\") && - LoadEnvironmentStringPaths().Count > 0; + get + { + if (_envStringPaths == null) + { + LoadEnvironmentStringPaths(); + } + return _envStringPaths; + } } - internal static Dictionary LoadEnvironmentStringPaths() + internal static bool IsEnvironmentVariableSearch(string search) { - var envStringPaths = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + return search.StartsWith("%") + && search != "%%" + && !search.Contains('\\') + && EnvStringPaths.Count > 0; + } + + public static bool HasEnvironmentVar(string search) + { + // "c:\foo %appdata%\" returns false + var splited = search.Split(Path.DirectorySeparatorChar); + return splited.Any(dir => dir.StartsWith('%') && + dir.EndsWith('%') && + dir.Length > 2 && + dir.Split('%').Length == 3); + } + + private static void LoadEnvironmentStringPaths() + { + _envStringPaths = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + var homedrive = Environment.GetEnvironmentVariable("HOMEDRIVE")?.EnsureTrailingSlash() ?? "C:\\"; foreach (DictionaryEntry special in Environment.GetEnvironmentVariables()) { var path = special.Value.ToString(); + // we add a trailing slash to the path to make sure drive paths become valid absolute paths. + // for example, if %systemdrive% is C: we turn it to C:\ + path = path.EnsureTrailingSlash(); + + // if we don't have an absolute path, we use Path.GetFullPath to get one. + // for example, if %homepath% is \Users\John we turn it to C:\Users\John + // Add basepath for GetFullPath() to parse %HOMEPATH% correctly + path = Path.IsPathFullyQualified(path) ? path : Path.GetFullPath(path, homedrive); + if (Directory.Exists(path)) { - // we add a trailing slash to the path to make sure drive paths become valid absolute paths. - // for example, if %systemdrive% is C: we turn it to C:\ - path = path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; - - // if we don't have an absolute path, we use Path.GetFullPath to get one. - // for example, if %homepath% is \Users\John we turn it to C:\Users\John - path = Path.IsPathFullyQualified(path) ? path : Path.GetFullPath(path); - // 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(), path); + // Call ToUpper() to make the results look consistent + _envStringPaths.Add(special.Key.ToString().ToUpper(), path); } } - - return envStringPaths; - } - - internal static string TranslateEnvironmentVariablePath(string environmentVariablePath) - { - var envStringPaths = LoadEnvironmentStringPaths(); - var splitSearch = environmentVariablePath.Substring(1).Split("%"); - var exactEnvStringPath = splitSearch[0]; - - // if there are more than 2 % characters in the query, don't bother - if (splitSearch.Length == 2 && envStringPaths.ContainsKey(exactEnvStringPath)) - { - var queryPartToReplace = $"%{exactEnvStringPath}%"; - var expandedPath = envStringPaths[exactEnvStringPath]; - // replace the %envstring% part of the query with its expanded equivalent - return environmentVariablePath.Replace(queryPartToReplace, expandedPath); - } - - return environmentVariablePath; } internal static List GetEnvironmentStringPathSuggestions(string querySearch, Query query, PluginInitContext context) { var results = new List(); - var environmentVariables = LoadEnvironmentStringPaths(); var search = querySearch; if (querySearch.EndsWith("%") && search.Length > 1) @@ -72,12 +77,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search // query starts and ends with a %, find an exact match from env-string paths search = querySearch.Substring(1, search.Length - 2); - if (environmentVariables.ContainsKey(search)) + if (EnvStringPaths.ContainsKey(search)) { - var expandedPath = environmentVariables[search]; - + var expandedPath = EnvStringPaths[search]; + results.Add(ResultManager.CreateFolderResult($"%{search}%", expandedPath, expandedPath, query)); - + return results; } } @@ -90,8 +95,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search { search = search.Substring(1); } - - foreach (var p in environmentVariables) + + foreach (var p in EnvStringPaths) { if (p.Key.StartsWith(search, StringComparison.InvariantCultureIgnoreCase)) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs index 5381d729d..3efd09c4d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs @@ -13,13 +13,12 @@ using Flow.Launcher.Plugin.Explorer.Exceptions; namespace Flow.Launcher.Plugin.Explorer.Search.Everything { - public static class EverythingApi { - private const int BufferSize = 4096; private static SemaphoreSlim _semaphore = new(1, 1); + // cached buffer to remove redundant allocations. private static readonly StringBuilder buffer = new(BufferSize); @@ -35,46 +34,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything InvalidCallError } - /// - /// Gets or sets a value indicating whether [match path]. - /// - /// true if [match path]; otherwise, false. - public static bool MatchPath - { - get => EverythingApiDllImport.Everything_GetMatchPath(); - set => EverythingApiDllImport.Everything_SetMatchPath(value); - } - - /// - /// Gets or sets a value indicating whether [match case]. - /// - /// true if [match case]; otherwise, false. - public static bool MatchCase - { - get => EverythingApiDllImport.Everything_GetMatchCase(); - set => EverythingApiDllImport.Everything_SetMatchCase(value); - } - - /// - /// Gets or sets a value indicating whether [match whole word]. - /// - /// true if [match whole word]; otherwise, false. - public static bool MatchWholeWord - { - get => EverythingApiDllImport.Everything_GetMatchWholeWord(); - set => EverythingApiDllImport.Everything_SetMatchWholeWord(value); - } - - /// - /// Gets or sets a value indicating whether [enable regex]. - /// - /// true if [enable regex]; otherwise, false. - public static bool EnableRegex - { - get => EverythingApiDllImport.Everything_GetRegex(); - set => EverythingApiDllImport.Everything_SetRegex(value); - } - /// /// Checks whether the sort option is Fast Sort. /// @@ -95,7 +54,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything try { - EverythingApiDllImport.Everything_GetMajorVersion(); + EverythingApiDllImport.Everything_GetMajorVersion(); var result = EverythingApiDllImport.Everything_GetLastError() != StateCode.IPCError; return result; } @@ -122,7 +81,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything await _semaphore.WaitAsync(token); - + try { if (token.IsCancellationRequested) @@ -152,6 +111,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything EverythingApiDllImport.Everything_SetMax(option.MaxCount); EverythingApiDllImport.Everything_SetSort(option.SortOption); + EverythingApiDllImport.Everything_SetMatchPath(option.IsFullPathSearch); if (token.IsCancellationRequested) yield break; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs index ffd22d9f5..4bb8a0cdd 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs @@ -27,20 +27,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything Enum.GetName(Settings.IndexSearchEngineOption.Everything)!, Main.Context.API.GetTranslation("flowlauncher_plugin_everything_click_to_launch_or_install"), Main.Context.API.GetTranslation("flowlauncher_plugin_everything_is_not_running"), - ClickToInstallEverythingAsync) - { - ErrorIcon = Constants.EverythingErrorImagePath - }; + Constants.EverythingErrorImagePath, + ClickToInstallEverythingAsync); } catch (DllNotFoundException) { throw new EngineNotAvailableException( Enum.GetName(Settings.IndexSearchEngineOption.Everything)!, "Please check whether your system is x86 or x64", - Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue")) - { - ErrorIcon = Constants.GeneralSearchErrorImagePath - }; + Constants.GeneralSearchErrorImagePath, + Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue")); } } private async ValueTask ClickToInstallEverythingAsync(ActionContext _) @@ -72,16 +68,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything if (!Settings.EnableEverythingContentSearch) { throw new EngineNotAvailableException(Enum.GetName(Settings.IndexSearchEngineOption.Everything)!, - "Click to Enable Everything Content Search (only applicable to Everything 1.5+ with indexed content)", - "Everything Content Search is not enabled.", + Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"), + Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"), + Constants.EverythingErrorImagePath, _ => { Settings.EnableEverythingContentSearch = true; return ValueTask.FromResult(true); - }) - { - ErrorIcon = Constants.EverythingErrorImagePath - }; + }); } if (token.IsCancellationRequested) yield break; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs index 6839822a4..0b1bbd0ec 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs @@ -3,12 +3,15 @@ using Flow.Launcher.Plugin.Everything.Everything; namespace Flow.Launcher.Plugin.Explorer.Search.Everything { - public record struct EverythingSearchOption(string Keyword, + public record struct EverythingSearchOption( + string Keyword, SortOption SortOption, - bool IsContentSearch = false, + bool IsContentSearch = false, string ContentSearchKeyword = default, string ParentPath = default, bool IsRecursive = true, - int Offset = 0, - int MaxCount = 100); + int Offset = 0, + int MaxCount = 100, + bool IsFullPathSearch = true + ); } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/AccessLink.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/AccessLink.cs index 0e4465b45..1975211f9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/AccessLink.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/AccessLink.cs @@ -15,7 +15,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks { get { - var path = Path.EndsWith(Constants.DirectorySeperator) ? Path[0..^1] : Path; + var path = Path.EndsWith(Constants.DirectorySeparator) ? Path[0..^1] : Path; if (path.EndsWith(':')) return path[0..^1] + " Drive"; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index 2676b27b2..ed4f39735 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -27,8 +27,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search var usePathSearchActionKeyword = Settings.PathSearchKeywordEnabled && !Settings.SearchActionKeywordEnabled; - var pathSearchActionKeyword = Settings.PathSearchActionKeyword == Query.GlobalPluginWildcardSign - ? string.Empty + var pathSearchActionKeyword = Settings.PathSearchActionKeyword == Query.GlobalPluginWildcardSign + ? string.Empty : $"{Settings.PathSearchActionKeyword} "; var searchActionKeyword = Settings.SearchActionKeyword == Query.GlobalPluginWildcardSign @@ -36,12 +36,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search : $"{Settings.SearchActionKeyword} "; var keyword = usePathSearchActionKeyword ? pathSearchActionKeyword : searchActionKeyword; - + var formatted_path = path; if (type == ResultType.Folder) - // the seperator is needed so when navigating the folder structure contents of the folder are listed - formatted_path = path.EndsWith(Constants.DirectorySeperator) ? path : path + Constants.DirectorySeperator; + // the separator is needed so when navigating the folder structure contents of the folder are listed + formatted_path = path.EndsWith(Constants.DirectorySeparator) ? path : path + Constants.DirectorySeparator; return $"{keyword}{formatted_path}"; } @@ -49,8 +49,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search public static string GetAutoCompleteText(string title, Query query, string path, ResultType resultType) { return !Settings.PathSearchKeywordEnabled && !Settings.SearchActionKeywordEnabled - ? $"{query.ActionKeyword} {title}" // Only Quick Access action keyword is used in this scenario - : GetPathWithActionKeyword(path, resultType, query.ActionKeyword); + ? $"{query.ActionKeyword} {title}" // Only Quick Access action keyword is used in this scenario + : GetPathWithActionKeyword(path, resultType, query.ActionKeyword); } public static Result CreateResult(Query query, SearchResult result) @@ -71,7 +71,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search { Title = title, IcoPath = path, - SubTitle = Path.GetDirectoryName(path), + SubTitle = subtitle, AutoCompleteText = GetAutoCompleteText(title, query, path, ResultType.Folder), TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData, CopyText = path, @@ -187,9 +187,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search internal static Result CreateOpenCurrentFolderResult(string path, string actionKeyword, bool windowsIndexed = false) { - // Path passed from PathSearchAsync ends with Constants.DirectorySeperator ('\'), need to remove the seperator + // Path passed from PathSearchAsync ends with Constants.DirectorySeparator ('\'), need to remove the separator // so it's consistent with folder results returned by index search which does not end with one - var folderPath = path.TrimEnd(Constants.DirectorySeperator); + var folderPath = path.TrimEnd(Constants.DirectorySeparator); return new Result { @@ -215,9 +215,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search internal static Result CreateFileResult(string filePath, Query query, int score = 0, bool windowsIndexed = false) { - Result.PreviewInfo preview = IsMedia(Path.GetExtension(filePath)) ? new Result.PreviewInfo { - IsMedia = true, - PreviewImagePath = filePath, + Result.PreviewInfo preview = IsMedia(Path.GetExtension(filePath)) ? new Result.PreviewInfo + { + IsMedia = true, PreviewImagePath = filePath, } : Result.PreviewInfo.Default; var title = Path.GetFileName(filePath); @@ -246,6 +246,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search { FileName = filePath, UseShellExecute = true, + WorkingDirectory = Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty, Verb = "runas", }); } @@ -286,8 +287,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search public static bool IsMedia(string extension) { if (string.IsNullOrEmpty(extension)) - { - return false; + { + return false; } else { @@ -295,7 +296,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search } } - public static readonly string[] MediaExtensions = { ".jpg", ".png", ".avi", ".mkv", ".bmp", ".gif", ".wmv", ".mp3", ".flac", ".mp4" }; + public static readonly string[] MediaExtensions = + { + ".jpg", ".png", ".avi", ".mkv", ".bmp", ".gif", ".wmv", ".mp3", ".flac", ".mp4" + }; } public enum ResultType diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 93a81f947..51c4c3d9d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -13,9 +13,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search { public class SearchManager { - internal static PluginInitContext Context; + internal PluginInitContext Context; - internal static Settings Settings; + internal Settings Settings; public SearchManager(Settings settings, PluginInitContext context) { @@ -23,19 +23,23 @@ namespace Flow.Launcher.Plugin.Explorer.Search Settings = settings; } - private class PathEqualityComparator : IEqualityComparer + /// + /// Note: A path that ends with "\" and one that doesn't will not be regarded as equal. + /// + public class PathEqualityComparator : IEqualityComparer { private static PathEqualityComparator instance; public static PathEqualityComparator Instance => instance ??= new PathEqualityComparator(); public bool Equals(Result x, Result y) { - return x.Title == y.Title && x.SubTitle == y.SubTitle; + return x.Title.Equals(y.Title, StringComparison.OrdinalIgnoreCase) + && string.Equals(x.SubTitle, y.SubTitle, StringComparison.OrdinalIgnoreCase); } public int GetHashCode(Result obj) { - return HashCode.Combine(obj.Title.GetHashCode(), obj.SubTitle?.GetHashCode() ?? 0); + return HashCode.Combine(obj.Title.ToLowerInvariant(), obj.SubTitle?.ToLowerInvariant() ?? ""); } } @@ -105,19 +109,21 @@ namespace Flow.Launcher.Plugin.Explorer.Search await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false)) results.Add(ResultManager.CreateResult(query, search)); } + catch (OperationCanceledException) + { + return new List(); + } + catch (EngineNotAvailableException) + { + throw; + } catch (Exception e) { - if (e is OperationCanceledException) - return results.ToList(); - - if (e is EngineNotAvailableException) - throw; - throw new SearchException(engineName, e.Message, e); } - + results.RemoveWhere(r => Settings.IndexSearchExcludedSubdirectoryPaths.Any( - excludedPath => r.SubTitle.StartsWith(excludedPath.Path, StringComparison.OrdinalIgnoreCase))); + excludedPath => FilesFolders.PathContains(excludedPath.Path, r.SubTitle))); return results.ToList(); } @@ -142,7 +148,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search }; } - private static List EverythingContentSearchResult(Query query) + private List EverythingContentSearchResult(Query query) { return new List() { @@ -167,18 +173,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search var results = new HashSet(PathEqualityComparator.Instance); - var isEnvironmentVariable = EnvironmentVariables.IsEnvironmentVariableSearch(querySearch); - - if (isEnvironmentVariable) + if (EnvironmentVariables.IsEnvironmentVariableSearch(querySearch)) return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, Context); - // Query is a location path with a full environment variable, eg. %appdata%\somefolder\ - var isEnvironmentVariablePath = querySearch[1..].Contains("%\\"); - - var locationPath = querySearch; - - if (isEnvironmentVariablePath) - locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath); + // Query is a location path with a full environment variable, eg. %appdata%\somefolder\, c:\users\%USERNAME%\downloads + var needToExpand = EnvironmentVariables.HasEnvironmentVar(querySearch); + var locationPath = needToExpand ? Environment.ExpandEnvironmentVariables(querySearch) : querySearch; // Check that actual location exists, otherwise directory search will throw directory not found exception if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).LocationExists()) @@ -234,7 +234,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search return results.ToList(); } - public static bool IsFileContentSearch(string actionKeyword) => actionKeyword == Settings.FileContentSearchActionKeyword; + public bool IsFileContentSearch(string actionKeyword) => actionKeyword == Settings.FileContentSearchActionKeyword; private bool UseWindowsIndexForDirectorySearch(string locationPath) @@ -245,10 +245,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory).StartsWith(x.Path, StringComparison.OrdinalIgnoreCase)) && WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory); } - + internal static bool IsEnvironmentVariableSearch(string search) { - return search.StartsWith("%") + return search.StartsWith("%") && search != "%%" && !search.Contains('\\'); } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs index abb50849d..c3a7d9e91 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs @@ -97,27 +97,25 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex private IAsyncEnumerable HandledEngineNotAvailableExceptionAsync() { - if (!SearchManager.Settings.WarnWindowsSearchServiceOff) + if (!Settings.WarnWindowsSearchServiceOff) return AsyncEnumerable.Empty(); - var api = SearchManager.Context.API; + var api = Main.Context.API; throw new EngineNotAvailableException( "Windows Index", api.GetTranslation("plugin_explorer_windowsSearchServiceFix"), api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"), + Constants.WindowsIndexErrorImagePath, c => { - SearchManager.Settings.WarnWindowsSearchServiceOff = false; + Settings.WarnWindowsSearchServiceOff = false; // Clears the warning message so user is not mistaken that it has not worked api.ChangeQuery(string.Empty); return ValueTask.FromResult(false); - }) - { - ErrorIcon = Constants.WindowsIndexErrorImagePath - }; + }); } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json index df2e556e1..0d94d60d9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json @@ -10,7 +10,7 @@ "Name": "Explorer", "Description": "Find and manage files and folders via Windows Search or Everything", "Author": "Jeremy Wu", - "Version": "2.1.0", + "Version": "2.2.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll", diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json index 084779ef9..64f5fce7a 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json @@ -1,10 +1,10 @@ { "ID": "6A122269676E40EB86EB543B945932B9", - "ActionKeyword": "*", + "ActionKeyword": "?", "Name": "Plugin Indicator", "Description": "Provides plugin action keyword suggestions", "Author": "qianlifeng", - "Version": "2.0.1", + "Version": "2.0.2", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginIndicator.dll", diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj index 2b773bee4..317ba4e89 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj @@ -38,6 +38,6 @@ - + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index 694cb5f8d..0d1ee5b16 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "2.0.0", + "Version": "2.0.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index 2f84fdbf5..8ba3ba800 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -378,15 +378,10 @@ namespace Flow.Launcher.Plugin.Program.Programs MatchResult matchResult; // We suppose Name won't be null - if (!Main._settings.EnableDescription || Description == null || Name.StartsWith(Description)) + if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || Name.Equals(Description)) { title = Name; - matchResult = StringMatcher.FuzzySearch(query, title); - } - else if (Description.StartsWith(Name)) - { - title = Description; - matchResult = StringMatcher.FuzzySearch(query, Description); + matchResult = StringMatcher.FuzzySearch(query, Name); } else { @@ -401,15 +396,19 @@ namespace Flow.Launcher.Plugin.Program.Programs } matchResult = descriptionMatch; } - else matchResult = nameMatch; + else + { + matchResult = nameMatch; + } } - if (!matchResult.Success) + if (!matchResult.IsSearchPrecisionScoreMet()) return null; var result = new Result { Title = title, + AutoCompleteText = Name, SubTitle = Main._settings.HideAppsPath ? string.Empty : Location, IcoPath = LogoPath, Preview = new Result.PreviewInfo diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index dec43530e..0798857ed 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -90,44 +90,28 @@ namespace Flow.Launcher.Plugin.Program.Programs bool useLocalizedName = !string.IsNullOrEmpty(LocalizedName) && !Name.Equals(LocalizedName); string resultName = useLocalizedName ? LocalizedName : Name; - if (!Main._settings.EnableDescription) + if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || resultName.Equals(Description)) { title = resultName; matchResult = StringMatcher.FuzzySearch(query, resultName); } else { - if (string.IsNullOrEmpty(Description) || resultName.StartsWith(Description)) + // Search in both + title = $"{resultName}: {Description}"; + var nameMatch = StringMatcher.FuzzySearch(query, resultName); + var descriptionMatch = StringMatcher.FuzzySearch(query, Description); + if (descriptionMatch.Score > nameMatch.Score) { - // Description is invalid or included in resultName - // Description is always localized, so Name.StartsWith(Description) is generally useless - title = resultName; - matchResult = StringMatcher.FuzzySearch(query, resultName); - } - else if (Description.StartsWith(resultName)) - { - // resultName included in Description - title = Description; - matchResult = StringMatcher.FuzzySearch(query, Description); + for (int i = 0; i < descriptionMatch.MatchData.Count; i++) + { + descriptionMatch.MatchData[i] += resultName.Length + 2; // 2 is ": " + } + matchResult = descriptionMatch; } else { - // Search in both - title = $"{resultName}: {Description}"; - var nameMatch = StringMatcher.FuzzySearch(query, resultName); - var descriptionMatch = StringMatcher.FuzzySearch(query, Description); - if (descriptionMatch.Score > nameMatch.Score) - { - for (int i = 0; i < descriptionMatch.MatchData.Count; i++) - { - descriptionMatch.MatchData[i] += resultName.Length + 2; // 2 is ": " - } - matchResult = descriptionMatch; - } - else - { - matchResult = nameMatch; - } + matchResult = nameMatch; } } @@ -171,6 +155,7 @@ namespace Flow.Launcher.Plugin.Program.Programs var result = new Result { Title = title, + AutoCompleteText = resultName, SubTitle = subtitle, IcoPath = IcoPath, Score = matchResult.Score, @@ -486,8 +471,8 @@ namespace Flow.Launcher.Plugin.Program.Programs } var paths = pathEnv.Split(";", StringSplitOptions.RemoveEmptyEntries).DistinctBy(p => p.ToLowerInvariant()); - - var toFilter = paths.Where(x => commonParents.All(parent => !IsSubPathOf(x, parent))) + + var toFilter = paths.Where(x => commonParents.All(parent => !FilesFolders.PathContains(parent, x))) .AsParallel() .SelectMany(p => EnumerateProgramsInDir(p, suffixes, recursive: false)); @@ -779,17 +764,6 @@ namespace Flow.Launcher.Plugin.Program.Programs } } - // https://stackoverflow.com/a/66877016 - private static bool IsSubPathOf(string subPath, string basePath) - { - var rel = Path.GetRelativePath(basePath, subPath); - return rel != "." - && rel != ".." - && !rel.StartsWith("../") - && !rel.StartsWith(@"..\") - && !Path.IsPathRooted(rel); - } - private static List GetCommonParents(IEnumerable programSources) { // To avoid unnecessary io @@ -801,8 +775,7 @@ namespace Flow.Launcher.Plugin.Program.Programs HashSet parents = group.ToHashSet(); foreach (var source in group) { - if (parents.Any(p => IsSubPathOf(source.Location, p.Location) && - source != p)) + if (parents.Any(p => FilesFolders.PathContains(p.Location, source.Location))) { parents.Remove(source); } diff --git a/Plugins/Flow.Launcher.Plugin.Program/plugin.json b/Plugins/Flow.Launcher.Plugin.Program/plugin.json index cd9119431..3d20c0a48 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Program/plugin.json @@ -4,7 +4,7 @@ "Name": "Program", "Description": "Search programs in Flow.Launcher", "Author": "qianlifeng", - "Version": "2.1.0", + "Version": "2.2.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Program.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Shell/plugin.json b/Plugins/Flow.Launcher.Plugin.Shell/plugin.json index 5885b10d7..2b80e5138 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Shell/plugin.json @@ -4,7 +4,7 @@ "Name": "Shell", "Description": "Provide executing commands from Flow Launcher", "Author": "qianlifeng", - "Version": "2.0.0", + "Version": "2.0.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Shell.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Sys/plugin.json b/Plugins/Flow.Launcher.Plugin.Sys/plugin.json index 0ce62c28c..74c9d9030 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Sys/plugin.json @@ -4,7 +4,7 @@ "Name": "System Commands", "Description": "Provide System related commands. e.g. shutdown,lock, setting etc.", "Author": "qianlifeng", - "Version": "2.0.0", + "Version": "2.0.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Sys.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Url/plugin.json b/Plugins/Flow.Launcher.Plugin.Url/plugin.json index aadaf5d70..48c184ec2 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Url/plugin.json @@ -4,7 +4,7 @@ "Name": "URL", "Description": "Open the typed URL from Flow Launcher", "Author": "qianlifeng", - "Version": "2.0.0", + "Version": "2.0.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Url.dll", diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json b/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json index ccd06b345..fb1e31c12 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json @@ -26,7 +26,7 @@ "Name": "Web Searches", "Description": "Provide the web search ability", "Author": "qianlifeng", - "Version": "2.0.1", + "Version": "2.0.2", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.WebSearch.dll", diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json b/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json index 0d8c7e19c..428a96923 100644 --- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json @@ -4,7 +4,7 @@ "Description": "Search settings inside Control Panel and Settings App", "Name": "Windows Settings", "Author": "TobiasSekan", - "Version": "3.0.1", + "Version": "3.0.2", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.WindowsSettings.dll", diff --git a/README.md b/README.md index cccbe401b..ef78e3cd0 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,8 @@ And you can download       +       +

### Mentions diff --git a/appveyor.yml b/appveyor.yml index ad8ac42b4..29fc4cf4b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: '1.11.0.{build}' +version: '1.12.1.{build}' init: - ps: |