diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml
index 27d1bfeab..7d0a553a4 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml
@@ -46,6 +46,7 @@
Shell Path
Index Search Excluded Paths
Use search result's location as the working directory of the executable
+ Display more information like size and age in tooltips
Hit Enter to open folder in Default File Manager
Use Index Search For Path Search
Indexing Options
@@ -82,6 +83,9 @@
Ctrl + Enter to open the directory
Ctrl + Enter to open the containing folder
+ {0}{4}Size: {1}{4}Date created: {2}{4}Date modified: {3}
+ Unknown
+ {0}{3}Space free: {1}{3}Total size: {2}
Copy path
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
index 5c4accdc0..6bbdcbe0a 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
@@ -14,6 +14,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
public static class ResultManager
{
+ private static readonly string ClassName = nameof(ResultManager);
+
private static readonly string[] SizeUnits = { "B", "KB", "MB", "GB", "TB" };
private static PluginInitContext Context;
private static Settings Settings { get; set; }
@@ -99,10 +101,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
AutoCompleteText = GetAutoCompleteText(title, query, path, ResultType.Folder),
TitleHighlightData = Context.API.FuzzySearch(query.Search, title).MatchData,
CopyText = path,
- Preview = new Result.PreviewInfo
- {
- FilePath = path,
- },
+ PreviewPanel = new Lazy(() => new PreviewPanel(Settings, path, ResultType.Folder)),
Action = c =>
{
if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt)
@@ -163,7 +162,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
},
Score = score,
TitleToolTip = Main.Context.API.GetTranslation("plugin_explorer_plugin_ToolTipOpenDirectory"),
- SubTitleToolTip = path,
+ SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFolderMoreInfoTooltip(path) : path,
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, WindowsIndexed = windowsIndexed }
};
}
@@ -184,6 +183,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search
if (progressValue >= 90)
progressBarColor = "#da2626";
+ var tooltip = Settings.DisplayMoreInformationInToolTip
+ ? GetVolumeMoreInfoTooltip(path, freespace, totalspace)
+ : path;
+
return new Result
{
Title = title,
@@ -202,8 +205,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
OpenFolder(path);
return true;
},
- TitleToolTip = path,
- SubTitleToolTip = path,
+ TitleToolTip = tooltip,
+ SubTitleToolTip = tooltip,
ContextData = new SearchResult { Type = ResultType.Volume, FullPath = path, WindowsIndexed = windowsIndexed }
};
}
@@ -269,7 +272,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search
bool isMedia = IsMedia(Path.GetExtension(filePath));
var title = Path.GetFileName(filePath);
-
/* Preview Detail */
var result = new Result
@@ -287,7 +289,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
TitleHighlightData = Context.API.FuzzySearch(query.Search, title).MatchData,
Score = score,
CopyText = filePath,
- PreviewPanel = new Lazy(() => new PreviewPanel(Settings, filePath)),
+ PreviewPanel = new Lazy(() => new PreviewPanel(Settings, filePath, ResultType.File)),
Action = c =>
{
if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt)
@@ -318,7 +320,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return true;
},
TitleToolTip = Main.Context.API.GetTranslation("plugin_explorer_plugin_ToolTipOpenContainingFolder"),
- SubTitleToolTip = filePath,
+ SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFileMoreInfoTooltip(filePath) : filePath,
ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, WindowsIndexed = windowsIndexed }
};
return result;
@@ -349,6 +351,46 @@ namespace Flow.Launcher.Plugin.Explorer.Search
_ = 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 string.Format(Context.API.GetTranslation("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 string.Format(Context.API.GetTranslation("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 string.Format(Context.API.GetTranslation("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" };
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs
index 4f83fc72e..77540f3a8 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs
@@ -37,6 +37,8 @@ namespace Flow.Launcher.Plugin.Explorer
public bool DefaultOpenFolderInFileManager { get; set; } = false;
+ public bool DisplayMoreInformationInToolTip { get; set; } = false;
+
public string SearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign;
public bool SearchActionKeywordEnabled { get; set; } = true;
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
index 59d3a5cfd..5aa6a13be 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
@@ -244,6 +244,21 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
"yyyy-MM-dd",
"yyyy-MM-dd ddd",
"yyyy-MM-dd, dddd",
+ "dd/MMM/yyyy",
+ "dd/MMM/yyyy ddd",
+ "dd/MMM/yyyy, dddd",
+ "dd-MMM-yyyy",
+ "dd-MMM-yyyy ddd",
+ "dd-MMM-yyyy, dddd",
+ "dd.MMM.yyyy",
+ "dd.MMM.yyyy ddd",
+ "dd.MMM.yyyy, dddd",
+ "MMM/dd/yyyy",
+ "MMM/dd/yyyy ddd",
+ "MMM/dd/yyyy, dddd",
+ "yyyy-MMM-dd",
+ "yyyy-MMM-dd ddd",
+ "yyyy-MMM-dd, dddd",
};
#endregion
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml
index a05d43041..8f4b4d862 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml
@@ -205,6 +205,7 @@
+
@@ -228,16 +229,25 @@
Content="{DynamicResource plugin_explorer_default_open_in_file_manager}"
IsChecked="{Binding Settings.DefaultOpenFolderInFileManager}" />
-
+
+
@@ -256,7 +266,7 @@
@@ -283,7 +293,7 @@
@@ -310,14 +320,14 @@
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs
index e1a957199..5714b0d0f 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs
@@ -2,7 +2,9 @@
using System.ComponentModel;
using System.Globalization;
using System.IO;
+using System.Linq;
using System.Runtime.CompilerServices;
+using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
@@ -16,8 +18,10 @@ namespace Flow.Launcher.Plugin.Explorer.Views;
public partial class PreviewPanel : UserControl, INotifyPropertyChanged
{
+ private static readonly string ClassName = nameof(PreviewPanel);
+
private string FilePath { get; }
- public string FileSize { get; } = "";
+ public string FileSize { get; private set; } = Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
public string CreatedAt { get; } = "";
public string LastModifiedAt { get; } = "";
private ImageSource _previewImage = new BitmapImage();
@@ -50,7 +54,7 @@ public partial class PreviewPanel : UserControl, INotifyPropertyChanged
? Visibility.Visible
: Visibility.Collapsed;
- public PreviewPanel(Settings settings, string filePath)
+ public PreviewPanel(Settings settings, string filePath, ResultType type)
{
InitializeComponent();
@@ -60,33 +64,32 @@ public partial class PreviewPanel : UserControl, INotifyPropertyChanged
if (Settings.ShowFileSizeInPreviewPanel)
{
- var fileSize = new FileInfo(filePath).Length;
- FileSize = ResultManager.ToReadableSize(fileSize, 2);
+ if (type == ResultType.File)
+ {
+ FileSize = GetFileSize(filePath);
+ }
+ else
+ {
+ _ = Task.Run(() =>
+ {
+ FileSize = GetFolderSize(filePath);
+ OnPropertyChanged(nameof(FileSize));
+ }).ConfigureAwait(false);
+ }
}
if (Settings.ShowCreatedDateInPreviewPanel)
{
- DateTime createdDate = File.GetCreationTime(filePath);
- string formattedDate = createdDate.ToString(
- $"{Settings.PreviewPanelDateFormat} {Settings.PreviewPanelTimeFormat}",
- CultureInfo.CurrentCulture
- );
-
- string result = formattedDate;
- if (Settings.ShowFileAgeInPreviewPanel) result = $"{GetFileAge(createdDate)} - {formattedDate}";
- CreatedAt = result;
+ CreatedAt = type == ResultType.File ?
+ GetFileCreatedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel) :
+ GetFolderCreatedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
}
if (Settings.ShowModifiedDateInPreviewPanel)
{
- DateTime lastModifiedDate = File.GetLastWriteTime(filePath);
- string formattedDate = lastModifiedDate.ToString(
- $"{Settings.PreviewPanelDateFormat} {Settings.PreviewPanelTimeFormat}",
- CultureInfo.CurrentCulture
- );
- string result = formattedDate;
- if (Settings.ShowFileAgeInPreviewPanel) result = $"{GetFileAge(lastModifiedDate)} - {formattedDate}";
- LastModifiedAt = result;
+ LastModifiedAt = type == ResultType.File ?
+ GetFileLastModifiedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel) :
+ GetFolderLastModifiedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
}
_ = LoadImageAsync();
@@ -96,7 +99,211 @@ public partial class PreviewPanel : UserControl, INotifyPropertyChanged
{
PreviewImage = await Main.Context.API.LoadImageAsync(FilePath, true).ConfigureAwait(false);
}
-
+
+ public static string GetFileSize(string filePath)
+ {
+ try
+ {
+ var fileInfo = new FileInfo(filePath);
+ return ResultManager.ToReadableSize(fileInfo.Length, 2);
+ }
+ catch (FileNotFoundException)
+ {
+ Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (Exception e)
+ {
+ Main.Context.API.LogException(ClassName, $"Failed to get file size for {filePath}", e);
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ }
+
+ public static string GetFileCreatedAt(string filePath, string previewPanelDateFormat, string previewPanelTimeFormat, bool showFileAgeInPreviewPanel)
+ {
+ try
+ {
+ var createdDate = File.GetCreationTime(filePath);
+ var formattedDate = createdDate.ToString(
+ $"{previewPanelDateFormat} {previewPanelTimeFormat}",
+ CultureInfo.CurrentCulture
+ );
+
+ var result = formattedDate;
+ if (showFileAgeInPreviewPanel) result = $"{GetFileAge(createdDate)} - {formattedDate}";
+ return result;
+ }
+ catch (FileNotFoundException)
+ {
+ Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (Exception e)
+ {
+ Main.Context.API.LogException(ClassName, $"Failed to get file created date for {filePath}", e);
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ }
+
+ public static string GetFileLastModifiedAt(string filePath, string previewPanelDateFormat, string previewPanelTimeFormat, bool showFileAgeInPreviewPanel)
+ {
+ try
+ {
+ var lastModifiedDate = File.GetLastWriteTime(filePath);
+ var formattedDate = lastModifiedDate.ToString(
+ $"{previewPanelDateFormat} {previewPanelTimeFormat}",
+ CultureInfo.CurrentCulture
+ );
+
+ var result = formattedDate;
+ if (showFileAgeInPreviewPanel) result = $"{GetFileAge(lastModifiedDate)} - {formattedDate}";
+ return result;
+ }
+ catch (FileNotFoundException)
+ {
+ Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (Exception e)
+ {
+ Main.Context.API.LogException(ClassName, $"Failed to get file modified date for {filePath}", e);
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ }
+
+ public static string GetFolderSize(string folderPath)
+ {
+ using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
+
+ try
+ {
+ // Use parallel enumeration for better performance
+ var directoryInfo = new DirectoryInfo(folderPath);
+ long size = directoryInfo.EnumerateFiles("*", SearchOption.AllDirectories)
+ .AsParallel()
+ .WithCancellation(timeoutCts.Token)
+ .Sum(file => file.Length);
+
+ return ResultManager.ToReadableSize(size, 2);
+ }
+ catch (FileNotFoundException)
+ {
+ Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (OperationCanceledException)
+ {
+ Main.Context.API.LogError(ClassName, $"Operation timed out while calculating folder size for {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ // For parallel operations, AggregateException may be thrown if any of the tasks fail
+ catch (AggregateException ae)
+ {
+ switch (ae.InnerException)
+ {
+ case FileNotFoundException:
+ Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ case UnauthorizedAccessException:
+ Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ case OperationCanceledException:
+ Main.Context.API.LogError(ClassName, $"Operation timed out while calculating folder size for {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ default:
+ Main.Context.API.LogException(ClassName, $"Failed to get folder size for {folderPath}", ae);
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ }
+ catch (Exception e)
+ {
+ Main.Context.API.LogException(ClassName, $"Failed to get folder size for {folderPath}", e);
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ }
+
+ public static string GetFolderCreatedAt(string folderPath, string previewPanelDateFormat, string previewPanelTimeFormat, bool showFileAgeInPreviewPanel)
+ {
+ try
+ {
+ var createdDate = Directory.GetCreationTime(folderPath);
+ var formattedDate = createdDate.ToString(
+ $"{previewPanelDateFormat} {previewPanelTimeFormat}",
+ CultureInfo.CurrentCulture
+ );
+
+ var result = formattedDate;
+ if (showFileAgeInPreviewPanel) result = $"{GetFileAge(createdDate)} - {formattedDate}";
+ return result;
+ }
+ catch (FileNotFoundException)
+ {
+ Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (Exception e)
+ {
+ Main.Context.API.LogException(ClassName, $"Failed to get folder created date for {folderPath}", e);
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ }
+
+ public static string GetFolderLastModifiedAt(string folderPath, string previewPanelDateFormat, string previewPanelTimeFormat, bool showFileAgeInPreviewPanel)
+ {
+ try
+ {
+ var lastModifiedDate = Directory.GetLastWriteTime(folderPath);
+ var formattedDate = lastModifiedDate.ToString(
+ $"{previewPanelDateFormat} {previewPanelTimeFormat}",
+ CultureInfo.CurrentCulture
+ );
+
+ var result = formattedDate;
+ if (showFileAgeInPreviewPanel) result = $"{GetFileAge(lastModifiedDate)} - {formattedDate}";
+ return result;
+ }
+ catch (FileNotFoundException)
+ {
+ Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (Exception e)
+ {
+ Main.Context.API.LogException(ClassName, $"Failed to get folder modified date for {folderPath}", e);
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ }
+
private static string GetFileAge(DateTime fileDateTime)
{
var now = DateTime.Now;