Change signature back to string to accommodate async method.

Port open Windows Context Menu feature
This commit is contained in:
Hongtao Zhang 2022-09-24 14:23:59 -05:00
parent 9d294ed71a
commit 2c3df3f4db
No known key found for this signature in database
GPG key ID: 75F655B91C7AC9BB
12 changed files with 1745 additions and 80 deletions

View file

@ -9,6 +9,8 @@ using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin.Explorer.Search;
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Input;
using MessageBox = System.Windows.Forms.MessageBox;
using MessageBoxIcon = System.Windows.Forms.MessageBoxIcon;
using MessageBoxButton = System.Windows.Forms.MessageBoxButtons;
@ -50,14 +52,18 @@ namespace Flow.Launcher.Plugin.Explorer
contextMenus.Add(CreateOpenWindowsIndexingOptions());
if (record.ShowIndexState)
contextMenus.Add(new Result {Title = "From index search: " + (record.WindowsIndexed ? "Yes" : "No"),
SubTitle = "Location: " + record.FullPath,
Score = 501, IcoPath = Constants.IndexImagePath});
contextMenus.Add(new Result
{
Title = "From index search: " + (record.WindowsIndexed ? "Yes" : "No"),
SubTitle = "Location: " + record.FullPath,
Score = 501,
IcoPath = Constants.IndexImagePath
});
var icoPath = (record.Type == ResultType.File) ? Constants.FileImagePath : Constants.FolderImagePath;
var fileOrFolder = (record.Type == ResultType.File) ? "file" : "folder";
if (!Settings.QuickAccessLinks.Any(x => x.Path == record.FullPath))
if (Settings.QuickAccessLinks.All(x => x.Path != record.FullPath))
{
contextMenus.Add(new Result
{
@ -65,13 +71,16 @@ namespace Flow.Launcher.Plugin.Explorer
SubTitle = string.Format(Context.API.GetTranslation("plugin_explorer_add_to_quickaccess_subtitle"), fileOrFolder),
Action = (context) =>
{
Settings.QuickAccessLinks.Add(new AccessLink { Path = record.FullPath, Type = record.Type });
Settings.QuickAccessLinks.Add(new AccessLink
{
Path = record.FullPath, Type = record.Type
});
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess"),
string.Format(
Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess_detail"),
fileOrFolder),
Constants.ExplorerIconImageFullPath);
string.Format(
Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess_detail"),
fileOrFolder),
Constants.ExplorerIconImageFullPath);
ViewModel.Save();
@ -93,10 +102,10 @@ namespace Flow.Launcher.Plugin.Explorer
Settings.QuickAccessLinks.Remove(Settings.QuickAccessLinks.FirstOrDefault(x => x.Path == record.FullPath));
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess"),
string.Format(
Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess_detail"),
fileOrFolder),
Constants.ExplorerIconImageFullPath);
string.Format(
Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess_detail"),
fileOrFolder),
Constants.ExplorerIconImageFullPath);
ViewModel.Save();
@ -107,12 +116,12 @@ namespace Flow.Launcher.Plugin.Explorer
IcoPath = Constants.RemoveQuickAccessImagePath
});
}
contextMenus.Add(new Result
{
Title = Context.API.GetTranslation("plugin_explorer_copypath"),
SubTitle = $"Copy the current {fileOrFolder} path to clipboard",
Action = (context) =>
Action = _ =>
{
try
{
@ -134,11 +143,14 @@ namespace Flow.Launcher.Plugin.Explorer
{
Title = Context.API.GetTranslation("plugin_explorer_copyfilefolder") + $" {fileOrFolder}",
SubTitle = $"Copy the {fileOrFolder} to clipboard",
Action = (context) =>
Action = _ =>
{
try
{
Clipboard.SetFileDropList(new System.Collections.Specialized.StringCollection { record.FullPath });
Clipboard.SetFileDropList(new System.Collections.Specialized.StringCollection
{
record.FullPath
});
return true;
}
catch (Exception e)
@ -153,7 +165,8 @@ namespace Flow.Launcher.Plugin.Explorer
IcoPath = icoPath
});
if (record.Type == ResultType.File || record.Type == ResultType.Folder)
if (record.Type is ResultType.File or ResultType.Folder)
contextMenus.Add(new Result
{
Title = Context.API.GetTranslation("plugin_explorer_deletefilefolder") + $" {fileOrFolder}",
@ -163,10 +176,10 @@ namespace Flow.Launcher.Plugin.Explorer
try
{
if (MessageBox.Show(
string.Format(Context.API.GetTranslation("plugin_explorer_deletefilefolderconfirm"),fileOrFolder),
string.Empty,
MessageBoxButton.YesNo,
MessageBoxIcon.Warning)
string.Format(Context.API.GetTranslation("plugin_explorer_deletefilefolderconfirm"), fileOrFolder),
string.Empty,
MessageBoxButton.YesNo,
MessageBoxIcon.Warning)
== DialogResult.No)
return false;
@ -175,11 +188,11 @@ namespace Flow.Launcher.Plugin.Explorer
else
Directory.Delete(record.FullPath, true);
Task.Run(() =>
_ = Task.Run(() =>
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_deletefilefoldersuccess"),
string.Format(Context.API.GetTranslation("plugin_explorer_deletefilefoldersuccess_detail"), fileOrFolder),
Constants.ExplorerIconImageFullPath);
string.Format(Context.API.GetTranslation("plugin_explorer_deletefilefoldersuccess_detail"), fileOrFolder),
Constants.ExplorerIconImageFullPath);
});
}
catch (Exception e)
@ -195,6 +208,52 @@ namespace Flow.Launcher.Plugin.Explorer
IcoPath = Constants.DeleteFileFolderImagePath
});
if (record.Type is not ResultType.Volume)
{
contextMenus.Add(new Result()
{
Title = Context.API.GetTranslation("plugin_explorer_show_contextmenu_title"),
IcoPath = Constants.ShowContextMenuImagePath,
Glyph = new GlyphInfo("Segoe Fluent Icons", "\uE700"),
Action = _ =>
{
if (record.Type is ResultType.Volume)
return false;
var screenWithMouseCursor = System.Windows.Forms.Screen.FromPoint(System.Windows.Forms.Cursor.Position);
var xOfScreenCenter = screenWithMouseCursor.WorkingArea.Left + screenWithMouseCursor.WorkingArea.Width / 2;
var yOfScreenCenter = screenWithMouseCursor.WorkingArea.Top + screenWithMouseCursor.WorkingArea.Height / 2;
var showPosition = new System.Drawing.Point(xOfScreenCenter, yOfScreenCenter);
switch (record.Type)
{
case ResultType.File:
{
var fileInfos = new FileInfo[]
{
new(record.FullPath)
};
new Peter.ShellContextMenu().ShowContextMenu(fileInfos, showPosition);
break;
}
case ResultType.Folder:
{
var directoryInfos = new DirectoryInfo[]
{
new(record.FullPath)
};
new Peter.ShellContextMenu().ShowContextMenu(directoryInfos, showPosition);
break;
}
}
return false;
}
});
}
if (record.Type == ResultType.File && CanRunAsDifferentUser(record.FullPath))
contextMenus.Add(new Result
{
@ -248,8 +307,8 @@ namespace Flow.Launcher.Plugin.Explorer
};
}
private Result CreateOpenWithEditorResult(SearchResult record)
{
string editorPath = Settings.EditorPath;
@ -277,7 +336,7 @@ namespace Flow.Launcher.Plugin.Explorer
IcoPath = Constants.FileImagePath
};
}
private Result CreateOpenWithShellResult(SearchResult record)
{
string shellPath = Settings.ShellPath;
@ -293,8 +352,7 @@ namespace Flow.Launcher.Plugin.Explorer
{
Process.Start(new ProcessStartInfo()
{
FileName = shellPath,
WorkingDirectory = record.FullPath
FileName = shellPath, WorkingDirectory = record.FullPath
});
return true;
}
@ -318,14 +376,17 @@ namespace Flow.Launcher.Plugin.Explorer
SubTitle = Context.API.GetTranslation("plugin_explorer_path") + " " + record.FullPath,
Action = _ =>
{
if(!Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x.Path == record.FullPath))
Settings.IndexSearchExcludedSubdirectoryPaths.Add(new AccessLink { Path = record.FullPath });
if (!Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x.Path == record.FullPath))
Settings.IndexSearchExcludedSubdirectoryPaths.Add(new AccessLink
{
Path = record.FullPath
});
Task.Run(() =>
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_excludedfromindexsearch_msg"),
Context.API.GetTranslation("plugin_explorer_path") +
" " + record.FullPath, Constants.ExplorerIconImageFullPath);
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_excludedfromindexsearch_msg"),
Context.API.GetTranslation("plugin_explorer_path") +
" " + record.FullPath, Constants.ExplorerIconImageFullPath);
// so the new path can be persisted to storage and not wait till next ViewModel save.
Context.API.SaveAppAllSettings();
@ -348,11 +409,11 @@ namespace Flow.Launcher.Plugin.Explorer
try
{
var psi = new ProcessStartInfo
{
FileName = "control.exe",
UseShellExecute = true,
Arguments = "srchadmin.dll"
};
{
FileName = "control.exe",
UseShellExecute = true,
Arguments = "srchadmin.dll"
};
Process.Start(psi);
return true;

File diff suppressed because it is too large Load diff

View file

@ -80,5 +80,6 @@
<system:String x:Key="plugin_explorer_contextmenu_remove_titletooltip">Remove from Quick Access</system:String>
<system:String x:Key="plugin_explorer_remove_from_quickaccess_title">Remove from Quick Access</system:String>
<system:String x:Key="plugin_explorer_remove_from_quickaccess_subtitle">Remove the current {0} from Quick Access</system:String>
<system:String x:Key="plugin_explorer_show_contextmenu_title">Show Windows Context Menu</system:String>
</ResourceDictionary>

View file

@ -17,6 +17,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
internal const string IndexingOptionsIconImagePath = "Images\\windowsindexingoptions.png";
internal const string QuickAccessImagePath = "Images\\quickaccess.png";
internal const string RemoveQuickAccessImagePath = "Images\\removequickaccess.png";
internal const string ShowContextMenuImagePath = "Images\\contextmenu.png";
internal const string ToolTipOpenDirectory = "Ctrl + Enter to open the directory";

View file

@ -119,7 +119,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
var builder = new StringBuilder();
builder.Append(option.Keyword);
if (!option.ParentPath.IsWhiteSpace())
if (!string.IsNullOrWhiteSpace(option.ParentPath))
{
builder.Append($" {(option.IsRecursive ? "" : "parent:")}\"{option.ParentPath}\"");
}

View file

@ -18,14 +18,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
}
public IAsyncEnumerable<SearchResult> SearchAsync(ReadOnlySpan<char> search, CancellationToken token)
public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token)
{
return EverythingApi.SearchAsync(
new EverythingSearchOption(search, Settings.SortOption),
token);
}
public IAsyncEnumerable<SearchResult> ContentSearchAsync(ReadOnlySpan<char> plainSearch,
ReadOnlySpan<char> contentSearch, CancellationToken token)
public IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch,
string contentSearch, CancellationToken token)
{
if (!Settings.EnableEverythingContentSearch)
{
@ -40,7 +40,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
contentSearch),
token);
}
public IAsyncEnumerable<SearchResult> EnumerateAsync(ReadOnlySpan<char> path, ReadOnlySpan<char> search, bool recursive, CancellationToken token)
public IAsyncEnumerable<SearchResult> EnumerateAsync(string path, string search, bool recursive, CancellationToken token)
{
return EverythingApi.SearchAsync(
new EverythingSearchOption(search,

View file

@ -3,11 +3,11 @@ using Flow.Launcher.Plugin.Everything.Everything;
namespace Flow.Launcher.Plugin.Explorer.Search.Everything
{
public record struct EverythingSearchOption(ReadOnlySpan<char> Keyword,
public record struct EverythingSearchOption(string Keyword,
SortOption SortOption,
bool IsContentSearch = false,
ReadOnlySpan<char> ContentSearchKeyword = default,
ReadOnlySpan<char> ParentPath = default,
string ContentSearchKeyword = default,
string ParentPath = default,
bool IsRecursive = true,
int Offset = 0,
int MaxCount = 100);

View file

@ -6,6 +6,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.IProvider
{
public interface IContentIndexProvider
{
public IAsyncEnumerable<SearchResult> ContentSearchAsync(ReadOnlySpan<char> plainSearch, ReadOnlySpan<char> contentSearch, CancellationToken token = default);
public IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token = default);
}
}

View file

@ -6,6 +6,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.IProvider
{
public interface IIndexProvider
{
public IAsyncEnumerable<SearchResult> SearchAsync(ReadOnlySpan<char> search, CancellationToken token);
public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token);
}
}

View file

@ -6,6 +6,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.IProvider
{
public interface IPathIndexProvider
{
public IAsyncEnumerable<SearchResult> EnumerateAsync(ReadOnlySpan<char> path, ReadOnlySpan<char> search, bool recursive, CancellationToken token);
public IAsyncEnumerable<SearchResult> EnumerateAsync(string path, string search, bool recursive, CancellationToken token);
}
}

View file

@ -2,6 +2,7 @@
using Flow.Launcher.Plugin.SharedCommands;
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@ -92,15 +93,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search
internal static Result CreateDriveSpaceDisplayResult(string path, bool windowsIndexed = false)
{
var progressBarColor = "#26a0da";
int? progressValue = null;
var title = string.Empty; // hide title when use progress bar,
var driveLetter = path.Substring(0, 1).ToUpper();
var driveName = driveLetter + ":\\";
DriveInfo drv = new DriveInfo(driveLetter);
var subtitle = toReadableSize(drv.AvailableFreeSpace, 2) + " free of " + toReadableSize(drv.TotalSize, 2);
double UsingSize = (Convert.ToDouble(drv.TotalSize) - Convert.ToDouble(drv.AvailableFreeSpace)) / Convert.ToDouble(drv.TotalSize) * 100;
double usingSize = (Convert.ToDouble(drv.TotalSize) - Convert.ToDouble(drv.AvailableFreeSpace)) / Convert.ToDouble(drv.TotalSize) * 100;
progressValue = Convert.ToInt32(UsingSize);
int? progressValue = Convert.ToInt32(usingSize);
if (progressValue >= 90)
progressBarColor = "#da2626";
@ -123,7 +123,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
SubTitleToolTip = path,
ContextData = new SearchResult
{
Type = ResultType.Folder,
Type = ResultType.Volume,
FullPath = path,
ShowIndexState = true,
WindowsIndexed = windowsIndexed
@ -177,25 +177,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search
internal static Result CreateOpenCurrentFolderResult(string path, bool windowsIndexed = false)
{
var retrievedDirectoryPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path);
var folderName = retrievedDirectoryPath.TrimEnd(Constants.DirectorySeperator).Split(new[]
var folderName = path.TrimEnd(Constants.DirectorySeperator).Split(new[]
{
Path.DirectorySeparatorChar
}, StringSplitOptions.None).Last();
if (retrievedDirectoryPath.EndsWith(":\\"))
{
var driveLetter = path.Substring(0, 1).ToUpper();
folderName = driveLetter + " drive";
}
var title = "Open current directory";
if (retrievedDirectoryPath != path)
title = "Open " + folderName;
var title = $"Open {folderName}";
var subtitleFolderName = folderName;
// ie. max characters can be displayed without subtitle cutting off: "Program Files (x86)"
@ -207,20 +195,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search
Title = title,
SubTitle = $"Use > to search within {subtitleFolderName}, " +
$"* to search for file extensions or >* to combine both searches.",
AutoCompleteText = GetPathWithActionKeyword(retrievedDirectoryPath, ResultType.Folder),
IcoPath = retrievedDirectoryPath,
AutoCompleteText = GetPathWithActionKeyword(path, ResultType.Folder),
IcoPath = path,
Score = 500,
Action = c =>
Action = _ =>
{
Context.API.OpenDirectory(retrievedDirectoryPath);
Context.API.OpenDirectory(path);
return true;
},
TitleToolTip = retrievedDirectoryPath,
SubTitleToolTip = retrievedDirectoryPath,
ContextData = new SearchResult
{
Type = ResultType.Folder,
FullPath = retrievedDirectoryPath,
FullPath = path,
ShowIndexState = true,
WindowsIndexed = windowsIndexed
}

View file

@ -54,15 +54,15 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
queryConstructor.Directory(path, search, recursive),
token);
}
public IAsyncEnumerable<SearchResult> SearchAsync(ReadOnlySpan<char> search, CancellationToken token)
public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token)
{
return WindowsIndexFilesAndFoldersSearchAsync(search, token: token);
}
public IAsyncEnumerable<SearchResult> ContentSearchAsync(ReadOnlySpan<char> plainSearch, ReadOnlySpan<char> contentSearch, CancellationToken token)
public IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token)
{
return WindowsIndexFileContentSearchAsync(contentSearch, token);
}
public IAsyncEnumerable<SearchResult> EnumerateAsync(ReadOnlySpan<char> path, ReadOnlySpan<char> search, bool recursive, CancellationToken token)
public IAsyncEnumerable<SearchResult> EnumerateAsync(string path, string search, bool recursive, CancellationToken token)
{
return WindowsIndexTopLevelFolderSearchAsync(search, path, recursive, token);
}