diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt
index 494d4de93..00cc67ea0 100644
--- a/.github/actions/spelling/allow.txt
+++ b/.github/actions/spelling/allow.txt
@@ -2,3 +2,4 @@ github
https
ssh
ubuntu
+runcount
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index 79d106ef2..d9cf68469 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -233,8 +233,8 @@ namespace Flow.Launcher.Plugin
/// Open directory in an explorer configured by user via Flow's Settings. The default is Windows Explorer
///
/// Directory Path to open
- /// Extra FileName Info
- public void OpenDirectory(string DirectoryPath, string FileName = null);
+ /// Extra FileName Info
+ public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null);
///
/// Opens the URL with the given Uri object.
diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs
index dc24872f5..1c4467762 100644
--- a/Flow.Launcher.Plugin/Result.cs
+++ b/Flow.Launcher.Plugin/Result.cs
@@ -178,7 +178,7 @@ namespace Flow.Launcher.Plugin
///
public override string ToString()
{
- return Title + SubTitle;
+ return Title + SubTitle + Score;
}
///
diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
index bd8d32ff5..6b7f0c2d3 100644
--- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
+++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
@@ -14,8 +14,6 @@ namespace Flow.Launcher.Plugin.SharedCommands
{
private const string FileExplorerProgramName = "explorer";
- private const string FileExplorerProgramEXE = "explorer.exe";
-
///
/// Copies the folder and all of its files and folders
/// including subfolders to the target location
@@ -151,7 +149,12 @@ namespace Flow.Launcher.Plugin.SharedCommands
///
public static void OpenPath(string fileOrFolderPath)
{
- var psi = new ProcessStartInfo { FileName = FileExplorerProgramName, UseShellExecute = true, Arguments = '"' + fileOrFolderPath + '"' };
+ var psi = new ProcessStartInfo
+ {
+ FileName = FileExplorerProgramName,
+ UseShellExecute = true,
+ Arguments = '"' + fileOrFolderPath + '"'
+ };
try
{
if (LocationExists(fileOrFolderPath) || FileExists(fileOrFolderPath))
@@ -167,15 +170,6 @@ namespace Flow.Launcher.Plugin.SharedCommands
}
}
- ///
- /// Open the folder that contains
- ///
- ///
- public static void OpenContainingFolder(string path)
- {
- Process.Start(FileExplorerProgramEXE, $" /select,\"{path}\"");
- }
-
///
/// This checks whether a given string is a directory path or network location string.
/// It does not check if location actually exists.
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index b2487693e..ec997f537 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -1,4 +1,4 @@
- using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
@@ -195,17 +195,20 @@ namespace Flow.Launcher
((PluginJsonStorage)_pluginJsonStorages[type]).Save();
}
- public void OpenDirectory(string DirectoryPath, string FileName = null)
+ public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null)
{
using var explorer = new Process();
var explorerInfo = _settingsVM.Settings.CustomExplorer;
explorer.StartInfo = new ProcessStartInfo
{
FileName = explorerInfo.Path,
- Arguments = FileName is null ?
- explorerInfo.DirectoryArgument.Replace("%d", DirectoryPath) :
- explorerInfo.FileArgument.Replace("%d", DirectoryPath).Replace("%f",
- Path.IsPathRooted(FileName) ? FileName : Path.Combine(DirectoryPath, FileName))
+ Arguments = FileNameOrFilePath is null
+ ? explorerInfo.DirectoryArgument.Replace("%d", DirectoryPath)
+ : explorerInfo.FileArgument
+ .Replace("%d", DirectoryPath)
+ .Replace("%f",
+ Path.IsPathRooted(FileNameOrFilePath) ? FileNameOrFilePath : Path.Combine(DirectoryPath, FileNameOrFilePath)
+ )
};
explorer.Start();
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs
index 3efd09c4d..e618b5c36 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs
@@ -17,7 +17,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
{
private const int BufferSize = 4096;
- private static SemaphoreSlim _semaphore = new(1, 1);
+ private static readonly SemaphoreSlim _semaphore = new(1, 1);
// cached buffer to remove redundant allocations.
private static readonly StringBuilder buffer = new(BufferSize);
@@ -34,6 +34,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
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.
///
@@ -78,7 +81,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
if (option.MaxCount < 0)
throw new ArgumentOutOfRangeException(nameof(option.MaxCount), option.MaxCount, "MaxCount must be greater than or equal to 0");
-
+
await _semaphore.WaitAsync(token);
@@ -112,6 +115,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
EverythingApiDllImport.Everything_SetSort(option.SortOption);
EverythingApiDllImport.Everything_SetMatchPath(option.IsFullPathSearch);
+
+ if (option.SortOption == SortOption.RUN_COUNT_DESCENDING)
+ {
+ EverythingApiDllImport.Everything_SetRequestFlags(EVERYTHING_REQUEST_FULL_PATH_AND_FILE_NAME | EVERYTHING_REQUEST_RUN_COUNT);
+ }
+ else
+ {
+ EverythingApiDllImport.Everything_SetRequestFlags(EVERYTHING_REQUEST_FULL_PATH_AND_FILE_NAME);
+ }
+
+
if (token.IsCancellationRequested) yield break;
@@ -132,10 +146,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
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
+ ResultType.Volume,
+ Score = (int)EverythingApiDllImport.Everything_GetResultRunCount( (uint)idx)
};
yield return result;
@@ -172,5 +188,19 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
throw new ArgumentOutOfRangeException();
}
}
+
+ public static async Task IncrementRunCounterAsync(string fileOrFolder)
+ {
+ await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
+ try
+ {
+ _ = EverythingApiDllImport.Everything_IncRunCountFromFileName(fileOrFolder);
+ }
+ catch (Exception)
+ {
+ /*ignored*/
+ }
+ 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 344707892..1ea23777c 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs
@@ -39,6 +39,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue"));
}
}
+
private async ValueTask ClickToInstallEverythingAsync(ActionContext _)
{
var installedPath = await EverythingDownloadHelper.PromptDownloadIfNotInstallAsync(Settings.EverythingInstalledPath, Main.Context.API);
@@ -68,8 +69,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
await foreach (var result in EverythingApi.SearchAsync(option, token))
yield return result;
}
- public async IAsyncEnumerable ContentSearchAsync(string plainSearch,
- string contentSearch,
+
+ public async IAsyncEnumerable ContentSearchAsync(string plainSearch, string contentSearch,
[EnumeratorCancellation] CancellationToken token)
{
await ThrowIfEverythingNotAvailableAsync(token);
@@ -102,6 +103,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
yield return result;
}
}
+
public async IAsyncEnumerable EnumerateAsync(string path, string search, bool recursive, [EnumeratorCancellation] CancellationToken token)
{
await ThrowIfEverythingNotAvailableAsync(token);
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/SortOption.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/SortOption.cs
index 434afd1b4..c57e3fe4a 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/SortOption.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/SortOption.cs
@@ -27,7 +27,6 @@ namespace Flow.Launcher.Plugin.Everything.Everything
ATTRIBUTES_DESCENDING = 16u,
FILE_LIST_FILENAME_ASCENDING = 17u,
FILE_LIST_FILENAME_DESCENDING = 18u,
- RUN_COUNT_ASCENDING = 19u,
RUN_COUNT_DESCENDING = 20u,
DATE_RECENTLY_CHANGED_ASCENDING = 21u,
DATE_RECENTLY_CHANGED_DESCENDING = 22u,
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
index ed4f39735..7147c348e 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
@@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
+using Flow.Launcher.Plugin.Explorer.Search.Everything;
namespace Flow.Launcher.Plugin.Explorer.Search
{
@@ -37,13 +38,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search
var keyword = usePathSearchActionKeyword ? pathSearchActionKeyword : searchActionKeyword;
- var formatted_path = path;
+ var formattedPath = path;
if (type == ResultType.Folder)
// 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;
+ formattedPath = path.EndsWith(Constants.DirectorySeparator) ? path : path + Constants.DirectorySeparator;
- return $"{keyword}{formatted_path}";
+ return $"{keyword}{formattedPath}";
}
public static string GetAutoCompleteText(string title, Query query, string path, ResultType resultType)
@@ -57,10 +58,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
return result.Type switch
{
- ResultType.Folder or ResultType.Volume => CreateFolderResult(Path.GetFileName(result.FullPath),
- result.FullPath, result.FullPath, query, 0, result.WindowsIndexed),
- ResultType.File => CreateFileResult(
- result.FullPath, query, 0, result.WindowsIndexed),
+ ResultType.Folder or ResultType.Volume =>
+ CreateFolderResult(Path.GetFileName(result.FullPath), result.FullPath, result.FullPath, query, result.Score, result.WindowsIndexed),
+ ResultType.File =>
+ CreateFileResult(result.FullPath, query, result.Score, result.WindowsIndexed),
_ => throw new ArgumentOutOfRangeException()
};
}
@@ -77,11 +78,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
CopyText = path,
Action = c =>
{
+ // open folder
if (c.SpecialKeyState.CtrlPressed || (!Settings.PathSearchKeywordEnabled && !Settings.SearchActionKeywordEnabled))
{
try
{
- Context.API.OpenDirectory(path);
+ OpenFolder(path);
return true;
}
catch (Exception ex)
@@ -91,6 +93,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
}
}
+ // or make this folder the current query
Context.API.ChangeQuery(GetPathWithActionKeyword(path, ResultType.Folder, query.ActionKeyword));
return false;
@@ -98,12 +101,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
Score = score,
TitleToolTip = InternationalizationManager.Instance.GetTranslation("plugin_explorer_plugin_ToolTipOpenDirectory"),
SubTitleToolTip = path,
- ContextData = new SearchResult
- {
- Type = ResultType.Folder,
- FullPath = path,
- WindowsIndexed = windowsIndexed
- }
+ ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, WindowsIndexed = windowsIndexed }
};
}
@@ -112,7 +110,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search
var progressBarColor = "#26a0da";
var title = string.Empty; // hide title when use progress bar,
var driveLetter = path[..1].ToUpper();
- var driveName = driveLetter + ":\\";
DriveInfo drv = new DriveInfo(driveLetter);
var freespace = ToReadableSize(drv.AvailableFreeSpace, 2);
var totalspace = ToReadableSize(drv.TotalSize, 2);
@@ -133,19 +130,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search
Score = 500,
ProgressBar = progressValue,
ProgressBarColor = progressBarColor,
- Action = c =>
+ Action = _ =>
{
- Context.API.OpenDirectory(path);
+ OpenFolder(path);
return true;
},
TitleToolTip = path,
SubTitleToolTip = path,
- ContextData = new SearchResult
- {
- Type = ResultType.Volume,
- FullPath = path,
- WindowsIndexed = windowsIndexed
- }
+ ContextData = new SearchResult { Type = ResultType.Volume, FullPath = path, WindowsIndexed = windowsIndexed }
};
}
@@ -153,7 +145,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
int mok = 0;
double drvSize = pDrvSize;
- string Space = "Byte";
+ string uom = "Byte"; // Unit Of Measurement
while (drvSize > 1024.0)
{
@@ -162,23 +154,23 @@ namespace Flow.Launcher.Plugin.Explorer.Search
}
if (mok == 1)
- Space = "KB";
+ uom = "KB";
else if (mok == 2)
- Space = " MB";
+ uom = " MB";
else if (mok == 3)
- Space = " GB";
+ uom = " GB";
else if (mok == 4)
- Space = " TB";
+ uom = " TB";
- var returnStr = $"{Convert.ToInt32(drvSize)}{Space}";
+ var returnStr = $"{Convert.ToInt32(drvSize)}{uom}";
if (mok != 0)
{
returnStr = pi switch
{
- 1 => $"{drvSize:F1}{Space}",
- 2 => $"{drvSize:F2}{Space}",
- 3 => $"{drvSize:F3}{Space}",
- _ => $"{Convert.ToInt32(drvSize)}{Space}"
+ 1 => $"{drvSize:F1}{uom}",
+ 2 => $"{drvSize:F2}{uom}",
+ 3 => $"{drvSize:F3}{uom}",
+ _ => $"{Convert.ToInt32(drvSize)}{uom}"
};
}
@@ -201,24 +193,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search
CopyText = folderPath,
Action = _ =>
{
- Context.API.OpenDirectory(folderPath);
+ OpenFolder(folderPath);
return true;
},
- ContextData = new SearchResult
- {
- Type = ResultType.Folder,
- FullPath = folderPath,
- WindowsIndexed = windowsIndexed
- }
+ ContextData = new SearchResult { Type = ResultType.Folder, FullPath = folderPath, WindowsIndexed = windowsIndexed }
};
}
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.Default;
+ Result.PreviewInfo preview = IsMedia(Path.GetExtension(filePath))
+ ? new Result.PreviewInfo { IsMedia = true, PreviewImagePath = filePath, }
+ : Result.PreviewInfo.Default;
var title = Path.GetFileName(filePath);
@@ -236,33 +222,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
try
{
- if (File.Exists(filePath) && c.SpecialKeyState.CtrlPressed && c.SpecialKeyState.ShiftPressed)
+ if (c.SpecialKeyState.CtrlPressed && c.SpecialKeyState.ShiftPressed)
{
- _ = Task.Run(() =>
- {
- try
- {
- Process.Start(new ProcessStartInfo
- {
- FileName = filePath,
- UseShellExecute = true,
- WorkingDirectory = Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty,
- Verb = "runas",
- });
- }
- catch (Exception e)
- {
- MessageBox.Show(e.Message, "Could not start " + filePath);
- }
- });
+ OpenFileAsAdmin(filePath);
}
else if (c.SpecialKeyState.CtrlPressed)
{
- Context.API.OpenDirectory(Path.GetDirectoryName(filePath), filePath);
+ OpenFolder(filePath, filePath);
}
else
{
- FilesFolders.OpenPath(filePath);
+ OpenFile(filePath);
}
}
catch (Exception ex)
@@ -274,32 +244,59 @@ namespace Flow.Launcher.Plugin.Explorer.Search
},
TitleToolTip = InternationalizationManager.Instance.GetTranslation("plugin_explorer_plugin_ToolTipOpenContainingFolder"),
SubTitleToolTip = filePath,
- ContextData = new SearchResult
- {
- Type = ResultType.File,
- FullPath = filePath,
- WindowsIndexed = windowsIndexed
- }
+ ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, WindowsIndexed = windowsIndexed }
};
return result;
}
- public static bool IsMedia(string extension)
+ private static bool IsMedia(string extension)
{
- if (string.IsNullOrEmpty(extension))
- {
- return false;
- }
- else
- {
- return MediaExtensions.Contains(extension.ToLowerInvariant());
- }
+ if (string.IsNullOrEmpty(extension)) { return false; }
+
+ return MediaExtensions.Contains(extension.ToLowerInvariant());
}
- public static readonly string[] MediaExtensions =
+ private static void OpenFile(string filePath)
{
- ".jpg", ".png", ".avi", ".mkv", ".bmp", ".gif", ".wmv", ".mp3", ".flac", ".mp4"
- };
+ IncrementEverythingRunCounterIfNeeded(filePath);
+ FilesFolders.OpenPath(filePath);
+ }
+
+ private static void OpenFolder(string folderPath, string fileNameOrFilePath = null)
+ {
+ IncrementEverythingRunCounterIfNeeded(folderPath);
+ Context.API.OpenDirectory(Path.GetDirectoryName(folderPath), fileNameOrFilePath);
+ }
+
+ private static void OpenFileAsAdmin(string filePath)
+ {
+ _ = Task.Run(() =>
+ {
+ try
+ {
+ IncrementEverythingRunCounterIfNeeded(filePath);
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = filePath,
+ UseShellExecute = true,
+ WorkingDirectory = Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty,
+ Verb = "runas",
+ });
+ }
+ catch (Exception e)
+ {
+ MessageBox.Show(e.Message, "Could not start " + filePath);
+ }
+ });
+ }
+
+ private static void IncrementEverythingRunCounterIfNeeded(string fileOrFolder)
+ {
+ if (Settings.EverythingEnabled)
+ _ = Task.Run(() => EverythingApi.IncrementRunCounterAsync(fileOrFolder));
+ }
+
+ private static readonly string[] MediaExtensions = { ".jpg", ".png", ".avi", ".mkv", ".bmp", ".gif", ".wmv", ".mp3", ".flac", ".mp4" };
}
public enum ResultType