diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index 81b9aba97e7..fe2b8a73553 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -108,3 +108,4 @@ Msix
dummyprofile
browserbookmark
copyurl
+PInvoke
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/x64/Everything3.dll b/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/x64/Everything3.dll
new file mode 100644
index 00000000000..be6a6f1409d
Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/x64/Everything3.dll differ
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/x86/Everything3.dll b/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/x86/Everything3.dll
new file mode 100644
index 00000000000..3d2659ada52
Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.Explorer/EverythingSDK/x86/Everything3.dll differ
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 a837a49b49b..93fc6f954e8 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
@@ -32,6 +32,12 @@
PreserveNewest
+
+ PreserveNewest
+
+
+ PreserveNewest
+
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml
index 1eae757083c..af9d61177ea 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml
@@ -1,4 +1,4 @@
-
@@ -44,6 +44,7 @@
Date and time format
Sort Option:
Everything Path:
+ Everything 1.5 instance name (requires app restart):
Launch Hidden
Editor Path
Shell Path
@@ -151,7 +152,11 @@
Failed to load Everything SDK
+ Please check whether your system is x86 or x64
Warning: Everything service is not running
+ Warning: Everything 1.5 is unavailable
+ Ensure Everything is running and verify the configured instance name
+ Warning: Everything 1.5 is unavailable. The service may not be running, or the instance name may be invalid
Error while querying Everything
Sort By
Name ↑
@@ -186,6 +191,7 @@
Search Full Path
Enable File/Folder Run Count
+ Use Everything 1.5 (requires app restart)
Click to launch or install Everything
Everything Installation
@@ -214,3 +220,4 @@
1 year ago
{0} years ago
+
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs
index f5b8b932581..f23f897ee2f 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs
@@ -46,8 +46,9 @@ public Task InitAsync(PluginInitContext context)
searchManager = new SearchManager(Settings, Context);
ResultManager.Init(Context, Settings);
- EverythingApiDllImport.Load(Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "EverythingSDK",
- Environment.Is64BitProcess ? "x64" : "x86"));
+ var sdkDirectory = Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "EverythingSDK",
+ Environment.Is64BitProcess ? "x64" : "x86");
+ Settings.EverythingManagerInstance.InitializeApi(sdkDirectory);
return Task.CompletedTask;
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Everything3ApiDllImport.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Everything3ApiDllImport.cs
new file mode 100644
index 00000000000..5e24d7298a4
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Everything3ApiDllImport.cs
@@ -0,0 +1,137 @@
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Flow.Launcher.Plugin.Explorer.Search.Everything
+{
+ internal static class Everything3ApiDllImport
+ {
+ private static IntPtr _dllHandle = IntPtr.Zero;
+ internal static bool IsLoaded => _dllHandle != IntPtr.Zero;
+
+ public static void Load(string directory)
+ {
+ if (_dllHandle != IntPtr.Zero)
+ {
+ return;
+ }
+
+ var path = Path.Combine(directory, Dll);
+ _dllHandle = LoadLibrary(path);
+ if (_dllHandle == IntPtr.Zero)
+ {
+ throw new Win32Exception(Marshal.GetLastPInvokeError());
+ }
+ }
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ private static extern IntPtr LoadLibrary(string name);
+
+ private const string Dll = "Everything3.dll";
+
+ [DllImport(Dll, CharSet = CharSet.Unicode)]
+ internal static extern IntPtr Everything3_ConnectW(string instanceName);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_DestroyClient(IntPtr client);
+
+ [DllImport(Dll)]
+ internal static extern IntPtr Everything3_CreateSearchState();
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_DestroySearchState(IntPtr searchState);
+
+ [DllImport(Dll, CharSet = CharSet.Unicode)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_SetSearchTextW(IntPtr searchState, string search);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_SetSearchRegex(IntPtr searchState, bool matchRegex);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_SetSearchMatchPath(IntPtr searchState, bool matchPath);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_SetSearchHideResultOmissions(IntPtr searchState, bool hideResultOmissions);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_SetSearchViewportOffset(IntPtr searchState, nuint offset);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_SetSearchViewportCount(IntPtr searchState, nuint count);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_ClearSearchSorts(IntPtr searchState);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_AddSearchSort(IntPtr searchState, uint propertyId, bool ascending);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_ClearSearchPropertyRequests(IntPtr searchState);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_AddSearchPropertyRequest(IntPtr searchState, uint propertyId);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_AddSearchPropertyRequestHighlighted(IntPtr searchState, uint propertyId);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_GetSearchPropertyRequestHighlight(IntPtr searchState, nuint index);
+
+ [DllImport(Dll)]
+ internal static extern IntPtr Everything3_Search(IntPtr client, IntPtr searchState);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_DestroyResultList(IntPtr resultList);
+
+ [DllImport(Dll)]
+ internal static extern nuint Everything3_GetResultListViewportCount(IntPtr resultList);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_IsFolderResult(IntPtr resultList, nuint resultIndex);
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_IsRootResult(IntPtr resultList, nuint resultIndex);
+
+ [DllImport(Dll, CharSet = CharSet.Unicode)]
+ internal static extern nuint Everything3_GetResultFullPathNameW(IntPtr resultList, nuint resultIndex, StringBuilder buffer, nuint bufferSizeInWChars);
+
+ [DllImport(Dll, CharSet = CharSet.Unicode)]
+ internal static extern nuint Everything3_GetResultPropertyTextHighlightedW(IntPtr resultList, nuint resultIndex, uint propertyId, StringBuilder buffer, nuint bufferSizeInWChars);
+
+ [DllImport(Dll, CharSet = CharSet.Unicode)]
+ internal static extern nuint Everything3_GetResultPropertyTextW(IntPtr resultList, nuint resultIndex, uint propertyId, StringBuilder buffer, nuint bufferSizeInWChars);
+
+ [DllImport(Dll)]
+ internal static extern uint Everything3_GetResultRunCount(IntPtr resultList, nuint resultIndex);
+
+ [DllImport(Dll, CharSet = CharSet.Unicode)]
+ internal static extern uint Everything3_IncRunCountFromFilenameW(IntPtr client, string fileName);
+
+ [DllImport(Dll)]
+ internal static extern uint Everything3_GetLastError();
+
+
+ [DllImport(Dll)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool Everything3_IsPropertyFastSort(IntPtr client, uint propertyId);
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs
index a786284699b..7d3030c058b 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs
@@ -1,52 +1,36 @@
-using Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions;
-using System;
+using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions;
namespace Flow.Launcher.Plugin.Explorer.Search.Everything
{
- public static class EverythingApi
+ public class LegacyEverythingApi : IEverythingApi
{
private const int BufferSize = 4096;
-
private static readonly SemaphoreSlim _semaphore = new(1, 1);
-
// cached buffer to remove redundant allocations.
private static readonly StringBuilder buffer = new(BufferSize);
- public enum StateCode
- {
- OK,
- MemoryError,
- IPCError,
- RegisterClassExError,
- CreateWindowError,
- CreateThreadError,
- InvalidIndexError,
- InvalidCallError
- }
-
const uint EVERYTHING_REQUEST_FULL_PATH_AND_FILE_NAME = 0x00000004u;
const uint EVERYTHING_REQUEST_RUN_COUNT = 0x00000400u;
///
/// Checks whether the sort option is Fast Sort.
///
- public static bool IsFastSortOption(EverythingSortOption sortOption)
+ public bool IsFastSortOption(EverythingSortOption sortOption)
{
var fastSortOptionEnabled = EverythingApiDllImport.Everything_IsFastSort(sortOption);
-
- // If the Everything service is not running, then this call will incorrectly report
+ // If the Everything service is not running, then this call will incorrectly report
// the state as false. This checks for errors thrown by the api and up to the caller to handle.
CheckAndThrowExceptionOnError();
-
return fastSortOptionEnabled;
}
- public static async ValueTask IsEverythingRunningAsync(CancellationToken token = default)
+ public async ValueTask IsEverythingRunningAsync(CancellationToken token = default)
{
try
{
@@ -60,8 +44,7 @@ public static async ValueTask IsEverythingRunningAsync(CancellationToken t
try
{
_ = EverythingApiDllImport.Everything_GetMajorVersion();
- var result = EverythingApiDllImport.Everything_GetLastError() != StateCode.IPCError;
- return result;
+ return EverythingApiDllImport.Everything_GetLastError() != EverythingStateCode.IPCError;
}
finally
{
@@ -70,19 +53,15 @@ public static async ValueTask IsEverythingRunningAsync(CancellationToken t
}
///
- /// Searches the specified key word and reset the everything API afterwards
+ /// Searches using the specified criteria and resets the Everything API afterwards.
///
- /// Search Criteria
- /// when cancelled the current search will stop and exit (and would not reset)
- /// An IAsyncEnumerable that will enumerate all results searched by the specific query and option
- public static async IAsyncEnumerable SearchAsync(EverythingSearchOption option,
- [EnumeratorCancellation] CancellationToken token)
+ /// The search criteria.
+ /// A cancellation token that stops the current search when cancellation is requested.
+ /// An asynchronous sequence of search results that match the specified criteria.
+ public async IAsyncEnumerable SearchAsync(EverythingSearchOption option, [EnumeratorCancellation] CancellationToken token = default)
{
- if (option.Offset < 0)
- throw new ArgumentOutOfRangeException(nameof(option.Offset), option.Offset, "Offset must be greater than or equal to 0");
-
- if (option.MaxCount < 0)
- throw new ArgumentOutOfRangeException(nameof(option.MaxCount), option.MaxCount, "MaxCount must be greater than or equal to 0");
+ var query = EverythingHelper.PrepareQuery(option);
+ var preparedOption = query.Option;
try
{
@@ -98,33 +77,15 @@ public static async IAsyncEnumerable SearchAsync(EverythingSearchO
if (token.IsCancellationRequested)
yield break;
- if (option.Keyword.StartsWith("@"))
- {
- EverythingApiDllImport.Everything_SetRegex(true);
- option.Keyword = option.Keyword[1..];
- }
+ EverythingApiDllImport.Everything_SetRegex(preparedOption.UseRegex);
+ EverythingApiDllImport.Everything_SetSearchW(query.SearchText);
+ EverythingApiDllImport.Everything_SetOffset(preparedOption.Offset);
+ EverythingApiDllImport.Everything_SetMax(preparedOption.MaxCount);
- var builder = new StringBuilder();
- builder.Append(option.Keyword);
+ EverythingApiDllImport.Everything_SetSort(preparedOption.SortOption);
+ EverythingApiDllImport.Everything_SetMatchPath(preparedOption.IsFullPathSearch);
- if (!string.IsNullOrWhiteSpace(option.ParentPath))
- {
- builder.Append($" {(option.IsRecursive ? "" : "parent:")}\"{option.ParentPath}\"");
- }
-
- if (option.IsContentSearch)
- {
- builder.Append($" content:\"{option.ContentSearchKeyword}\"");
- }
-
- EverythingApiDllImport.Everything_SetSearchW(builder.ToString());
- EverythingApiDllImport.Everything_SetOffset(option.Offset);
- EverythingApiDllImport.Everything_SetMax(option.MaxCount);
-
- EverythingApiDllImport.Everything_SetSort(option.SortOption);
- EverythingApiDllImport.Everything_SetMatchPath(option.IsFullPathSearch);
-
- if (option.SortOption == EverythingSortOption.RUN_COUNT_DESCENDING)
+ if (preparedOption.SortOption == EverythingSortOption.RUN_COUNT_DESCENDING)
{
EverythingApiDllImport.Everything_SetRequestFlags(EVERYTHING_REQUEST_FULL_PATH_AND_FILE_NAME | EVERYTHING_REQUEST_RUN_COUNT);
}
@@ -152,13 +113,12 @@ public static async IAsyncEnumerable SearchAsync(EverythingSearchO
var result = new SearchResult
{
- // todo the types are wrong. Everything expects uint everywhere, but we send int just above/below. how to fix? Is EverythingApiDllImport autogenerated or handmade?
FullPath = buffer.ToString(),
Type = EverythingApiDllImport.Everything_IsFolderResult(idx) ? ResultType.Folder :
EverythingApiDllImport.Everything_IsFileResult(idx) ? ResultType.File :
ResultType.Volume,
Score = Convert.ToInt32(EverythingApiDllImport.Everything_GetResultRunCount((uint)idx)),
- HighlightData = EverythingHighlightStringToHighlightList(EverythingApiDllImport.Everything_GetResultHighlightedFileName((uint)idx))
+ HighlightData = EverythingHelper.EverythingHighlightStringToHighlightList(EverythingApiDllImport.Everything_GetResultHighlightedFileName((uint)idx))
};
yield return result;
@@ -175,30 +135,35 @@ private static void CheckAndThrowExceptionOnError()
{
switch (EverythingApiDllImport.Everything_GetLastError())
{
- case StateCode.CreateThreadError:
+ case EverythingStateCode.CreateThreadError:
throw new CreateThreadException();
- case StateCode.CreateWindowError:
+ case EverythingStateCode.CreateWindowError:
throw new CreateWindowException();
- case StateCode.InvalidCallError:
+ case EverythingStateCode.InvalidCallError:
throw new InvalidCallException();
- case StateCode.InvalidIndexError:
+ case EverythingStateCode.InvalidIndexError:
throw new InvalidIndexException();
- case StateCode.IPCError:
+ case EverythingStateCode.IPCError:
throw new IPCErrorException();
- case StateCode.MemoryError:
+ case EverythingStateCode.MemoryError:
throw new MemoryErrorException();
- case StateCode.RegisterClassExError:
+ case EverythingStateCode.RegisterClassExError:
throw new RegisterClassExException();
- case StateCode.OK:
+ case EverythingStateCode.OK:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
- public static async Task IncrementRunCounterAsync(string fileOrFolder)
+ public async Task IncrementRunCounterAsync(string fileOrFolder)
{
- await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
+ var _entered = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
+ if (!_entered)
+ {
+ // If we can't acquire the semaphore within the timeout, we skip incrementing the run count to avoid blocking.
+ return;
+ }
try
{
_ = EverythingApiDllImport.Everything_IncRunCountFromFileName(fileOrFolder);
@@ -207,57 +172,10 @@ public static async Task IncrementRunCounterAsync(string fileOrFolder)
{
/*ignored*/
}
- finally { _semaphore.Release(); }
- }
-
- ///
- /// Convert the highlighted string from Everything API to a list of highlight indexes for our Result.
- ///
- /// Text inside a * quote is highlighted, two consecutive *'s is a single literal *. For example, in the highlighted text: abc*123* the 123 part is highlighted.
- /// A list of zero-based character indices that should be highlighted.
- public static List EverythingHighlightStringToHighlightList(string highlightString)
- {
- var highlightData = new List();
-
- if (string.IsNullOrEmpty(highlightString))
- return highlightData;
-
- var isHighlighted = false;
- var actualIndex = 0; // Index in the actual string (without * markers)
- var length = highlightString.Length;
-
- for (var i = 0; i < length; i++)
- {
- if (highlightString[i] == '*')
- {
- // Check if it's a literal * (two consecutive *)
- if (i + 1 < length && highlightString[i + 1] == '*')
- {
- // Two consecutive *'s represent a single literal *
- if (isHighlighted)
- {
- highlightData.Add(actualIndex);
- }
- actualIndex++;
- i++; // Skip the next *
- }
- else
- {
- isHighlighted = !isHighlighted;
- }
- }
- else
- {
- // Regular character
- if (isHighlighted)
- {
- highlightData.Add(actualIndex);
- }
- actualIndex++;
- }
+ finally
+ {
+ _semaphore.Release();
}
-
- return highlightData;
}
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs
index 344c1a42c16..9f2a5b739bc 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiDllImport.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
@@ -7,19 +8,26 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
{
public static class EverythingApiDllImport
{
+ private static IntPtr _dllHandle = IntPtr.Zero;
+ public static bool IsLoaded => _dllHandle != IntPtr.Zero;
+
public static void Load(string directory)
{
+ if (_dllHandle != IntPtr.Zero)
+ {
+ return;
+ }
+
var path = Path.Combine(directory, DLL);
- int code = LoadLibrary(path);
- if (code == 0)
+ _dllHandle = LoadLibrary(path);
+ if (_dllHandle == IntPtr.Zero)
{
- int err = Marshal.GetLastPInvokeError();
- Marshal.ThrowExceptionForHR(err);
+ throw new Win32Exception(Marshal.GetLastPInvokeError());
}
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
- private static extern int LoadLibrary(string name);
+ private static extern IntPtr LoadLibrary(string name);
private const string DLL = "Everything.dll";
@@ -66,7 +74,7 @@ public static void Load(string directory)
internal static extern string Everything_GetSearchW();
[DllImport(DLL)]
- internal static extern EverythingApi.StateCode Everything_GetLastError();
+ internal static extern EverythingStateCode Everything_GetLastError();
[DllImport(DLL, CharSet = CharSet.Unicode)]
internal static extern bool Everything_QueryW(bool bWait);
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiFactory.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiFactory.cs
new file mode 100644
index 00000000000..609222dd097
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiFactory.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Flow.Launcher.Plugin.Explorer.Search.Everything
+{
+ internal static class EverythingApiFactory
+ {
+ internal const string DefaultEverything15InstanceName = "1.5a";
+
+ public static IEverythingApi Create(Settings settings)
+ {
+ ArgumentNullException.ThrowIfNull(settings);
+
+ return settings.EnableEverything15Support
+ ? new EverythingApiV3(GetNormalizedInstanceName(settings.Everything15InstanceName))
+ : new LegacyEverythingApi();
+ }
+
+ internal static string GetNormalizedInstanceName(string instanceName) => string.IsNullOrWhiteSpace(instanceName)
+ ? DefaultEverything15InstanceName
+ : instanceName.Trim();
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiV3.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiV3.cs
new file mode 100644
index 00000000000..b7c1e932e71
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingApiV3.cs
@@ -0,0 +1,457 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions;
+
+namespace Flow.Launcher.Plugin.Explorer.Search.Everything
+{
+ public class EverythingApiV3 : IEverythingApi
+ {
+ private const int BufferSize = 4096;
+ private static readonly SemaphoreSlim _semaphore = new(1, 1);
+ private static readonly StringBuilder _buffer = new(BufferSize);
+
+ private readonly string _instanceName;
+
+ public EverythingApiV3(string instanceName)
+ {
+ _instanceName = instanceName;
+ }
+
+ const uint EVERYTHING3_PROPERTY_ID_NAME = 0;
+ const uint EVERYTHING3_PROPERTY_ID_PATH = 1;
+ const uint EVERYTHING3_PROPERTY_ID_SIZE = 2;
+ const uint EVERYTHING3_PROPERTY_ID_EXTENSION = 3;
+ const uint EVERYTHING3_PROPERTY_ID_TYPE = 4;
+ const uint EVERYTHING3_PROPERTY_ID_DATE_MODIFIED = 5;
+ const uint EVERYTHING3_PROPERTY_ID_DATE_CREATED = 6;
+ const uint EVERYTHING3_PROPERTY_ID_DATE_ACCESSED = 7;
+ const uint EVERYTHING3_PROPERTY_ID_ATTRIBUTES = 8;
+ const uint EVERYTHING3_PROPERTY_ID_DATE_RECENTLY_CHANGED = 9;
+ const uint EVERYTHING3_PROPERTY_ID_RUN_COUNT = 10;
+ const uint EVERYTHING3_PROPERTY_ID_DATE_RUN = 11;
+ const uint EVERYTHING3_PROPERTY_ID_FILE_LIST_NAME = 12;
+ const uint EVERYTHING3_PROPERTY_ID_PATH_AND_NAME = 240;
+
+ const uint EVERYTHING3_ERROR_OUT_OF_MEMORY = 0xE0000001;
+ const uint EVERYTHING3_ERROR_IPC_PIPE_NOT_FOUND = 0xE0000002;
+ const uint EVERYTHING3_ERROR_DISCONNECTED = 0xE0000003;
+ const uint EVERYTHING3_ERROR_INVALID_PARAMETER = 0xE0000004;
+ const uint EVERYTHING3_ERROR_PROPERTY_NOT_FOUND = 0xE0000007;
+
+ public async ValueTask IsEverythingRunningAsync(CancellationToken token = default)
+ {
+ try
+ {
+ await _semaphore.WaitAsync(token);
+ }
+ catch (OperationCanceledException)
+ {
+ return false;
+ }
+
+ try
+ {
+ if (!TryConnectEverything3(out var client))
+ return false;
+
+ _ = Everything3ApiDllImport.Everything3_DestroyClient(client);
+ return true;
+ }
+ finally
+ {
+ _semaphore.Release();
+ }
+ }
+
+ public async IAsyncEnumerable SearchAsync(EverythingSearchOption option, [EnumeratorCancellation] CancellationToken token = default)
+ {
+ var query = EverythingHelper.PrepareQuery(option);
+ var preparedOption = query.Option;
+
+ try
+ {
+ await _semaphore.WaitAsync(token);
+ }
+ catch (OperationCanceledException)
+ {
+ yield break;
+ }
+
+ try
+ {
+ if (token.IsCancellationRequested)
+ yield break;
+
+ if (!TryConnectEverything3(out var client))
+ throw new IPCErrorException();
+
+ await foreach (var result in SearchWithEverything3Async(client, preparedOption, query, token))
+ yield return result;
+ }
+ finally
+ {
+ _semaphore.Release();
+ }
+ }
+
+ public async Task IncrementRunCounterAsync(string fileOrFolder)
+ {
+ var _entered = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
+ if (!_entered)
+ {
+ // If we can't acquire the semaphore within the timeout, we skip incrementing the run count to avoid blocking.
+ return;
+ }
+ try
+ {
+ if (TryConnectEverything3(out var client))
+ {
+ try
+ {
+ Everything3ApiDllImport.Everything3_IncRunCountFromFilenameW(client, fileOrFolder);
+ }
+ finally
+ {
+ _ = Everything3ApiDllImport.Everything3_DestroyClient(client);
+ }
+ }
+ }
+ catch (Exception)
+ {
+ /*ignored*/
+ }
+ finally
+ {
+ _semaphore.Release();
+ }
+ }
+
+ public bool IsFastSortOption(EverythingSortOption sortOption)
+ {
+ if (!TryConnectEverything3(out var client))
+ throw new IPCErrorException();
+
+ try
+ {
+ if (TryConvertSortOption(sortOption, out var propertyId, out _))
+ {
+ var isFastSort = Everything3ApiDllImport.Everything3_IsPropertyFastSort(client, propertyId);
+ CheckAndThrowExceptionOnErrorFromEverything3();
+ return isFastSort;
+ }
+ }
+ finally
+ {
+ _ = Everything3ApiDllImport.Everything3_DestroyClient(client);
+ CheckAndThrowExceptionOnErrorFromEverything3();
+ }
+
+ return true;
+ }
+
+ private static async IAsyncEnumerable SearchWithEverything3Async(IntPtr client,
+ EverythingSearchOption option,
+ EverythingHelper.PreparedQuery query,
+ [EnumeratorCancellation] CancellationToken token)
+ {
+ IntPtr searchState = IntPtr.Zero;
+ IntPtr resultList = IntPtr.Zero;
+ try
+ {
+ searchState = Everything3ApiDllImport.Everything3_CreateSearchState();
+ if (searchState == IntPtr.Zero)
+ {
+ CheckAndThrowExceptionOnErrorFromEverything3();
+ yield break;
+ }
+
+ _ = Everything3ApiDllImport.Everything3_SetSearchRegex(searchState, option.UseRegex);
+ _ = Everything3ApiDllImport.Everything3_SetSearchMatchPath(searchState, option.IsFullPathSearch);
+ _ = Everything3ApiDllImport.Everything3_SetSearchTextW(searchState, query.SearchText);
+ _ = Everything3ApiDllImport.Everything3_SetSearchHideResultOmissions(searchState, true);
+ _ = Everything3ApiDllImport.Everything3_SetSearchViewportOffset(searchState, (nuint)option.Offset);
+ _ = Everything3ApiDllImport.Everything3_SetSearchViewportCount(searchState, (nuint)option.MaxCount);
+
+ if (TryConvertSortOption(option.SortOption, out var sortPropertyId, out var ascending))
+ {
+ if (!Everything3ApiDllImport.Everything3_AddSearchSort(searchState, sortPropertyId, ascending))
+ {
+ CheckAndThrowExceptionOnErrorFromEverything3();
+ yield break;
+ }
+ }
+
+ _ = Everything3ApiDllImport.Everything3_ClearSearchPropertyRequests(searchState);
+ _ = Everything3ApiDllImport.Everything3_AddSearchPropertyRequestHighlighted(searchState, EVERYTHING3_PROPERTY_ID_NAME);
+ _ = Everything3ApiDllImport.Everything3_AddSearchPropertyRequestHighlighted(searchState, EVERYTHING3_PROPERTY_ID_PATH);
+ _ = Everything3ApiDllImport.Everything3_AddSearchPropertyRequest(searchState, EVERYTHING3_PROPERTY_ID_PATH_AND_NAME);
+ _ = Everything3ApiDllImport.Everything3_AddSearchPropertyRequest(searchState, EVERYTHING3_PROPERTY_ID_RUN_COUNT);
+
+ if (token.IsCancellationRequested)
+ yield break;
+
+ resultList = Everything3ApiDllImport.Everything3_Search(client, searchState);
+ if (resultList == IntPtr.Zero)
+ {
+ CheckAndThrowExceptionOnErrorFromEverything3();
+ yield break;
+ }
+
+ var resultCount = Everything3ApiDllImport.Everything3_GetResultListViewportCount(resultList);
+ for (nuint idx = 0; idx < resultCount; ++idx)
+ {
+ if (token.IsCancellationRequested)
+ {
+ yield break;
+ }
+
+ if (!TryCreateSearchResult(resultList, idx, out var result))
+ continue;
+
+ yield return result;
+ }
+ }
+ finally
+ {
+ if (resultList != IntPtr.Zero)
+ _ = Everything3ApiDllImport.Everything3_DestroyResultList(resultList);
+
+ if (searchState != IntPtr.Zero)
+ _ = Everything3ApiDllImport.Everything3_DestroySearchState(searchState);
+
+ _ = Everything3ApiDllImport.Everything3_DestroyClient(client);
+ }
+
+ await Task.CompletedTask;
+ }
+
+ private static bool TryCreateSearchResult(IntPtr resultList, nuint resultIndex, out SearchResult result)
+ {
+ result = default;
+
+ if (!TryGetResultFullPath(resultList, resultIndex, out var fullPath))
+ return false;
+
+ result = new SearchResult
+ {
+ FullPath = fullPath,
+ Type = GetResultType(resultList, resultIndex),
+ Score = Convert.ToInt32(Everything3ApiDllImport.Everything3_GetResultRunCount(resultList, resultIndex)),
+ HighlightData = GetHighlightData(resultList, resultIndex)
+ };
+
+ return true;
+ }
+
+ private static bool TryGetResultFullPath(IntPtr resultList, nuint resultIndex, out string fullPath)
+ {
+ _buffer.Clear();
+ var fullPathLength = Everything3ApiDllImport.Everything3_GetResultFullPathNameW(resultList, resultIndex, _buffer, BufferSize);
+ if (fullPathLength == 0)
+ {
+ CheckAndThrowExceptionOnErrorFromEverything3();
+ fullPath = string.Empty;
+ return false;
+ }
+
+ fullPath = _buffer.ToString();
+ return !string.IsNullOrEmpty(fullPath);
+ }
+
+ private static ResultType GetResultType(IntPtr resultList, nuint resultIndex)
+ {
+ return Everything3ApiDllImport.Everything3_IsFolderResult(resultList, resultIndex)
+ ? ResultType.Folder
+ : Everything3ApiDllImport.Everything3_IsRootResult(resultList, resultIndex)
+ ? ResultType.Volume
+ : ResultType.File;
+ }
+
+ private static List GetHighlightData(IntPtr resultList, nuint resultIndex)
+ {
+ _buffer.Clear();
+ var highlightedFileNameLength = Everything3ApiDllImport.Everything3_GetResultPropertyTextHighlightedW(
+ resultList,
+ resultIndex,
+ EVERYTHING3_PROPERTY_ID_NAME,
+ _buffer,
+ BufferSize);
+
+ return highlightedFileNameLength > 0
+ ? EverythingHelper.EverythingHighlightStringToHighlightList(_buffer.ToString())
+ : [];
+ }
+
+ private bool TryUseEverything3Client(Action action)
+ {
+ if (!TryConnectEverything3(out var client))
+ return false;
+
+ try
+ {
+ action(client);
+ return true;
+ }
+ finally
+ {
+ _ = Everything3ApiDllImport.Everything3_DestroyClient(client);
+ }
+ }
+
+ private bool TryConnectEverything3(out IntPtr client)
+ {
+ client = IntPtr.Zero;
+ var normalizedInstanceName = string.IsNullOrWhiteSpace(_instanceName)
+ ? EverythingApiFactory.DefaultEverything15InstanceName
+ : _instanceName.Trim();
+
+ try
+ {
+ client = Everything3ApiDllImport.Everything3_ConnectW(normalizedInstanceName);
+ return client != IntPtr.Zero;
+ }
+ catch (DllNotFoundException)
+ {
+ return false;
+ }
+ catch (EntryPointNotFoundException)
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Covert the old Everything 1.4 sort options in our UI to Everything 3 property ID and sort direction for compatibility.
+ ///
+ private static bool TryConvertSortOption(EverythingSortOption sortOption, out uint propertyId, out bool ascending)
+ {
+ switch (sortOption)
+ {
+ case EverythingSortOption.NAME_ASCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_NAME;
+ ascending = true;
+ return true;
+ case EverythingSortOption.NAME_DESCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_NAME;
+ ascending = false;
+ return true;
+ case EverythingSortOption.PATH_ASCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_PATH;
+ ascending = true;
+ return true;
+ case EverythingSortOption.PATH_DESCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_PATH;
+ ascending = false;
+ return true;
+ case EverythingSortOption.SIZE_ASCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_SIZE;
+ ascending = true;
+ return true;
+ case EverythingSortOption.SIZE_DESCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_SIZE;
+ ascending = false;
+ return true;
+ case EverythingSortOption.EXTENSION_ASCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_EXTENSION;
+ ascending = true;
+ return true;
+ case EverythingSortOption.EXTENSION_DESCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_EXTENSION;
+ ascending = false;
+ return true;
+ case EverythingSortOption.TYPE_NAME_ASCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_TYPE;
+ ascending = true;
+ return true;
+ case EverythingSortOption.TYPE_NAME_DESCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_TYPE;
+ ascending = false;
+ return true;
+ case EverythingSortOption.DATE_CREATED_ASCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_DATE_CREATED;
+ ascending = true;
+ return true;
+ case EverythingSortOption.DATE_CREATED_DESCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_DATE_CREATED;
+ ascending = false;
+ return true;
+ case EverythingSortOption.DATE_MODIFIED_ASCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_DATE_MODIFIED;
+ ascending = true;
+ return true;
+ case EverythingSortOption.DATE_MODIFIED_DESCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_DATE_MODIFIED;
+ ascending = false;
+ return true;
+ case EverythingSortOption.ATTRIBUTES_ASCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_ATTRIBUTES;
+ ascending = true;
+ return true;
+ case EverythingSortOption.ATTRIBUTES_DESCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_ATTRIBUTES;
+ ascending = false;
+ return true;
+ case EverythingSortOption.FILE_LIST_FILENAME_ASCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_FILE_LIST_NAME;
+ ascending = true;
+ return true;
+ case EverythingSortOption.FILE_LIST_FILENAME_DESCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_FILE_LIST_NAME;
+ ascending = false;
+ return true;
+ case EverythingSortOption.RUN_COUNT_DESCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_RUN_COUNT;
+ ascending = false;
+ return true;
+ case EverythingSortOption.DATE_RECENTLY_CHANGED_ASCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_DATE_RECENTLY_CHANGED;
+ ascending = true;
+ return true;
+ case EverythingSortOption.DATE_RECENTLY_CHANGED_DESCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_DATE_RECENTLY_CHANGED;
+ ascending = false;
+ return true;
+ case EverythingSortOption.DATE_ACCESSED_ASCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_DATE_ACCESSED;
+ ascending = true;
+ return true;
+ case EverythingSortOption.DATE_ACCESSED_DESCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_DATE_ACCESSED;
+ ascending = false;
+ return true;
+ case EverythingSortOption.DATE_RUN_ASCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_DATE_RUN;
+ ascending = true;
+ return true;
+ case EverythingSortOption.DATE_RUN_DESCENDING:
+ propertyId = EVERYTHING3_PROPERTY_ID_DATE_RUN;
+ ascending = false;
+ return true;
+ default:
+ propertyId = EVERYTHING3_PROPERTY_ID_NAME;
+ ascending = true;
+ return false;
+ }
+ }
+
+ private static void CheckAndThrowExceptionOnErrorFromEverything3()
+ {
+ switch (Everything3ApiDllImport.Everything3_GetLastError())
+ {
+ case EVERYTHING3_ERROR_OUT_OF_MEMORY:
+ throw new MemoryErrorException();
+ case EVERYTHING3_ERROR_IPC_PIPE_NOT_FOUND:
+ case EVERYTHING3_ERROR_DISCONNECTED:
+ throw new IPCErrorException();
+ case EVERYTHING3_ERROR_INVALID_PARAMETER:
+ throw new InvalidCallException();
+ case EVERYTHING3_ERROR_PROPERTY_NOT_FOUND:
+ throw new ArgumentException("EVERYTHING3_ERROR_PROPERTY_NOT_FOUND");
+ }
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAvailabilityService.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAvailabilityService.cs
new file mode 100644
index 00000000000..dbde7ff3093
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAvailabilityService.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using Flow.Launcher.Plugin.Explorer.Exceptions;
+
+namespace Flow.Launcher.Plugin.Explorer.Search.Everything
+{
+ internal class EverythingAvailabilityService
+ {
+ private readonly Settings _settings;
+
+ public EverythingAvailabilityService(Settings settings)
+ {
+ _settings = settings;
+ }
+
+ public async ValueTask EnsureAvailableAsync(IEverythingApi api, CancellationToken token = default)
+ {
+ try
+ {
+ if (!await api.IsEverythingRunningAsync(token))
+ {
+ if (_settings.EnableEverything15Support)
+ {
+ throw new EngineNotAvailableException(
+ Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
+ Localize.flowlauncher_plugin_everything_15_resolution(),
+ Localize.flowlauncher_plugin_everything_15_unavailable(),
+ Constants.EverythingErrorImagePath,
+ ClickToInstallEverythingAsync);
+ }
+ else
+ {
+ throw new EngineNotAvailableException(
+ Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
+ Localize.flowlauncher_plugin_everything_click_to_launch_or_install(),
+ Localize.flowlauncher_plugin_everything_15_unavailable(),
+ Constants.EverythingErrorImagePath,
+ ClickToInstallEverythingAsync);
+ }
+ }
+ }
+ catch (Exception ex) when (ex is DllNotFoundException || ex is EntryPointNotFoundException)
+ {
+ throw new EngineNotAvailableException(
+ Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
+ Localize.flowlauncher_plugin_everything_architecture_check(),
+ Constants.GeneralSearchErrorImagePath,
+ Localize.flowlauncher_plugin_everything_sdk_issue());
+ }
+ }
+
+ private async ValueTask ClickToInstallEverythingAsync(ActionContext _)
+ {
+ try
+ {
+ var installedPath = await EverythingDownloadHelper.PromptDownloadIfNotInstallAsync(_settings.EverythingInstalledPath, Main.Context.API);
+
+ if (installedPath == null)
+ {
+ Main.Context.API.ShowMsgError(Localize.flowlauncher_plugin_everything_not_found());
+ Main.Context.API.LogError(nameof(EverythingAvailabilityService), "Unable to find Everything.exe");
+
+ return false;
+ }
+
+ _settings.EverythingInstalledPath = installedPath;
+ Process.Start(installedPath, "-startup");
+
+ return true;
+ }
+ catch (Exception e)
+ {
+ Main.Context.API.ShowMsgError(Localize.flowlauncher_plugin_everything_install_issue());
+ Main.Context.API.LogException(nameof(EverythingAvailabilityService), "Failed to install Everything", e);
+
+ return false;
+ }
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingHelper.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingHelper.cs
new file mode 100644
index 00000000000..0be29b4ab57
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingHelper.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Flow.Launcher.Plugin.Explorer.Search.Everything
+{
+ public enum EverythingStateCode
+ {
+ OK,
+ MemoryError,
+ IPCError,
+ RegisterClassExError,
+ CreateWindowError,
+ CreateThreadError,
+ InvalidIndexError,
+ InvalidCallError
+ }
+
+ public static class EverythingHelper
+ {
+ #region Query
+
+ public readonly record struct PreparedQuery(
+ EverythingSearchOption Option,
+ string SearchText);
+
+ public static PreparedQuery PrepareQuery(EverythingSearchOption option)
+ {
+ if (option.Offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(option.Offset), option.Offset, "Offset must be greater than or equal to 0");
+ if (option.MaxCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(option.MaxCount), option.MaxCount, "MaxCount must be greater than or equal to 0");
+
+ var keyword = option.Keyword;
+ if (!string.IsNullOrEmpty(keyword) && keyword.StartsWith("@", StringComparison.Ordinal))
+ {
+ option.UseRegex = true;
+ keyword = keyword[1..];
+ }
+
+ var builder = new StringBuilder();
+ builder.Append(keyword);
+
+ if (!string.IsNullOrWhiteSpace(option.ParentPath))
+ {
+ builder.Append($" {(option.IsRecursive ? "" : "parent:")}\"{option.ParentPath}\"");
+ }
+
+ if (option.IsContentSearch)
+ {
+ builder.Append($" content:\"{option.ContentSearchKeyword}\"");
+ }
+
+ return new PreparedQuery(option with { Keyword = keyword }, builder.ToString());
+ }
+
+ #endregion
+
+ #region Result
+
+ ///
+ /// Convert the highlighted string from Everything API to a list of highlight indexes for our Result.
+ ///
+ /// Text inside a * quote is highlighted, two consecutive *'s is a single literal *. For example, in the highlighted text: abc*123* the 123 part is highlighted.
+ /// A list of zero-based character indices that should be highlighted.
+ public static List EverythingHighlightStringToHighlightList(string highlightString)
+ {
+ var highlightData = new List();
+
+ if (string.IsNullOrEmpty(highlightString))
+ return highlightData;
+
+ var isHighlighted = false;
+ var actualIndex = 0; // Index in the actual string (without * markers)
+ var length = highlightString.Length;
+
+ for (var i = 0; i < length; i++)
+ {
+ if (highlightString[i] == '*')
+ {
+ // Check if it's a literal * (two consecutive *)
+ if (i + 1 < length && highlightString[i + 1] == '*')
+ {
+ // Two consecutive *'s represent a single literal *
+ if (isHighlighted)
+ {
+ highlightData.Add(actualIndex);
+ }
+ actualIndex++;
+ i++; // Skip the next *
+ }
+ else
+ {
+ isHighlighted = !isHighlighted;
+ }
+ }
+ else
+ {
+ // Regular character
+ if (isHighlighted)
+ {
+ highlightData.Add(actualIndex);
+ }
+ actualIndex++;
+ }
+ }
+
+ return highlightData;
+ }
+
+ #endregion
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSdkLoader.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSdkLoader.cs
new file mode 100644
index 00000000000..e36b808c07e
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSdkLoader.cs
@@ -0,0 +1,31 @@
+using System.Threading;
+
+namespace Flow.Launcher.Plugin.Explorer.Search.Everything
+{
+ internal static class EverythingSdkLoader
+ {
+ private static readonly SemaphoreSlim _semaphore = new(1, 1);
+
+ public static void EnsureLoaded(string sdkDirectory, bool enableEverything15Support)
+ {
+ _semaphore.Wait();
+ try
+ {
+ if (enableEverything15Support)
+ {
+ if (!Everything3ApiDllImport.IsLoaded)
+ Everything3ApiDllImport.Load(sdkDirectory);
+ }
+ else
+ {
+ if (!EverythingApiDllImport.IsLoaded)
+ EverythingApiDllImport.Load(sdkDirectory);
+ }
+ }
+ finally
+ {
+ _semaphore.Release();
+ }
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs
index eb994a6f98f..787b5e3bb40 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
@@ -11,70 +10,22 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
{
public class EverythingSearchManager : IIndexProvider, IContentIndexProvider, IPathIndexProvider
{
- private static readonly string ClassName = nameof(EverythingSearchManager);
-
private Settings Settings { get; }
+ private readonly EverythingAvailabilityService _availabilityService;
+ private readonly Lock _syncRoot = new();
+ private bool isApiInitialized;
+ private IEverythingApi api;
public EverythingSearchManager(Settings settings)
{
Settings = settings;
- }
-
- private async ValueTask ThrowIfEverythingNotAvailableAsync(CancellationToken token = default)
- {
- try
- {
- if (!await EverythingApi.IsEverythingRunningAsync(token))
- throw new EngineNotAvailableException(
- Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
- Localize.flowlauncher_plugin_everything_click_to_launch_or_install(),
- Localize.flowlauncher_plugin_everything_is_not_running(),
- Constants.EverythingErrorImagePath,
- ClickToInstallEverythingAsync);
- }
- catch (DllNotFoundException)
- {
- throw new EngineNotAvailableException(
- Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
- "Please check whether your system is x86 or x64",
- Constants.GeneralSearchErrorImagePath,
- Localize.flowlauncher_plugin_everything_sdk_issue());
- }
- }
-
- private async ValueTask ClickToInstallEverythingAsync(ActionContext _)
- {
- try
- {
- var installedPath = await EverythingDownloadHelper.PromptDownloadIfNotInstallAsync(Settings.EverythingInstalledPath, Main.Context.API);
-
- if (installedPath == null)
- {
- Main.Context.API.ShowMsgError(Localize.flowlauncher_plugin_everything_not_found());
- Main.Context.API.LogError(ClassName, "Unable to find Everything.exe");
-
- return false;
- }
-
- Settings.EverythingInstalledPath = installedPath;
- Process.Start(installedPath, "-startup");
-
- return true;
- }
- // Sometimes Everything installation will fail because of permission issues or file not found issues
- // Just let the user know that Everything is not installed properly and ask them to install it manually
- catch (Exception e)
- {
- Main.Context.API.ShowMsgError(Localize.flowlauncher_plugin_everything_install_issue());
- Main.Context.API.LogException(ClassName, "Failed to install Everything", e);
-
- return false;
- }
+ _availabilityService = new EverythingAvailabilityService(settings);
+ api = EverythingApiFactory.Create(settings);
}
public async IAsyncEnumerable SearchAsync(string search, [EnumeratorCancellation] CancellationToken token)
{
- await ThrowIfEverythingNotAvailableAsync(token);
+ await _availabilityService.EnsureAvailableAsync(api, token);
if (token.IsCancellationRequested)
yield break;
@@ -85,14 +36,14 @@ public async IAsyncEnumerable SearchAsync(string search, [Enumerat
IsFullPathSearch: Settings.EverythingSearchFullPath,
IsRunCounterEnabled: Settings.EverythingEnableRunCount);
- await foreach (var result in EverythingApi.SearchAsync(option, token))
+ await foreach (var result in api.SearchAsync(option, token))
yield return result;
}
public async IAsyncEnumerable ContentSearchAsync(string plainSearch, string contentSearch,
[EnumeratorCancellation] CancellationToken token)
{
- await ThrowIfEverythingNotAvailableAsync(token);
+ await _availabilityService.EnsureAvailableAsync(api, token);
if (!Settings.EnableEverythingContentSearch)
{
@@ -119,7 +70,7 @@ public async IAsyncEnumerable ContentSearchAsync(string plainSearc
IsFullPathSearch: Settings.EverythingSearchFullPath,
IsRunCounterEnabled: Settings.EverythingEnableRunCount);
- await foreach (var result in EverythingApi.SearchAsync(option, token))
+ await foreach (var result in api.SearchAsync(option, token))
{
yield return result;
}
@@ -127,7 +78,7 @@ public async IAsyncEnumerable ContentSearchAsync(string plainSearc
public async IAsyncEnumerable EnumerateAsync(string path, string search, bool recursive, [EnumeratorCancellation] CancellationToken token)
{
- await ThrowIfEverythingNotAvailableAsync(token);
+ await _availabilityService.EnsureAvailableAsync(api, token);
if (token.IsCancellationRequested)
yield break;
@@ -140,8 +91,25 @@ public async IAsyncEnumerable EnumerateAsync(string path, string s
IsFullPathSearch: Settings.EverythingSearchFullPath,
IsRunCounterEnabled: Settings.EverythingEnableRunCount);
- await foreach (var result in EverythingApi.SearchAsync(option, token))
+ await foreach (var result in api.SearchAsync(option, token))
yield return result;
}
+
+ public void InitializeApi(string sdkDirectory)
+ {
+ lock (_syncRoot)
+ {
+ if (isApiInitialized)
+ return;
+
+ EverythingSdkLoader.EnsureLoaded(sdkDirectory, Settings.EnableEverything15Support);
+ api = EverythingApiFactory.Create(Settings);
+ isApiInitialized = true;
+ }
+ }
+
+ public bool IsFastSortOption(EverythingSortOption sortOption) => api.IsFastSortOption(sortOption);
+
+ public Task IncrementRunCounterAsync(string fileOrFolder) => api.IncrementRunCounterAsync(fileOrFolder);
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs
index d8b670a0895..1d6446d396a 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs
@@ -10,6 +10,7 @@ public record struct EverythingSearchOption(
int Offset = 0,
int MaxCount = 100,
bool IsFullPathSearch = true,
- bool IsRunCounterEnabled = true
+ bool IsRunCounterEnabled = true,
+ bool UseRegex = false
);
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/IEverythingApi.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/IEverythingApi.cs
new file mode 100644
index 00000000000..3c7ed7e896a
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/IEverythingApi.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Flow.Launcher.Plugin.Explorer.Search.Everything
+{
+ public interface IEverythingApi
+ {
+ ValueTask IsEverythingRunningAsync(CancellationToken token = default);
+ IAsyncEnumerable SearchAsync(EverythingSearchOption option, CancellationToken token = default);
+ Task IncrementRunCounterAsync(string fileOrFolder);
+ bool IsFastSortOption(EverythingSortOption sortOption);
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
index b73a59bcdc1..7845498a963 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
@@ -413,7 +413,7 @@ private static void OpenFolder(string folderPath, string fileNameOrFilePath = nu
private static void IncrementEverythingRunCounterIfNeeded(string fileOrFolder)
{
if (Settings.EverythingEnabled && Settings.EverythingEnableRunCount)
- _ = Task.Run(() => EverythingApi.IncrementRunCounterAsync(fileOrFolder));
+ _ = Task.Run(() => Settings.EverythingManagerInstance.IncrementRunCounterAsync(fileOrFolder));
}
private static string GetFileMoreInfoTooltip(string filePath)
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
index 28ab1d2e24b..46216f931c6 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
@@ -291,6 +291,9 @@ private bool UseWindowsIndexForDirectorySearch(string locationPath)
private bool IsExcludedFile(SearchResult result)
{
+ if (string.IsNullOrEmpty(result.FullPath))
+ return false;
+
string[] excludedFileTypes = Settings.ExcludedFileTypes.Split([','], StringSplitOptions.RemoveEmptyEntries);
string fileExtension = Path.GetExtension(result.FullPath).TrimStart('.');
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs
index b0fbad84fbe..e746bc453e6 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs
@@ -89,7 +89,7 @@ public class Settings
#region SearchEngine
- private EverythingSearchManager EverythingManagerInstance => _everythingManagerInstance ??= new EverythingSearchManager(this);
+ internal EverythingSearchManager EverythingManagerInstance => _everythingManagerInstance ??= new EverythingSearchManager(this);
private WindowsIndexSearchManager WindowsIndexSearchManager => _windowsIndexSearchManager ??= new WindowsIndexSearchManager(this);
public IndexSearchEngineOption IndexSearchEngine { get; set; } = IndexSearchEngineOption.WindowsIndex;
@@ -163,6 +163,8 @@ public enum ContentIndexSearchEngineOption
public bool EverythingSearchFullPath { get; set; } = false;
public bool EverythingEnableRunCount { get; set; } = true;
+ public bool EnableEverything15Support { get; set; } = false;
+ public string Everything15InstanceName { get; set; } = EverythingApiFactory.DefaultEverything15InstanceName;
#endregion
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
index a9b2e6a89e3..22093a9abf1 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
@@ -619,7 +619,7 @@ public Visibility FastSortWarningVisibility
{
try
{
- return EverythingApi.IsFastSortOption(Settings.SortOption) ? Visibility.Collapsed : Visibility.Visible;
+ return Settings.EverythingManagerInstance.IsFastSortOption(Settings.SortOption) ? Visibility.Collapsed : Visibility.Visible;
}
catch (IPCErrorException)
{
@@ -627,7 +627,7 @@ public Visibility FastSortWarningVisibility
// update the message to let user know in the settings panel.
return Visibility.Visible;
}
- catch (DllNotFoundException)
+ catch (Exception ex) when (ex is DllNotFoundException || ex is EntryPointNotFoundException)
{
return Visibility.Collapsed;
}
@@ -642,14 +642,16 @@ public string SortOptionWarningMessage
{
// this method is used to determine if Everything service is running because as at Everything v1.4.1
// the sdk does not provide a dedicated interface to determine if it is running.
- return EverythingApi.IsFastSortOption(Settings.SortOption) ? string.Empty
+ return Settings.EverythingManagerInstance.IsFastSortOption(Settings.SortOption) ? string.Empty
: Localize.flowlauncher_plugin_everything_nonfastsort_warning();
}
catch (IPCErrorException)
{
- return Localize.flowlauncher_plugin_everything_is_not_running();
+ return Settings.EnableEverything15Support
+ ? Localize.flowlauncher_plugin_everything_15_sort_warning()
+ : Localize.flowlauncher_plugin_everything_is_not_running();
}
- catch (DllNotFoundException)
+ catch (Exception ex) when (ex is DllNotFoundException || ex is EntryPointNotFoundException)
{
return Localize.flowlauncher_plugin_everything_sdk_issue();
}
@@ -666,6 +668,46 @@ public string EverythingInstalledPath
}
}
+ public bool EnableEverything15Support
+ {
+ get => Settings.EnableEverything15Support;
+ set
+ {
+ if (Settings.EnableEverything15Support == value)
+ return;
+
+ Settings.EnableEverything15Support = value;
+
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(FastSortWarningVisibility));
+ OnPropertyChanged(nameof(SortOptionWarningMessage));
+ }
+ }
+
+ public string Everything15InstanceName
+ {
+ get => Settings.Everything15InstanceName;
+ set
+ {
+ var instanceName = string.IsNullOrWhiteSpace(value)
+ ? EverythingApiFactory.DefaultEverything15InstanceName
+ : value.Trim();
+
+ if (Settings.Everything15InstanceName == instanceName)
+ return;
+
+ Settings.Everything15InstanceName = instanceName;
+
+ if (EnableEverything15Support)
+ {
+ OnPropertyChanged(nameof(FastSortWarningVisibility));
+ OnPropertyChanged(nameof(SortOptionWarningMessage));
+ }
+
+ OnPropertyChanged();
+ }
+ }
+
#endregion
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml
index d6066df953f..90a0213c301 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml
@@ -13,6 +13,8 @@
mc:Ignorable="d">
+
+
@@ -435,6 +437,8 @@
+
+
@@ -602,6 +606,8 @@
+
+
@@ -614,11 +620,37 @@
Grid.ColumnSpan="2"
Margin="{StaticResource SettingPanelItemTopBottomMargin}"
HorizontalAlignment="Left"
+ Content="{DynamicResource flowlauncher_plugin_everything_enable_15_support}"
+ IsChecked="{Binding EnableEverything15Support}" />
+
+
+
+
+