mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #1817 from VictoriousRaptor/FixExplorer
Fix bugs in explorer plugin
This commit is contained in:
commit
db2b856a66
10 changed files with 265 additions and 122 deletions
|
|
@ -1,7 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
#pragma warning disable IDE0005
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
#pragma warning restore IDE0005
|
||||||
|
|
||||||
namespace Flow.Launcher.Plugin.SharedCommands
|
namespace Flow.Launcher.Plugin.SharedCommands
|
||||||
{
|
{
|
||||||
|
|
@ -206,22 +208,16 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
||||||
///</summary>
|
///</summary>
|
||||||
public static string GetPreviousExistingDirectory(Func<string, bool> locationExists, string path)
|
public static string GetPreviousExistingDirectory(Func<string, bool> locationExists, string path)
|
||||||
{
|
{
|
||||||
var previousDirectoryPath = "";
|
|
||||||
var index = path.LastIndexOf('\\');
|
var index = path.LastIndexOf('\\');
|
||||||
if (index > 0 && index < (path.Length - 1))
|
if (index > 0 && index < (path.Length - 1))
|
||||||
{
|
{
|
||||||
previousDirectoryPath = path.Substring(0, index + 1);
|
string previousDirectoryPath = path.Substring(0, index + 1);
|
||||||
if (!locationExists(previousDirectoryPath))
|
return locationExists(previousDirectoryPath) ? previousDirectoryPath : "";
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return previousDirectoryPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///<summary>
|
///<summary>
|
||||||
|
|
@ -241,5 +237,33 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns if <paramref name="parentPath"/> contains <paramref name="subPath"/>.
|
||||||
|
/// From https://stackoverflow.com/a/66877016
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parentPath">Parent path</param>
|
||||||
|
/// <param name="subPath">Sub path</param>
|
||||||
|
/// <param name="allowEqual">If <see langword="true"/>, when <paramref name="parentPath"/> and <paramref name="subPath"/> are equal, returns <see langword="true"/></param>
|
||||||
|
/// <returns></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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns path ended with "\"
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string EnsureTrailingSlash(this string path)
|
||||||
|
{
|
||||||
|
return path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
53
Flow.Launcher.Test/FilesFoldersTest.cs
Normal file
53
Flow.Launcher.Test/FilesFoldersTest.cs
Normal file
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,9 +7,11 @@ using Flow.Launcher.Plugin.SharedCommands;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using static Flow.Launcher.Plugin.Explorer.Search.SearchManager;
|
||||||
|
|
||||||
namespace Flow.Launcher.Test.Plugins
|
namespace Flow.Launcher.Test.Plugins
|
||||||
{
|
{
|
||||||
|
|
@ -176,7 +178,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var searchManager = new SearchManager(new Settings(), new PluginInitContext());
|
var searchManager = new SearchManager(new Settings(), new PluginInitContext());
|
||||||
|
|
||||||
// When
|
// When
|
||||||
var result = SearchManager.IsFileContentSearch(query.ActionKeyword);
|
var result = searchManager.IsFileContentSearch(query.ActionKeyword);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.IsTrue(result,
|
Assert.IsTrue(result,
|
||||||
|
|
@ -193,6 +195,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
[TestCase(@"c:\>*", true)]
|
[TestCase(@"c:\>*", true)]
|
||||||
[TestCase(@"c:\>", true)]
|
[TestCase(@"c:\>", true)]
|
||||||
[TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)]
|
[TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)]
|
||||||
|
[TestCase(@"c:\SomeLocation\SomeOtherLocation", true)]
|
||||||
public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult)
|
public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult)
|
||||||
{
|
{
|
||||||
// When, Given
|
// When, Given
|
||||||
|
|
@ -393,5 +396,68 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
// Then
|
// Then
|
||||||
Assert.AreEqual(result, expectedResult);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using Flow.Launcher.Plugin.Explorer.Search.IProvider;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace Flow.Launcher.Plugin.Explorer.Exceptions;
|
namespace Flow.Launcher.Plugin.Explorer.Exceptions;
|
||||||
|
|
||||||
|
|
@ -20,7 +18,7 @@ public class EngineNotAvailableException : Exception
|
||||||
string engineName,
|
string engineName,
|
||||||
string resolution,
|
string resolution,
|
||||||
string message,
|
string message,
|
||||||
Func<ActionContext, ValueTask<bool>> action = null) : base(message)
|
Func<ActionContext, ValueTask<bool>>? action = null) : base(message)
|
||||||
{
|
{
|
||||||
EngineName = engineName;
|
EngineName = engineName;
|
||||||
Resolution = resolution;
|
Resolution = resolution;
|
||||||
|
|
@ -40,6 +38,23 @@ public class EngineNotAvailableException : Exception
|
||||||
EngineName = engineName;
|
EngineName = engineName;
|
||||||
Resolution = resolution;
|
Resolution = resolution;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EngineNotAvailableException(
|
||||||
|
string engineName,
|
||||||
|
string resolution,
|
||||||
|
string message,
|
||||||
|
string errorIconPath,
|
||||||
|
Func<ActionContext, ValueTask<bool>>? action = null) : base(message)
|
||||||
|
{
|
||||||
|
EngineName = engineName;
|
||||||
|
Resolution = resolution;
|
||||||
|
ErrorIcon = errorIconPath;
|
||||||
|
Action = action ?? (_ =>
|
||||||
|
{
|
||||||
|
Clipboard.SetDataObject(this.ToString());
|
||||||
|
return ValueTask.FromResult(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,70 +1,75 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Linq;
|
||||||
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
|
|
||||||
namespace Flow.Launcher.Plugin.Explorer.Search
|
namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
{
|
{
|
||||||
public static class EnvironmentVariables
|
public static class EnvironmentVariables
|
||||||
{
|
{
|
||||||
internal static bool IsEnvironmentVariableSearch(string search)
|
private static Dictionary<string, string> _envStringPaths = null;
|
||||||
|
private static Dictionary<string, string> EnvStringPaths
|
||||||
{
|
{
|
||||||
return search.StartsWith("%")
|
get
|
||||||
&& search != "%%"
|
{
|
||||||
&& !search.Contains("\\") &&
|
if (_envStringPaths == null)
|
||||||
LoadEnvironmentStringPaths().Count > 0;
|
{
|
||||||
|
LoadEnvironmentStringPaths();
|
||||||
|
}
|
||||||
|
return _envStringPaths;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Dictionary<string, string> LoadEnvironmentStringPaths()
|
internal static bool IsEnvironmentVariableSearch(string search)
|
||||||
{
|
{
|
||||||
var envStringPaths = new Dictionary<string, string>(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<string, string>(StringComparer.InvariantCultureIgnoreCase);
|
||||||
|
var homedrive = Environment.GetEnvironmentVariable("HOMEDRIVE")?.EnsureTrailingSlash() ?? "C:\\";
|
||||||
|
|
||||||
foreach (DictionaryEntry special in Environment.GetEnvironmentVariables())
|
foreach (DictionaryEntry special in Environment.GetEnvironmentVariables())
|
||||||
{
|
{
|
||||||
var path = special.Value.ToString();
|
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))
|
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.
|
// Variables are returned with a mixture of all upper/lower case.
|
||||||
// Call ToLower() to make the results look consistent
|
// Call ToUpper() to make the results look consistent
|
||||||
envStringPaths.Add(special.Key.ToString().ToLower(), path);
|
_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<Result> GetEnvironmentStringPathSuggestions(string querySearch, Query query, PluginInitContext context)
|
internal static List<Result> GetEnvironmentStringPathSuggestions(string querySearch, Query query, PluginInitContext context)
|
||||||
{
|
{
|
||||||
var results = new List<Result>();
|
var results = new List<Result>();
|
||||||
|
|
||||||
var environmentVariables = LoadEnvironmentStringPaths();
|
|
||||||
var search = querySearch;
|
var search = querySearch;
|
||||||
|
|
||||||
if (querySearch.EndsWith("%") && search.Length > 1)
|
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
|
// query starts and ends with a %, find an exact match from env-string paths
|
||||||
search = querySearch.Substring(1, search.Length - 2);
|
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));
|
results.Add(ResultManager.CreateFolderResult($"%{search}%", expandedPath, expandedPath, query));
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -90,8 +95,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
{
|
{
|
||||||
search = search.Substring(1);
|
search = search.Substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var p in environmentVariables)
|
foreach (var p in EnvStringPaths)
|
||||||
{
|
{
|
||||||
if (p.Key.StartsWith(search, StringComparison.InvariantCultureIgnoreCase))
|
if (p.Key.StartsWith(search, StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -27,20 +27,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
||||||
Enum.GetName(Settings.IndexSearchEngineOption.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_click_to_launch_or_install"),
|
||||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_is_not_running"),
|
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_is_not_running"),
|
||||||
ClickToInstallEverythingAsync)
|
Constants.EverythingErrorImagePath,
|
||||||
{
|
ClickToInstallEverythingAsync);
|
||||||
ErrorIcon = Constants.EverythingErrorImagePath
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch (DllNotFoundException)
|
catch (DllNotFoundException)
|
||||||
{
|
{
|
||||||
throw new EngineNotAvailableException(
|
throw new EngineNotAvailableException(
|
||||||
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
||||||
"Please check whether your system is x86 or x64",
|
"Please check whether your system is x86 or x64",
|
||||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue"))
|
Constants.GeneralSearchErrorImagePath,
|
||||||
{
|
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue"));
|
||||||
ErrorIcon = Constants.GeneralSearchErrorImagePath
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private async ValueTask<bool> ClickToInstallEverythingAsync(ActionContext _)
|
private async ValueTask<bool> ClickToInstallEverythingAsync(ActionContext _)
|
||||||
|
|
@ -72,16 +68,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
||||||
if (!Settings.EnableEverythingContentSearch)
|
if (!Settings.EnableEverythingContentSearch)
|
||||||
{
|
{
|
||||||
throw new EngineNotAvailableException(Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
throw new EngineNotAvailableException(Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
||||||
"Click to Enable Everything Content Search (only applicable to Everything 1.5+ with indexed content)",
|
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"),
|
||||||
"Everything Content Search is not enabled.",
|
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"),
|
||||||
|
Constants.EverythingErrorImagePath,
|
||||||
_ =>
|
_ =>
|
||||||
{
|
{
|
||||||
Settings.EnableEverythingContentSearch = true;
|
Settings.EnableEverythingContentSearch = true;
|
||||||
return ValueTask.FromResult(true);
|
return ValueTask.FromResult(true);
|
||||||
})
|
});
|
||||||
{
|
|
||||||
ErrorIcon = Constants.EverythingErrorImagePath
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
IcoPath = path,
|
IcoPath = path,
|
||||||
SubTitle = Path.GetDirectoryName(path),
|
SubTitle = subtitle,
|
||||||
AutoCompleteText = GetAutoCompleteText(title, query, path, ResultType.Folder),
|
AutoCompleteText = GetAutoCompleteText(title, query, path, ResultType.Folder),
|
||||||
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData,
|
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData,
|
||||||
CopyText = path,
|
CopyText = path,
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
{
|
{
|
||||||
public class SearchManager
|
public class SearchManager
|
||||||
{
|
{
|
||||||
internal static PluginInitContext Context;
|
internal PluginInitContext Context;
|
||||||
|
|
||||||
internal static Settings Settings;
|
internal Settings Settings;
|
||||||
|
|
||||||
public SearchManager(Settings settings, PluginInitContext context)
|
public SearchManager(Settings settings, PluginInitContext context)
|
||||||
{
|
{
|
||||||
|
|
@ -23,19 +23,23 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
Settings = settings;
|
Settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PathEqualityComparator : IEqualityComparer<Result>
|
/// <summary>
|
||||||
|
/// Note: A path that ends with "\" and one that doesn't will not be regarded as equal.
|
||||||
|
/// </summary>
|
||||||
|
public class PathEqualityComparator : IEqualityComparer<Result>
|
||||||
{
|
{
|
||||||
private static PathEqualityComparator instance;
|
private static PathEqualityComparator instance;
|
||||||
public static PathEqualityComparator Instance => instance ??= new PathEqualityComparator();
|
public static PathEqualityComparator Instance => instance ??= new PathEqualityComparator();
|
||||||
|
|
||||||
public bool Equals(Result x, Result y)
|
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)
|
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))
|
await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false))
|
||||||
results.Add(ResultManager.CreateResult(query, search));
|
results.Add(ResultManager.CreateResult(query, search));
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
return new List<Result>();
|
||||||
|
}
|
||||||
|
catch (EngineNotAvailableException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
if (e is OperationCanceledException)
|
|
||||||
return results.ToList();
|
|
||||||
|
|
||||||
if (e is EngineNotAvailableException)
|
|
||||||
throw;
|
|
||||||
|
|
||||||
throw new SearchException(engineName, e.Message, e);
|
throw new SearchException(engineName, e.Message, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
results.RemoveWhere(r => Settings.IndexSearchExcludedSubdirectoryPaths.Any(
|
results.RemoveWhere(r => Settings.IndexSearchExcludedSubdirectoryPaths.Any(
|
||||||
excludedPath => r.SubTitle.StartsWith(excludedPath.Path, StringComparison.OrdinalIgnoreCase)));
|
excludedPath => FilesFolders.PathContains(excludedPath.Path, r.SubTitle)));
|
||||||
|
|
||||||
return results.ToList();
|
return results.ToList();
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +148,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Result> EverythingContentSearchResult(Query query)
|
private List<Result> EverythingContentSearchResult(Query query)
|
||||||
{
|
{
|
||||||
return new List<Result>()
|
return new List<Result>()
|
||||||
{
|
{
|
||||||
|
|
@ -167,18 +173,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
|
|
||||||
var results = new HashSet<Result>(PathEqualityComparator.Instance);
|
var results = new HashSet<Result>(PathEqualityComparator.Instance);
|
||||||
|
|
||||||
var isEnvironmentVariable = EnvironmentVariables.IsEnvironmentVariableSearch(querySearch);
|
if (EnvironmentVariables.IsEnvironmentVariableSearch(querySearch))
|
||||||
|
|
||||||
if (isEnvironmentVariable)
|
|
||||||
return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, Context);
|
return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, Context);
|
||||||
|
|
||||||
// Query is a location path with a full environment variable, eg. %appdata%\somefolder\
|
// Query is a location path with a full environment variable, eg. %appdata%\somefolder\, c:\users\%USERNAME%\downloads
|
||||||
var isEnvironmentVariablePath = querySearch[1..].Contains("%\\");
|
var needToExpand = EnvironmentVariables.HasEnvironmentVar(querySearch);
|
||||||
|
var locationPath = needToExpand ? Environment.ExpandEnvironmentVariables(querySearch) : querySearch;
|
||||||
var locationPath = querySearch;
|
|
||||||
|
|
||||||
if (isEnvironmentVariablePath)
|
|
||||||
locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath);
|
|
||||||
|
|
||||||
// Check that actual location exists, otherwise directory search will throw directory not found exception
|
// Check that actual location exists, otherwise directory search will throw directory not found exception
|
||||||
if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).LocationExists())
|
if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).LocationExists())
|
||||||
|
|
@ -234,7 +234,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
return results.ToList();
|
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)
|
private bool UseWindowsIndexForDirectorySearch(string locationPath)
|
||||||
|
|
@ -245,10 +245,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory).StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))
|
x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory).StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))
|
||||||
&& WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory);
|
&& WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool IsEnvironmentVariableSearch(string search)
|
internal static bool IsEnvironmentVariableSearch(string search)
|
||||||
{
|
{
|
||||||
return search.StartsWith("%")
|
return search.StartsWith("%")
|
||||||
&& search != "%%"
|
&& search != "%%"
|
||||||
&& !search.Contains('\\');
|
&& !search.Contains('\\');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,27 +97,25 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
||||||
|
|
||||||
private IAsyncEnumerable<SearchResult> HandledEngineNotAvailableExceptionAsync()
|
private IAsyncEnumerable<SearchResult> HandledEngineNotAvailableExceptionAsync()
|
||||||
{
|
{
|
||||||
if (!SearchManager.Settings.WarnWindowsSearchServiceOff)
|
if (!Settings.WarnWindowsSearchServiceOff)
|
||||||
return AsyncEnumerable.Empty<SearchResult>();
|
return AsyncEnumerable.Empty<SearchResult>();
|
||||||
|
|
||||||
var api = SearchManager.Context.API;
|
var api = Main.Context.API;
|
||||||
|
|
||||||
throw new EngineNotAvailableException(
|
throw new EngineNotAvailableException(
|
||||||
"Windows Index",
|
"Windows Index",
|
||||||
api.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
|
api.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
|
||||||
api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
|
api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
|
||||||
|
Constants.WindowsIndexErrorImagePath,
|
||||||
c =>
|
c =>
|
||||||
{
|
{
|
||||||
SearchManager.Settings.WarnWindowsSearchServiceOff = false;
|
Settings.WarnWindowsSearchServiceOff = false;
|
||||||
|
|
||||||
// Clears the warning message so user is not mistaken that it has not worked
|
// Clears the warning message so user is not mistaken that it has not worked
|
||||||
api.ChangeQuery(string.Empty);
|
api.ChangeQuery(string.Empty);
|
||||||
|
|
||||||
return ValueTask.FromResult(false);
|
return ValueTask.FromResult(false);
|
||||||
})
|
});
|
||||||
{
|
|
||||||
ErrorIcon = Constants.WindowsIndexErrorImagePath
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -470,8 +470,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
||||||
}
|
}
|
||||||
|
|
||||||
var paths = pathEnv.Split(";", StringSplitOptions.RemoveEmptyEntries).DistinctBy(p => p.ToLowerInvariant());
|
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()
|
.AsParallel()
|
||||||
.SelectMany(p => EnumerateProgramsInDir(p, suffixes, recursive: false));
|
.SelectMany(p => EnumerateProgramsInDir(p, suffixes, recursive: false));
|
||||||
|
|
||||||
|
|
@ -763,17 +763,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<string> GetCommonParents(IEnumerable<ProgramSource> programSources)
|
private static List<string> GetCommonParents(IEnumerable<ProgramSource> programSources)
|
||||||
{
|
{
|
||||||
// To avoid unnecessary io
|
// To avoid unnecessary io
|
||||||
|
|
@ -785,8 +774,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
||||||
HashSet<ProgramSource> parents = group.ToHashSet();
|
HashSet<ProgramSource> parents = group.ToHashSet();
|
||||||
foreach (var source in group)
|
foreach (var source in group)
|
||||||
{
|
{
|
||||||
if (parents.Any(p => IsSubPathOf(source.Location, p.Location) &&
|
if (parents.Any(p => FilesFolders.PathContains(p.Location, source.Location)))
|
||||||
source != p))
|
|
||||||
{
|
{
|
||||||
parents.Remove(source);
|
parents.Remove(source);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue