Flow.Launcher/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs

421 lines
18 KiB
C#
Raw Permalink Normal View History

using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Controls;
2025-04-09 05:05:43 +00:00
using System.Windows.Input;
using Flow.Launcher.Plugin.Explorer.Search.Everything;
using Flow.Launcher.Plugin.Explorer.Views;
2025-04-09 05:05:43 +00:00
using Flow.Launcher.Plugin.SharedCommands;
2025-07-23 05:51:35 +00:00
using Flow.Launcher.Plugin.SharedModels;
using Peter;
2025-04-09 05:05:43 +00:00
using Path = System.IO.Path;
namespace Flow.Launcher.Plugin.Explorer.Search
{
public static class ResultManager
{
2025-06-04 09:26:33 +00:00
private static readonly string ClassName = nameof(ResultManager);
private static readonly string[] SizeUnits = { "B", "KB", "MB", "GB", "TB" };
private static PluginInitContext Context;
2021-07-02 03:38:07 +00:00
private static Settings Settings { get; set; }
2021-07-02 03:38:07 +00:00
public static void Init(PluginInitContext context, Settings settings)
{
Context = context;
2021-07-02 03:38:07 +00:00
Settings = settings;
2021-12-06 21:57:06 +00:00
}
2022-12-24 11:51:09 +00:00
public static string GetPathWithActionKeyword(string path, ResultType type, string actionKeyword)
2021-12-06 21:57:06 +00:00
{
2022-12-24 11:51:09 +00:00
// actionKeyword will be empty string if using global, query.ActionKeyword is ""
2021-12-07 21:01:16 +00:00
2022-12-24 11:51:09 +00:00
var usePathSearchActionKeyword = Settings.PathSearchKeywordEnabled && !Settings.SearchActionKeywordEnabled;
var pathSearchActionKeyword = Settings.PathSearchActionKeyword == Query.GlobalPluginWildcardSign
? string.Empty
2022-12-24 11:51:09 +00:00
: $"{Settings.PathSearchActionKeyword} ";
var searchActionKeyword = Settings.SearchActionKeyword == Query.GlobalPluginWildcardSign
? string.Empty
: $"{Settings.SearchActionKeyword} ";
var keyword = usePathSearchActionKeyword ? pathSearchActionKeyword : searchActionKeyword;
2023-03-08 12:20:33 +00:00
var formattedPath = path;
2021-12-07 21:01:16 +00:00
if (type == ResultType.Folder)
2023-01-13 07:12:45 +00:00
// the separator is needed so when navigating the folder structure contents of the folder are listed
2023-03-08 12:20:33 +00:00
formattedPath = path.EndsWith(Constants.DirectorySeparator) ? path : path + Constants.DirectorySeparator;
2021-12-07 21:01:16 +00:00
2023-03-08 12:20:33 +00:00
return $"{keyword}{formattedPath}";
}
public static string GetAutoCompleteText(string title, Query query, string path, ResultType resultType)
2022-12-22 11:57:37 +00:00
{
return !Settings.PathSearchKeywordEnabled && !Settings.SearchActionKeywordEnabled
? $"{query.ActionKeyword} {title}" // Only Quick Access action keyword is used in this scenario
: GetPathWithActionKeyword(path, resultType, query.ActionKeyword);
2022-12-22 11:57:37 +00:00
}
public static Result CreateResult(Query query, SearchResult result)
{
return result.Type switch
{
2023-03-03 10:30:25 +00:00
ResultType.Folder or ResultType.Volume =>
CreateFolderResult(Path.GetFileName(result.FullPath), result.FullPath, result.FullPath, query, result.Score, result.WindowsIndexed),
2023-03-03 10:30:25 +00:00
ResultType.File =>
CreateFileResult(result.FullPath, query, result.Score, result.WindowsIndexed),
2025-04-09 05:05:43 +00:00
_ => throw new ArgumentOutOfRangeException(null)
};
}
internal static void ShowNativeContextMenu(string path, ResultType type)
{
2025-07-23 05:51:35 +00:00
var screenWithMouseCursor = MonitorInfo.GetCursorDisplayMonitor();
var xOfScreenCenter = screenWithMouseCursor.WorkingArea.Left + screenWithMouseCursor.WorkingArea.Width / 2;
var yOfScreenCenter = screenWithMouseCursor.WorkingArea.Top + screenWithMouseCursor.WorkingArea.Height / 2;
2025-07-23 05:51:35 +00:00
var showPosition = new System.Drawing.Point((int)xOfScreenCenter, (int)yOfScreenCenter);
switch (type)
{
case ResultType.File:
var fileInfo = new FileInfo[] { new(path) };
new ShellContextMenu().ShowContextMenu(fileInfo, showPosition);
break;
case ResultType.Folder:
var folderInfo = new System.IO.DirectoryInfo[] { new(path) };
new ShellContextMenu().ShowContextMenu(folderInfo, showPosition);
break;
}
}
internal static Result CreateFolderResult(string title, string subtitle, string path, Query query, int score = 0, bool windowsIndexed = false)
{
return new Result
{
2023-03-08 12:20:33 +00:00
Title = title,
IcoPath = path,
SubTitle = subtitle,
2022-12-22 11:57:37 +00:00
AutoCompleteText = GetAutoCompleteText(title, query, path, ResultType.Folder),
2025-04-09 05:05:43 +00:00
TitleHighlightData = Context.API.FuzzySearch(query.Search, title).MatchData,
2022-12-05 03:46:11 +00:00
CopyText = path,
Preview = new Result.PreviewInfo
{
FilePath = path,
},
PreviewPanel = new Lazy<UserControl>(() => new PreviewPanel(Settings, path, ResultType.Folder)),
Action = c =>
{
if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt)
{
ShowNativeContextMenu(path, ResultType.Folder);
return false;
}
2023-03-03 12:36:36 +00:00
// open folder
if (c.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift))
{
try
{
2023-03-14 07:21:03 +00:00
OpenFolder(path);
return true;
}
catch (Exception ex)
{
Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error());
return false;
}
}
// Open containing folder
if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control)
{
try
{
Context.API.OpenDirectory(Path.GetDirectoryName(path), path);
return true;
}
catch (Exception ex)
{
Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error());
return false;
}
}
2023-03-14 07:32:47 +00:00
// If path search is disabled just open it in file manager
2023-03-29 07:19:55 +00:00
if (Settings.DefaultOpenFolderInFileManager || (!Settings.PathSearchKeywordEnabled && !Settings.SearchActionKeywordEnabled))
{
try
{
OpenFolder(path);
return true;
}
catch (Exception ex)
{
Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error());
return false;
}
}
else
{
// or make this folder the current query
Context.API.ChangeQuery(GetPathWithActionKeyword(path, ResultType.Folder, query.ActionKeyword));
}
return false;
},
2021-04-13 11:40:04 +00:00
Score = score,
TitleToolTip = Localize.plugin_explorer_plugin_ToolTipOpenDirectory(),
SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFolderMoreInfoTooltip(path) : path,
2023-03-03 10:30:25 +00:00
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, WindowsIndexed = windowsIndexed }
};
}
internal static Result CreateDriveSpaceDisplayResult(string path, string actionKeyword, int score)
{
return CreateDriveSpaceDisplayResult(path, actionKeyword, score, SearchManager.UseIndexSearch(path));
}
internal static Result CreateDriveSpaceDisplayResult(string path, string actionKeyword, bool windowsIndexed = false)
{
return CreateDriveSpaceDisplayResult(path, actionKeyword, 500, windowsIndexed);
}
private static Result CreateDriveSpaceDisplayResult(string path, string actionKeyword, int score, bool windowsIndexed = false)
{
var progressBarColor = "#26a0da";
var title = string.Empty; // hide title when use progress bar,
2022-10-10 16:11:48 +00:00
var driveLetter = path[..1].ToUpper();
DriveInfo drv = new DriveInfo(driveLetter);
2022-12-23 07:15:11 +00:00
var freespace = ToReadableSize(drv.AvailableFreeSpace, 2);
var totalspace = ToReadableSize(drv.TotalSize, 2);
var subtitle = Localize.plugin_explorer_diskfreespace(freespace, totalspace);
double usingSize = (Convert.ToDouble(drv.TotalSize) - Convert.ToDouble(drv.AvailableFreeSpace)) / Convert.ToDouble(drv.TotalSize) * 100;
int? progressValue = Convert.ToInt32(usingSize);
if (progressValue >= 90)
progressBarColor = "#da2626";
var tooltip = Settings.DisplayMoreInformationInToolTip
? GetVolumeMoreInfoTooltip(path, freespace, totalspace)
: path;
return new Result
{
Title = title,
SubTitle = subtitle,
AutoCompleteText = GetPathWithActionKeyword(path, ResultType.Folder, actionKeyword),
IcoPath = path,
Score = score,
ProgressBar = progressValue,
ProgressBarColor = progressBarColor,
2023-04-21 15:52:00 +00:00
Preview = new Result.PreviewInfo
{
FilePath = path,
},
2023-03-08 12:20:33 +00:00
Action = _ =>
{
2023-03-14 07:21:03 +00:00
OpenFolder(path);
return true;
},
TitleToolTip = tooltip,
SubTitleToolTip = tooltip,
2023-03-03 10:30:25 +00:00
ContextData = new SearchResult { Type = ResultType.Volume, FullPath = path, WindowsIndexed = windowsIndexed }
};
}
internal static string ToReadableSize(long sizeOnDrive, int pi)
{
var unitIndex = 0;
double readableSize = sizeOnDrive;
while (readableSize > 1024.0 && unitIndex < SizeUnits.Length - 1)
{
readableSize /= 1024.0;
unitIndex++;
}
var unit = SizeUnits[unitIndex] ?? "";
var returnStr = $"{Convert.ToInt32(readableSize)} {unit}";
if (unitIndex != 0)
{
2022-10-10 16:11:48 +00:00
returnStr = pi switch
{
1 => $"{readableSize:F1} {unit}",
2 => $"{readableSize:F2} {unit}",
3 => $"{readableSize:F3} {unit}",
_ => $"{Convert.ToInt32(readableSize)} {unit}"
2022-10-10 16:11:48 +00:00
};
}
return returnStr;
}
internal static Result CreateOpenCurrentFolderResult(string path, string actionKeyword, bool windowsIndexed = false)
{
2023-01-13 07:12:45 +00:00
// Path passed from PathSearchAsync ends with Constants.DirectorySeparator ('\'), need to remove the separator
// so it's consistent with folder results returned by index search which does not end with one
2023-01-13 07:12:45 +00:00
var folderPath = path.TrimEnd(Constants.DirectorySeparator);
return new Result
{
Title = Localize.plugin_explorer_openresultfolder(),
SubTitle = Localize.plugin_explorer_openresultfolder_subtitle(),
AutoCompleteText = GetPathWithActionKeyword(folderPath, ResultType.Folder, actionKeyword),
IcoPath = folderPath,
Score = 500,
CopyText = folderPath,
Action = c =>
{
if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt)
{
ShowNativeContextMenu(folderPath, ResultType.Folder);
return false;
}
2023-03-14 07:21:03 +00:00
OpenFolder(folderPath);
return true;
},
2023-03-03 10:30:25 +00:00
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = folderPath, WindowsIndexed = windowsIndexed }
};
}
internal static Result CreateFileResult(string filePath, Query query, int score = 0, bool windowsIndexed = false)
{
var isMedia = IsMedia(Path.GetExtension(filePath));
var title = Path.GetFileName(filePath) ?? string.Empty;
var directory = Path.GetDirectoryName(filePath) ?? string.Empty;
2022-12-22 11:57:37 +00:00
/* Preview Detail */
var result = new Result
{
2022-12-22 11:57:37 +00:00
Title = title,
SubTitle = directory,
IcoPath = filePath,
2023-04-21 15:52:00 +00:00
Preview = new Result.PreviewInfo
{
IsMedia = isMedia,
PreviewImagePath = isMedia ? filePath : null,
FilePath = filePath,
},
2022-12-22 11:57:37 +00:00
AutoCompleteText = GetAutoCompleteText(title, query, filePath, ResultType.File),
2025-04-09 05:05:43 +00:00
TitleHighlightData = Context.API.FuzzySearch(query.Search, title).MatchData,
2021-04-13 11:40:04 +00:00
Score = score,
2022-12-05 03:46:11 +00:00
CopyText = filePath,
PreviewPanel = new Lazy<UserControl>(() => new PreviewPanel(Settings, filePath, ResultType.File)),
Action = c =>
{
if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt)
{
ShowNativeContextMenu(filePath, ResultType.File);
return false;
}
try
{
if (c.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift))
{
OpenFile(filePath, Settings.UseLocationAsWorkingDir ? directory : string.Empty, true);
}
else if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control)
{
2023-03-14 07:21:03 +00:00
OpenFolder(filePath, filePath);
2023-03-14 07:32:47 +00:00
}
else
{
OpenFile(filePath, Settings.UseLocationAsWorkingDir ? directory : string.Empty);
}
}
catch (Exception ex)
{
Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_openfile_error());
}
return true;
},
TitleToolTip = Localize.plugin_explorer_plugin_ToolTipOpenContainingFolder(),
SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFileMoreInfoTooltip(filePath) : filePath,
2023-03-03 10:30:25 +00:00
ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, WindowsIndexed = windowsIndexed }
};
return result;
}
2022-12-05 08:01:20 +00:00
2023-03-14 07:21:03 +00:00
private static bool IsMedia(string extension)
{
2023-03-14 07:32:47 +00:00
if (string.IsNullOrEmpty(extension)) { return false; }
return MediaExtensions.Contains(extension.ToLowerInvariant());
}
private static void OpenFile(string filePath, string workingDir = "", bool asAdmin = false)
{
2023-03-14 07:21:03 +00:00
IncrementEverythingRunCounterIfNeeded(filePath);
2024-12-01 12:13:13 +00:00
FilesFolders.OpenFile(filePath, workingDir, asAdmin, (string str) => Context.API.ShowMsgBox(str));
}
2023-03-14 07:21:03 +00:00
private static void OpenFolder(string folderPath, string fileNameOrFilePath = null)
{
2023-03-14 07:21:03 +00:00
IncrementEverythingRunCounterIfNeeded(folderPath);
Context.API.OpenDirectory(folderPath, fileNameOrFilePath);
}
2023-03-14 07:21:03 +00:00
private static void IncrementEverythingRunCounterIfNeeded(string fileOrFolder)
{
if (Settings.EverythingEnabled && Settings.EverythingEnableRunCount)
2023-03-03 12:36:36 +00:00
_ = Task.Run(() => EverythingApi.IncrementRunCounterAsync(fileOrFolder));
}
private static string GetFileMoreInfoTooltip(string filePath)
{
try
{
var fileSize = PreviewPanel.GetFileSize(filePath);
var fileCreatedAt = PreviewPanel.GetFileCreatedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
var fileModifiedAt = PreviewPanel.GetFileLastModifiedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
return Localize.plugin_explorer_plugin_tooltip_more_info(filePath, fileSize, fileCreatedAt, fileModifiedAt, Environment.NewLine);
}
catch (Exception e)
{
Context.API.LogException(ClassName, $"Failed to load tooltip for {filePath}", e);
return filePath;
}
}
private static string GetFolderMoreInfoTooltip(string folderPath)
{
try
{
var folderSize = PreviewPanel.GetFolderSize(folderPath);
var folderCreatedAt = PreviewPanel.GetFolderCreatedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
var folderModifiedAt = PreviewPanel.GetFolderLastModifiedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
return Localize.plugin_explorer_plugin_tooltip_more_info(folderPath, folderSize, folderCreatedAt, folderModifiedAt, Environment.NewLine);
}
catch (Exception e)
{
Context.API.LogException(ClassName, $"Failed to load tooltip for {folderPath}", e);
return folderPath;
}
}
private static string GetVolumeMoreInfoTooltip(string volumePath, string freespace, string totalspace)
{
return Localize.plugin_explorer_plugin_tooltip_more_info_volume(volumePath, freespace, totalspace, Environment.NewLine);
}
private static readonly string[] MediaExtensions =
{
".jpg", ".png", ".avi", ".mkv", ".bmp", ".gif", ".wmv", ".mp3", ".flac", ".mp4",
".m4a", ".m4v", ".heic", ".mov", ".flv", ".webm"
};
}
2021-01-26 09:01:12 +00:00
public enum ResultType
{
Volume,
Folder,
File
}
}