Merge branch 'dev' into ImagePreview
|
|
@ -183,6 +183,13 @@ namespace Flow.Launcher.Plugin
|
|||
/// <param name="oldActionKeyword">The actionkeyword that is supposed to be removed</param>
|
||||
void RemoveActionKeyword(string pluginId, string oldActionKeyword);
|
||||
|
||||
/// <summary>
|
||||
/// Check whether specific ActionKeyword is assigned to any of the plugin
|
||||
/// </summary>
|
||||
/// <param name="actionKeyword">The actionkeyword for checking</param>
|
||||
/// <returns>True if the actionkeyword is already assigned, False otherwise</returns>
|
||||
bool ActionKeywordAssigned(string actionKeyword);
|
||||
|
||||
/// <summary>
|
||||
/// Log debug message
|
||||
/// Message will only be logged in Debug mode
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Flow.Launcher.Plugin.SharedCommands;
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
|
@ -32,12 +33,11 @@ namespace Flow.Launcher.Test.Plugins
|
|||
{
|
||||
new Result
|
||||
{
|
||||
Title="Result 1"
|
||||
Title = "Result 1"
|
||||
},
|
||||
|
||||
new Result
|
||||
{
|
||||
Title="Result 2"
|
||||
Title = "Result 2"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -46,15 +46,13 @@ namespace Flow.Launcher.Test.Plugins
|
|||
|
||||
private bool PreviousLocationNotExistReturnsFalse(string dummyString) => false;
|
||||
|
||||
[SupportedOSPlatform("windows7.0")]
|
||||
[TestCase("C:\\SomeFolder\\", "directory='file:C:\\SomeFolder\\'")]
|
||||
public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestrictionsShouldUseDirectoryString(string path, string expectedString)
|
||||
{
|
||||
// Given
|
||||
var queryConstructor = new QueryConstructor(new Settings());
|
||||
|
||||
// When
|
||||
var folderPath = path;
|
||||
var result = queryConstructor.QueryWhereRestrictionsForTopLevelDirectorySearch(folderPath);
|
||||
var result = QueryConstructor.TopLevelDirectoryConstraint(folderPath);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(result == expectedString,
|
||||
|
|
@ -62,6 +60,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
$"Actual: {result}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows7.0")]
|
||||
[TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\' ORDER BY System.FileName")]
|
||||
[TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\' ORDER BY System.FileName")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString)
|
||||
|
|
@ -70,130 +69,68 @@ namespace Flow.Launcher.Test.Plugins
|
|||
var queryConstructor = new QueryConstructor(new Settings());
|
||||
|
||||
//When
|
||||
var queryString = queryConstructor.QueryForTopLevelDirectorySearch(folderPath);
|
||||
var queryString = queryConstructor.Directory(folderPath);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(queryString == expectedString,
|
||||
Assert.IsTrue(queryString.Replace(" ", " ") == expectedString.Replace(" ", " "),
|
||||
$"Expected string: {expectedString}{Environment.NewLine} " +
|
||||
$"Actual string was: {queryString}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
[TestCase("C:\\SomeFolder\\flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " +
|
||||
"FROM SystemIndex WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
|
||||
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033))" +
|
||||
" AND directory='file:C:\\SomeFolder' ORDER BY System.FileName")]
|
||||
[SupportedOSPlatform("windows7.0")]
|
||||
[TestCase("C:\\SomeFolder", "flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType" +
|
||||
" FROM SystemIndex WHERE directory='file:C:\\SomeFolder'" +
|
||||
" AND (System.FileName LIKE 'flow.launcher.sln%' OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"'))" +
|
||||
" ORDER BY System.FileName")]
|
||||
public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString(
|
||||
string userSearchString, string expectedString)
|
||||
string folderPath, string userSearchString, string expectedString)
|
||||
{
|
||||
// Given
|
||||
var queryConstructor = new QueryConstructor(new Settings());
|
||||
|
||||
//When
|
||||
var queryString = queryConstructor.QueryForTopLevelDirectorySearch(userSearchString);
|
||||
var queryString = queryConstructor.Directory(folderPath, userSearchString);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(queryString == expectedString,
|
||||
$"Expected string: {expectedString}{Environment.NewLine} " +
|
||||
$"Actual string was: {queryString}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
[TestCase("C:\\SomeFolder\\SomeApp", "(System.FileName LIKE 'SomeApp%' " +
|
||||
"OR CONTAINS(System.FileName,'\"SomeApp*\"',1033))" +
|
||||
" AND directory='file:C:\\SomeFolder'")]
|
||||
public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryWhereRestrictionsShouldUseDirectoryString(
|
||||
string userSearchString, string expectedString)
|
||||
{
|
||||
// Given
|
||||
var queryConstructor = new QueryConstructor(new Settings());
|
||||
|
||||
//When
|
||||
var queryString = queryConstructor.QueryWhereRestrictionsForTopLevelDirectorySearch(userSearchString);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(queryString == expectedString,
|
||||
$"Expected string: {expectedString}{Environment.NewLine} " +
|
||||
$"Actual string was: {queryString}{Environment.NewLine}");
|
||||
Assert.AreEqual(expectedString, queryString);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows7.0")]
|
||||
[TestCase("scope='file:'")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString)
|
||||
{
|
||||
//When
|
||||
var resultString = QueryConstructor.QueryWhereRestrictionsForAllFilesAndFoldersSearch;
|
||||
const string resultString = QueryConstructor.RestrictionsForAllFilesAndFoldersSearch;
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(resultString == expectedString,
|
||||
$"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
|
||||
$"Actual string was: {resultString}{Environment.NewLine}");
|
||||
Assert.AreEqual(expectedString, resultString);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows7.0")]
|
||||
[TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" " +
|
||||
"FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
|
||||
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY System.FileName")]
|
||||
"FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
|
||||
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY System.FileName")]
|
||||
[TestCase("", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" FROM \"SystemIndex\" WHERE WorkId IS NOT NULL AND scope='file:' ORDER BY System.FileName")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString(
|
||||
string userSearchString, string expectedString)
|
||||
{
|
||||
// Given
|
||||
var queryConstructor = new QueryConstructor(new Settings());
|
||||
var baseQuery = queryConstructor.CreateBaseQuery();
|
||||
|
||||
var baseQuery = queryConstructor.BaseQueryHelper;
|
||||
|
||||
// system running this test could have different locale than the hard-coded 1033 LCID en-US.
|
||||
var queryKeywordLocale = baseQuery.QueryKeywordLocale;
|
||||
expectedString = expectedString.Replace("1033", queryKeywordLocale.ToString());
|
||||
|
||||
|
||||
|
||||
//When
|
||||
var resultString = queryConstructor.QueryForAllFilesAndFolders(userSearchString);
|
||||
var resultString = queryConstructor.FilesAndFolders(userSearchString);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(resultString == expectedString,
|
||||
$"Expected query string: {expectedString}{Environment.NewLine} " +
|
||||
$"Actual string was: {resultString}{Environment.NewLine}");
|
||||
Assert.AreEqual(expectedString, resultString);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public async Task GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldContinueDirectoryInfoClassSearchAsync()
|
||||
{
|
||||
// Given
|
||||
var searchManager = new SearchManager(new Settings(), new PluginInitContext());
|
||||
|
||||
// When
|
||||
var results = await searchManager.TopLevelDirectorySearchBehaviourAsync(
|
||||
MethodWindowsIndexSearchReturnsZeroResultsAsync,
|
||||
MethodDirectoryInfoClassSearchReturnsTwoResults,
|
||||
false,
|
||||
new Query(),
|
||||
"string not used",
|
||||
default);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(results.Count == 2,
|
||||
$"Expected to have 2 results from DirectoryInfoClassSearch {Environment.NewLine} " +
|
||||
$"Actual number of results is {results.Count} {Environment.NewLine}");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public async Task GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearchAsync()
|
||||
{
|
||||
// Given
|
||||
var searchManager = new SearchManager(new Settings(), new PluginInitContext());
|
||||
|
||||
// When
|
||||
var results = await searchManager.TopLevelDirectorySearchBehaviourAsync(
|
||||
MethodWindowsIndexSearchReturnsZeroResultsAsync,
|
||||
MethodDirectoryInfoClassSearchReturnsTwoResults,
|
||||
true,
|
||||
new Query(),
|
||||
"string not used",
|
||||
default);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(results.Count == 0,
|
||||
$"Expected to have 0 results because location is indexed {Environment.NewLine} " +
|
||||
$"Actual number of results is {results.Count} {Environment.NewLine}");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows7.0")]
|
||||
[TestCase(@"some words", @"FREETEXT('some words')")]
|
||||
public void GivenWindowsIndexSearch_WhenQueryWhereRestrictionsIsForFileContentSearch_ThenShouldReturnFreeTextString(
|
||||
string querySearchString, string expectedString)
|
||||
|
|
@ -202,7 +139,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
var queryConstructor = new QueryConstructor(new Settings());
|
||||
|
||||
//When
|
||||
var resultString = queryConstructor.QueryWhereRestrictionsForFileContentSearch(querySearchString);
|
||||
var resultString = QueryConstructor.RestrictionsForFileContentSearch(querySearchString);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(resultString == expectedString,
|
||||
|
|
@ -210,8 +147,9 @@ namespace Flow.Launcher.Test.Plugins
|
|||
$"Actual string was: {resultString}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows7.0")]
|
||||
[TestCase("some words", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " +
|
||||
"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY System.FileName")]
|
||||
"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY System.FileName")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString(
|
||||
string userSearchString, string expectedString)
|
||||
{
|
||||
|
|
@ -219,7 +157,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
var queryConstructor = new QueryConstructor(new Settings());
|
||||
|
||||
//When
|
||||
var resultString = queryConstructor.QueryForFileContentSearch(userSearchString);
|
||||
var resultString = queryConstructor.FileContent(userSearchString);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(resultString == expectedString,
|
||||
|
|
@ -230,12 +168,15 @@ namespace Flow.Launcher.Test.Plugins
|
|||
public void GivenQuery_WhenActionKeywordForFileContentSearchExists_ThenFileContentSearchRequiredShouldReturnTrue()
|
||||
{
|
||||
// Given
|
||||
var query = new Query { ActionKeyword = "doc:", Search = "search term" };
|
||||
var query = new Query
|
||||
{
|
||||
ActionKeyword = "doc:", Search = "search term"
|
||||
};
|
||||
|
||||
var searchManager = new SearchManager(new Settings(), new PluginInitContext());
|
||||
|
||||
// When
|
||||
var result = searchManager.IsFileContentSearch(query.ActionKeyword);
|
||||
var result = SearchManager.IsFileContentSearch(query.ActionKeyword);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(result,
|
||||
|
|
@ -303,24 +244,19 @@ namespace Flow.Launcher.Test.Plugins
|
|||
$"Actual path string is {returnedPath} {Environment.NewLine}");
|
||||
}
|
||||
|
||||
[TestCase("c:\\SomeFolder\\>", "scope='file:c:\\SomeFolder'")]
|
||||
[TestCase("c:\\SomeFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' "
|
||||
+ "OR CONTAINS(System.FileName,'\"SomeName*\"',1033)) AND "
|
||||
+ "scope='file:c:\\SomeFolder'")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString(string path, string expectedString)
|
||||
[SupportedOSPlatform("windows7.0")]
|
||||
[TestCase("c:\\SomeFolder", "scope='file:c:\\SomeFolder'")]
|
||||
[TestCase("c:\\OtherFolder", "scope='file:c:\\OtherFolder'")]
|
||||
public void GivenFilePath_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString(string path, string expectedString)
|
||||
{
|
||||
// Given
|
||||
var queryConstructor = new QueryConstructor(new Settings());
|
||||
|
||||
//When
|
||||
var resultString = queryConstructor.QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path);
|
||||
var resultString = QueryConstructor.RecursiveDirectoryConstraint(path);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(resultString == expectedString,
|
||||
$"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
|
||||
$"Actual string was: {resultString}{Environment.NewLine}");
|
||||
Assert.AreEqual(expectedString, resultString);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows7.0")]
|
||||
[TestCase("c:\\somefolder\\>somefile", "*somefile*")]
|
||||
[TestCase("c:\\somefolder\\somefile", "somefile*")]
|
||||
[TestCase("c:\\somefolder\\", "*")]
|
||||
|
|
@ -331,9 +267,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
var resultString = DirectoryInfoSearch.ConstructSearchCriteria(path);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(resultString == expectedString,
|
||||
$"Expected criteria string: {expectedString}{Environment.NewLine} " +
|
||||
$"Actual criteria string was: {resultString}{Environment.NewLine}");
|
||||
Assert.AreEqual(expectedString, resultString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,24 +8,17 @@ using Flow.Launcher.ViewModel;
|
|||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
public partial class ActionKeywords : Window
|
||||
public partial class ActionKeywords
|
||||
{
|
||||
private readonly PluginPair plugin;
|
||||
private Settings settings;
|
||||
private readonly Internationalization translater = InternationalizationManager.Instance;
|
||||
private readonly PluginViewModel pluginViewModel;
|
||||
|
||||
public ActionKeywords(string pluginId, Settings settings, PluginViewModel pluginViewModel)
|
||||
public ActionKeywords(PluginViewModel pluginViewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
plugin = PluginManager.GetPluginForId(pluginId);
|
||||
this.settings = settings;
|
||||
plugin = pluginViewModel.PluginPair;
|
||||
this.pluginViewModel = pluginViewModel;
|
||||
if (plugin == null)
|
||||
{
|
||||
MessageBox.Show(translater.GetTranslation("cannotFindSpecifiedPlugin"));
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void ActionKeyword_OnLoaded(object sender, RoutedEventArgs e)
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ namespace Flow.Launcher
|
|||
|
||||
public void OnSecondAppStarted()
|
||||
{
|
||||
Current.MainWindow.Show();
|
||||
_mainVM.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -610,7 +610,7 @@ namespace Flow.Launcher
|
|||
&& QueryTextBox.CaretIndex == QueryTextBox.Text.Length)
|
||||
{
|
||||
var queryWithoutActionKeyword =
|
||||
QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins).Search;
|
||||
QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins)?.Search;
|
||||
|
||||
if (FilesFolders.IsLocationPathString(queryWithoutActionKeyword))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.ViewModel;
|
||||
using System;
|
||||
|
|
@ -23,14 +22,12 @@ namespace Flow.Launcher
|
|||
public partial class PriorityChangeWindow : Window
|
||||
{
|
||||
private readonly PluginPair plugin;
|
||||
private Settings settings;
|
||||
private readonly Internationalization translater = InternationalizationManager.Instance;
|
||||
private readonly PluginViewModel pluginViewModel;
|
||||
public PriorityChangeWindow(string pluginId, Settings settings, PluginViewModel pluginViewModel)
|
||||
public PriorityChangeWindow(string pluginId, PluginViewModel pluginViewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
plugin = PluginManager.GetPluginForId(pluginId);
|
||||
this.settings = settings;
|
||||
this.pluginViewModel = pluginViewModel;
|
||||
if (plugin == null)
|
||||
{
|
||||
|
|
@ -74,4 +71,4 @@ namespace Flow.Launcher
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,8 @@ namespace Flow.Launcher
|
|||
public void AddActionKeyword(string pluginId, string newActionKeyword) =>
|
||||
PluginManager.AddActionKeyword(pluginId, newActionKeyword);
|
||||
|
||||
public bool ActionKeywordAssigned(string actionKeyword) => PluginManager.ActionKeywordRegistered(actionKeyword);
|
||||
|
||||
public void RemoveActionKeyword(string pluginId, string oldActionKeyword) =>
|
||||
PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword);
|
||||
|
||||
|
|
|
|||
|
|
@ -1143,7 +1143,7 @@
|
|||
x:Name="PriorityButton"
|
||||
Margin="0,0,22,0"
|
||||
VerticalAlignment="Center"
|
||||
Click="OnPluginPriorityClick"
|
||||
Command="{Binding EditPluginPriorityCommand}"
|
||||
Content="{Binding Priority, UpdateSourceTrigger=PropertyChanged}"
|
||||
Cursor="Hand"
|
||||
ToolTip="{DynamicResource priorityToolTip}">
|
||||
|
|
@ -1221,8 +1221,8 @@
|
|||
Height="34"
|
||||
Margin="5,0,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
Click="OnPluginActionKeywordsClick"
|
||||
Content="{Binding ActionKeywordsText}"
|
||||
Command="{Binding SetActionKeywordsCommand}"
|
||||
Cursor="Hand"
|
||||
DockPanel.Dock="Right"
|
||||
FontWeight="Bold"
|
||||
|
|
@ -1371,9 +1371,14 @@
|
|||
Cursor="Hand"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource PluginInfoColor}"
|
||||
MouseUp="OnPluginDirecotyClick"
|
||||
Text="{DynamicResource pluginDirectory}"
|
||||
TextDecorations="Underline" />
|
||||
TextDecorations="Underline" >
|
||||
<TextBlock.InputBindings>
|
||||
<MouseBinding
|
||||
Command="{Binding OpenPluginDirectoryCommand}"
|
||||
MouseAction="LeftClick"/>
|
||||
</TextBlock.InputBindings>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
</ItemsControl>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using Flow.Launcher.Infrastructure;
|
|||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.ViewModel;
|
||||
using ModernWpf;
|
||||
using ModernWpf.Controls;
|
||||
|
|
@ -18,7 +17,6 @@ using System.Windows.Data;
|
|||
using System.Windows.Forms;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Navigation;
|
||||
using Button = System.Windows.Controls.Button;
|
||||
using Control = System.Windows.Controls.Control;
|
||||
|
|
@ -189,44 +187,11 @@ namespace Flow.Launcher
|
|||
{
|
||||
if (sender is Control { DataContext: PluginViewModel pluginViewModel })
|
||||
{
|
||||
PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(pluginViewModel.PluginPair.Metadata.ID, settings, pluginViewModel);
|
||||
PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(pluginViewModel.PluginPair.Metadata.ID, pluginViewModel);
|
||||
priorityChangeWindow.ShowDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPluginActionKeywordsClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var id = viewModel.SelectedPlugin.PluginPair.Metadata.ID;
|
||||
ActionKeywords changeKeywordsWindow = new ActionKeywords(id, settings, viewModel.SelectedPlugin);
|
||||
changeKeywordsWindow.ShowDialog();
|
||||
}
|
||||
|
||||
private void OnPluginNameClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
var website = viewModel.SelectedPlugin.PluginPair.Metadata.Website;
|
||||
if (!string.IsNullOrEmpty(website))
|
||||
{
|
||||
var uri = new Uri(website);
|
||||
if (Uri.CheckSchemeName(uri.Scheme))
|
||||
{
|
||||
website.OpenInBrowserTab();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPluginDirecotyClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
var directory = viewModel.SelectedPlugin.PluginPair.Metadata.PluginDirectory;
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
PluginManager.API.OpenDirectory(directory);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Proxy
|
||||
|
|
@ -297,22 +262,6 @@ namespace Flow.Launcher
|
|||
}
|
||||
}
|
||||
|
||||
private static T FindParent<T>(DependencyObject child) where T : DependencyObject
|
||||
{
|
||||
//get parent item
|
||||
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
|
||||
|
||||
//we've reached the end of the tree
|
||||
if (parentObject == null) return null;
|
||||
|
||||
//check if the parent matches the type we're looking for
|
||||
T parent = parentObject as T;
|
||||
if (parent != null)
|
||||
return parent;
|
||||
else
|
||||
return FindParent<T>(parentObject);
|
||||
}
|
||||
|
||||
private void OnExternalPluginInstallClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not Button { DataContext: PluginStoreItemViewModel plugin } button)
|
||||
|
|
@ -335,8 +284,6 @@ namespace Flow.Launcher
|
|||
var name = viewModel.SelectedPlugin.PluginPair.Metadata.Name;
|
||||
viewModel.DisplayPluginQuery($"uninstall {name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void OnExternalPluginUninstallClick(object sender, RoutedEventArgs e)
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ using Flow.Launcher.Plugin;
|
|||
using Flow.Launcher.Infrastructure.Image;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using System.Windows.Controls;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
public class PluginViewModel : BaseModel
|
||||
public partial class PluginViewModel : BaseModel
|
||||
{
|
||||
private readonly PluginPair _pluginPair;
|
||||
public PluginPair PluginPair
|
||||
|
|
@ -53,6 +54,7 @@ namespace Flow.Launcher.ViewModel
|
|||
set
|
||||
{
|
||||
_isExpanded = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(SettingControl));
|
||||
}
|
||||
|
|
@ -60,12 +62,12 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
private Control _settingControl;
|
||||
private bool _isExpanded;
|
||||
public Control SettingControl
|
||||
public Control SettingControl
|
||||
=> IsExpanded
|
||||
? _settingControl
|
||||
??= PluginPair.Plugin is not ISettingProvider settingProvider
|
||||
? new Control()
|
||||
: settingProvider.CreateSettingPanel()
|
||||
? new Control()
|
||||
: settingProvider.CreateSettingPanel()
|
||||
: null;
|
||||
private ImageSource _image = ImageLoader.MissingImage;
|
||||
|
||||
|
|
@ -87,7 +89,29 @@ namespace Flow.Launcher.ViewModel
|
|||
OnPropertyChanged(nameof(Priority));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void EditPluginPriority()
|
||||
{
|
||||
PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(PluginPair.Metadata.ID, this);
|
||||
priorityChangeWindow.ShowDialog();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenPluginDirectory()
|
||||
{
|
||||
var directory = PluginPair.Metadata.PluginDirectory;
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
PluginManager.API.OpenDirectory(directory);
|
||||
}
|
||||
|
||||
public static bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword);
|
||||
|
||||
[RelayCommand]
|
||||
private void SetActionKeywords()
|
||||
{
|
||||
ActionKeywords changeKeywordsWindow = new ActionKeywords(this);
|
||||
changeKeywordsWindow.ShowDialog();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -356,6 +356,8 @@ namespace Flow.Launcher.ViewModel
|
|||
await PluginsManifest.UpdateManifestAsync();
|
||||
OnPropertyChanged(nameof(ExternalPlugins));
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal void DisplayPluginQuery(string queryToDisplay, PluginPair plugin, int actionKeywordPosition = 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -37,25 +39,25 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
var contextMenus = new List<Result>();
|
||||
if (selectedResult.ContextData is SearchResult record)
|
||||
{
|
||||
if (record.Type == ResultType.File)
|
||||
if (record.Type == ResultType.File && !string.IsNullOrEmpty(Settings.EditorPath))
|
||||
contextMenus.Add(CreateOpenWithEditorResult(record));
|
||||
|
||||
if (record.Type == ResultType.Folder && record.WindowsIndexed)
|
||||
{
|
||||
contextMenus.Add(CreateAddToIndexSearchExclusionListResult(record));
|
||||
|
||||
contextMenus.Add(CreateOpenWithShellResult(record));
|
||||
}
|
||||
contextMenus.Add(CreateOpenContainingFolderResult(record));
|
||||
|
||||
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});
|
||||
if (record.WindowsIndexed)
|
||||
{
|
||||
contextMenus.Add(CreateOpenWindowsIndexingOptions());
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
|
|
@ -63,13 +65,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();
|
||||
|
||||
|
|
@ -91,10 +96,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();
|
||||
|
||||
|
|
@ -105,12 +110,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
|
||||
{
|
||||
|
|
@ -132,11 +137,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)
|
||||
|
|
@ -151,7 +159,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}",
|
||||
|
|
@ -161,10 +170,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;
|
||||
|
||||
|
|
@ -173,11 +182,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)
|
||||
|
|
@ -193,6 +202,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(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\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
|
||||
{
|
||||
|
|
@ -246,12 +301,13 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Result CreateOpenWithEditorResult(SearchResult record)
|
||||
{
|
||||
string editorPath = "Notepad.exe"; // TODO add the ability to create a custom editor
|
||||
string editorPath = Settings.EditorPath;
|
||||
|
||||
var name = Context.API.GetTranslation("plugin_explorer_openwitheditor")
|
||||
+ " " + Path.GetFileNameWithoutExtension(editorPath);
|
||||
var name = $"{Context.API.GetTranslation("plugin_explorer_openwitheditor")} {Path.GetFileNameWithoutExtension(editorPath)}";
|
||||
|
||||
return new Result
|
||||
{
|
||||
|
|
@ -265,7 +321,38 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = $"Failed to open editor for file at {record.FullPath}";
|
||||
var message = $"Failed to open editor for file at {record.FullPath} with Editor {Path.GetFileNameWithoutExtension(editorPath)} at {editorPath}";
|
||||
LogException(message, e);
|
||||
Context.API.ShowMsgError(message);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
IcoPath = Constants.FileImagePath
|
||||
};
|
||||
}
|
||||
|
||||
private Result CreateOpenWithShellResult(SearchResult record)
|
||||
{
|
||||
string shellPath = Settings.ShellPath;
|
||||
|
||||
var name = $"{Context.API.GetTranslation("plugin_explorer_openwithshell")} {Path.GetFileNameWithoutExtension(shellPath)}";
|
||||
|
||||
return new Result
|
||||
{
|
||||
Title = name,
|
||||
Action = _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = shellPath, WorkingDirectory = record.FullPath
|
||||
});
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = $"Failed to open editor for file at {record.FullPath} with Shell {Path.GetFileNameWithoutExtension(shellPath)} at {shellPath}";
|
||||
LogException(message, e);
|
||||
Context.API.ShowMsgError(message);
|
||||
return false;
|
||||
|
|
@ -283,14 +370,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();
|
||||
|
|
@ -313,11 +403,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;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.IProvider;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Exceptions;
|
||||
|
||||
public class EngineNotAvailableException : Exception
|
||||
{
|
||||
public string EngineName { get; }
|
||||
public string Resolution { get; }
|
||||
public Func<ActionContext, ValueTask<bool>>? Action { get; }
|
||||
|
||||
public string? ErrorIcon { get; init; }
|
||||
|
||||
public EngineNotAvailableException(
|
||||
string engineName,
|
||||
string resolution,
|
||||
string message,
|
||||
Func<ActionContext, ValueTask<bool>> action = null) : base(message)
|
||||
{
|
||||
EngineName = engineName;
|
||||
Resolution = resolution;
|
||||
Action = action ?? (_ =>
|
||||
{
|
||||
Clipboard.SetDataObject(this.ToString());
|
||||
return ValueTask.FromResult(true);
|
||||
});
|
||||
}
|
||||
|
||||
public EngineNotAvailableException(
|
||||
string engineName,
|
||||
string resolution,
|
||||
string message,
|
||||
Exception innerException) : base(message, innerException)
|
||||
{
|
||||
EngineName = engineName;
|
||||
Resolution = resolution;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Engine {EngineName} is not available.\n Try to {Resolution}\n {base.ToString()}";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Exceptions
|
||||
{
|
||||
public class SearchException : Exception
|
||||
{
|
||||
public string EngineName { get; }
|
||||
public SearchException(string engineName, string message) : base(message)
|
||||
{
|
||||
EngineName = engineName;
|
||||
}
|
||||
|
||||
public SearchException(string engineName, string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
EngineName = engineName;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{EngineName} Search Exception:\n {base.ToString()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<Nullable>warnings</Nullable>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
|
|
@ -24,6 +25,12 @@
|
|||
<None Include="plugin.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="EverythingSDK\x64\Everything.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="EverythingSDK\x86\Everything.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -39,6 +46,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Data.OleDb" Version="5.0.0" />
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageReference Include="tlbimp-Microsoft.Search.Interop" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
@ -47,5 +55,5 @@
|
|||
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
1615
Plugins/Flow.Launcher.Plugin.Explorer/Helper/ShellContextMenu.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using Flow.Launcher.Plugin.Everything.Everything;
|
||||
using JetBrains.Annotations;
|
||||
using System;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Helper;
|
||||
|
||||
public static class SortOptionTranslationHelper
|
||||
{
|
||||
[CanBeNull]
|
||||
public static IPublicAPI API { get; internal set; }
|
||||
|
||||
public static string GetTranslatedName(this SortOption sortOption)
|
||||
{
|
||||
const string prefix = "flowlauncher_plugin_everything_sort_by_";
|
||||
|
||||
ArgumentNullException.ThrowIfNull(API);
|
||||
|
||||
var enumName = Enum.GetName(sortOption);
|
||||
var splited = enumName.Split('_');
|
||||
var name = string.Join('_', splited[..^1]);
|
||||
var direction = splited[^1];
|
||||
|
||||
return $"{API.GetTranslation(prefix + name.ToLower())} {API.GetTranslation(prefix + direction.ToLower())}";
|
||||
}
|
||||
}
|
||||
BIN
Plugins/Flow.Launcher.Plugin.Explorer/Images/context_menu.png
Normal file
|
After Width: | Height: | Size: 251 B |
BIN
Plugins/Flow.Launcher.Plugin.Explorer/Images/error.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
BIN
Plugins/Flow.Launcher.Plugin.Explorer/Images/index_error.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
Plugins/Flow.Launcher.Plugin.Explorer/Images/index_error2.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
Plugins/Flow.Launcher.Plugin.Explorer/Images/robot_error.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -1,8 +1,8 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<!--Dialogues-->
|
||||
<!-- Dialogues -->
|
||||
<system:String x:Key="plugin_explorer_make_selection_warning">Please make a selection first</system:String>
|
||||
<system:String x:Key="plugin_explorer_select_folder_link_warning">Please select a folder link</system:String>
|
||||
<system:String x:Key="plugin_explorer_delete_folder_link">Are you sure you want to delete {0}?</system:String>
|
||||
|
|
@ -17,15 +17,22 @@
|
|||
<system:String x:Key="plugin_explorer_alternative_title">Explorer Alternative</system:String>
|
||||
<system:String x:Key="plugin_explorer_directoryinfosearch_error">Error occurred during search: {0}</system:String>
|
||||
|
||||
<!--Controls-->
|
||||
<!-- Controls -->
|
||||
<system:String x:Key="plugin_explorer_delete">Delete</system:String>
|
||||
<system:String x:Key="plugin_explorer_edit">Edit</system:String>
|
||||
<system:String x:Key="plugin_explorer_add">Add</system:String>
|
||||
<system:String x:Key="plugin_explorer_generalsetting_header">General Setting</system:String>
|
||||
<system:String x:Key="plugin_explorer_manageactionkeywords_header">Customise Action Keywords</system:String>
|
||||
<system:String x:Key="plugin_explorer_quickaccesslinks_header">Quick Access Links</system:String>
|
||||
<system:String x:Key="plugin_explorer_everything_setting_header">Everything Setting</system:String>
|
||||
<system:String x:Key="plugin_explorer_everything_sort_option">Sort Option:</system:String>
|
||||
<system:String x:Key="plugin_explorer_everything_installed_path">Everything Path:</system:String>
|
||||
<system:String x:Key="plugin_explorer_launch_hidden">Launch Hidden</system:String>
|
||||
<system:String x:Key="plugin_explorer_editor_path">Editor Path</system:String>
|
||||
<system:String x:Key="plugin_explorer_shell_path">Shell Path</system:String>
|
||||
<system:String x:Key="plugin_explorer_indexsearchexcludedpaths_header">Index Search Excluded Paths</system:String>
|
||||
<system:String x:Key="plugin_explorer_use_location_as_working_dir">Use search result's location as executable working directory</system:String>
|
||||
<system:String x:Key="plugin_explorer_usewindowsindexfordirectorysearch">Use Index Search For Path Search</system:String>
|
||||
<system:String x:Key="plugin_explorer_usewindowsindexfordirectorysearch_tooltip">Turning this on will return indexed directories/files faster, but if a directory/file is not indexed it will not show up. If a directory/file has been added to Index Search Excluded Path then it will still show up even if this option is on</system:String>
|
||||
<system:String x:Key="plugin_explorer_manageindexoptions">Indexing Options</system:String>
|
||||
<system:String x:Key="plugin_explorer_actionkeywordview_search">Search:</system:String>
|
||||
<system:String x:Key="plugin_explorer_actionkeywordview_pathsearch">Path Search:</system:String>
|
||||
|
|
@ -36,12 +43,24 @@
|
|||
<system:String x:Key="plugin_explorer_actionkeyword_done">Done</system:String>
|
||||
<system:String x:Key="plugin_explorer_actionkeyword_enabled">Enabled</system:String>
|
||||
<system:String x:Key="plugin_explorer_actionkeyword_enabled_tooltip">When disabled Flow will not execute this search option, and will additionally revert back to '*' to free up the action keyword</system:String>
|
||||
<system:String x:Key="plugin_explorer_engine_everything">Everything</system:String>
|
||||
<system:String x:Key="plugin_explorer_engine_windows_index">Windows Index</system:String>
|
||||
<system:String x:Key="plugin_explorer_path_enumeration_engine_none">Direct Enumeration</system:String>
|
||||
|
||||
<!--Plugin Infos-->
|
||||
<system:String x:Key="plugin_explorer_Content_Search_Engine">Content Search Engine</system:String>
|
||||
<system:String x:Key="plugin_explorer_Directory_Recursive_Search_Engine">Directory Recursive Search Engine</system:String>
|
||||
<system:String x:Key="plugin_explorer_Index_Search_Engine">Index Search Engine</system:String>
|
||||
<system:String x:Key="plugin_explorer_Open_Window_Index_Option">Open Window Index Option</system:String>
|
||||
|
||||
<!-- Plugin Infos -->
|
||||
<system:String x:Key="plugin_explorer_plugin_name">Explorer</system:String>
|
||||
<system:String x:Key="plugin_explorer_plugin_description">Search and manage files and folders. Explorer utilises Windows Index Search</system:String>
|
||||
|
||||
<!--Context menu items-->
|
||||
<!-- Plugin Tooltip -->
|
||||
<system:String x:Key="plugin_explorer_plugin_ToolTipOpenDirectory">Ctrl + Enter to open the directory</system:String>
|
||||
<system:String x:Key="plugin_explorer_plugin_ToolTipOpenContainingFolder">Ctrl + Enter to open the containing folder</system:String>
|
||||
|
||||
<!-- Context menu items -->
|
||||
<system:String x:Key="plugin_explorer_copypath">Copy path</system:String>
|
||||
<system:String x:Key="plugin_explorer_copyfilefolder">Copy</system:String>
|
||||
<system:String x:Key="plugin_explorer_deletefilefolder">Delete</system:String>
|
||||
|
|
@ -52,6 +71,7 @@
|
|||
<system:String x:Key="plugin_explorer_opencontainingfolder">Open containing folder</system:String>
|
||||
<system:String x:Key="plugin_explorer_opencontainingfolder_subtitle">Opens the location that contains the file or folder</system:String>
|
||||
<system:String x:Key="plugin_explorer_openwitheditor">Open With Editor:</system:String>
|
||||
<system:String x:Key="plugin_explorer_openwithshell">Open With Shell:</system:String>
|
||||
<system:String x:Key="plugin_explorer_excludefromindexsearch">Exclude current and sub-directories from Index Search</system:String>
|
||||
<system:String x:Key="plugin_explorer_excludedfromindexsearch_msg">Excluded from Index Search</system:String>
|
||||
<system:String x:Key="plugin_explorer_openindexingoptions">Open Windows Indexing Options</system:String>
|
||||
|
|
@ -67,5 +87,36 @@
|
|||
<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>
|
||||
|
||||
<!-- Everything -->
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sdk_issue">Everything SDK Loaded Fail</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_is_not_running">Warning: Everything service is not running</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_query_error">Error while querying Everything</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by">Sort By</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_name">Name</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_path">Path</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_size">Size</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_extension">Extension</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_type_name">Type Name</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_date_created">Date Created</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_date_modified">Date Modified</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_attributes">Attributes</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_file_list_filename">File List FileName</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_run_count">Run Count</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_date_recently_changed">Date Recently Changed</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_date_accessed">Date Accessed</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_date_run">Date Run</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_ascending">↑</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_sort_by_descending">↓</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_nonfastsort_warning">Warning: This is not a Fast Sort option, searches may be slow</system:String>
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_everything_click_to_launch_or_install">Click to Launch or Install Everything</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_installing_title">Everything Installation</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_installing_subtitle">Installing Everything service. Please wait...</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_installationsuccess_subtitle">Successfully installed Everything service</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_installationfailed_subtitle">Failed to automatically install Everything service. Please manually install it from https://www.voidtools.com</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_run_service">Click here to start it</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_everything_installing_select">Unable to find an Everything installation, would you like to manually select a location?{0}{0}Click no and Everything will be automatically installed for you</system:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
|
@ -1,19 +1,22 @@
|
|||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Plugin.Explorer.Helper;
|
||||
using Flow.Launcher.Plugin.Explorer.Search;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.Everything;
|
||||
using Flow.Launcher.Plugin.Explorer.ViewModels;
|
||||
using Flow.Launcher.Plugin.Explorer.Views;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Flow.Launcher.Plugin.Explorer.Exceptions;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer
|
||||
{
|
||||
public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n
|
||||
{
|
||||
internal PluginInitContext Context { get; set; }
|
||||
internal static PluginInitContext Context { get; set; }
|
||||
|
||||
internal Settings Settings;
|
||||
|
||||
|
|
@ -31,23 +34,19 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
public Task InitAsync(PluginInitContext context)
|
||||
{
|
||||
Context = context;
|
||||
|
||||
|
||||
Settings = context.API.LoadSettingJsonStorage<Settings>();
|
||||
|
||||
viewModel = new SettingsViewModel(context, Settings);
|
||||
|
||||
|
||||
// as at v1.7.0 this is to maintain backwards compatibility, need to be removed afterwards.
|
||||
if (Settings.QuickFolderAccessLinks.Any())
|
||||
{
|
||||
Settings.QuickAccessLinks = Settings.QuickFolderAccessLinks;
|
||||
Settings.QuickFolderAccessLinks = new List<AccessLink>();
|
||||
}
|
||||
|
||||
contextMenu = new ContextMenu(Context, Settings, viewModel);
|
||||
searchManager = new SearchManager(Settings, Context);
|
||||
ResultManager.Init(Context, Settings);
|
||||
|
||||
SortOptionTranslationHelper.API = context.API;
|
||||
|
||||
EverythingApiDllImport.Load(Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "EverythingSDK",
|
||||
Environment.Is64BitProcess ? "x64" : "x86"));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +57,34 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
|
||||
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
||||
{
|
||||
return await searchManager.SearchAsync(query, token);
|
||||
try
|
||||
{
|
||||
return await searchManager.SearchAsync(query, token);
|
||||
}
|
||||
catch (Exception e) when (e is SearchException or EngineNotAvailableException)
|
||||
{
|
||||
return new List<Result>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Title = e.Message,
|
||||
SubTitle = e is EngineNotAvailableException { Resolution: { } resolution }
|
||||
? resolution
|
||||
: "Enter to copy the message to clipboard",
|
||||
Score = 501,
|
||||
IcoPath = e is EngineNotAvailableException { ErrorIcon: { } iconPath }
|
||||
? iconPath
|
||||
: Constants.GeneralSearchErrorImagePath,
|
||||
AsyncAction = e is EngineNotAvailableException {Action: { } action}
|
||||
? action
|
||||
: _ =>
|
||||
{
|
||||
Clipboard.SetDataObject(e.ToString());
|
||||
return new ValueTask<bool>(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginTitle()
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@ 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\\context_menu.png";
|
||||
internal const string EverythingErrorImagePath = "Images\\everything_error.png";
|
||||
internal const string IndexSearchWarningImagePath = "Images\\index_error.png";
|
||||
internal const string WindowsIndexErrorImagePath = "Images\\index_error2.png";
|
||||
internal const string GeneralSearchErrorImagePath = "Images\\robot_error.png";
|
||||
|
||||
|
||||
internal const string ToolTipOpenDirectory = "Ctrl + Enter to open the directory";
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
{
|
||||
public static class DirectoryInfoSearch
|
||||
{
|
||||
internal static List<Result> TopLevelDirectorySearch(Query query, string search, CancellationToken token)
|
||||
internal static IEnumerable<SearchResult> TopLevelDirectorySearch(Query query, string search, CancellationToken token)
|
||||
{
|
||||
var criteria = ConstructSearchCriteria(search);
|
||||
|
||||
|
|
@ -19,9 +19,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
return DirectorySearch(new EnumerationOptions
|
||||
{
|
||||
RecurseSubdirectories = true
|
||||
}, query, search, criteria, token);
|
||||
}, search, criteria, token);
|
||||
|
||||
return DirectorySearch(new EnumerationOptions(), query, search, criteria,
|
||||
return DirectorySearch(new EnumerationOptions(), search, criteria,
|
||||
token); // null will be passed as default
|
||||
}
|
||||
|
||||
|
|
@ -33,10 +33,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
{
|
||||
var indexOfSeparator = search.LastIndexOf(Constants.DirectorySeperator);
|
||||
|
||||
incompleteName = search.Substring(indexOfSeparator + 1).ToLower();
|
||||
incompleteName = search[(indexOfSeparator + 1)..].ToLower();
|
||||
|
||||
if (incompleteName.StartsWith(Constants.AllFilesFolderSearchWildcard))
|
||||
incompleteName = "*" + incompleteName.Substring(1);
|
||||
incompleteName = string.Concat("*", incompleteName.AsSpan(1));
|
||||
}
|
||||
|
||||
incompleteName += "*";
|
||||
|
|
@ -44,54 +44,45 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
return incompleteName;
|
||||
}
|
||||
|
||||
private static List<Result> DirectorySearch(EnumerationOptions enumerationOption, Query query, string search,
|
||||
private static IEnumerable<SearchResult> DirectorySearch(EnumerationOptions enumerationOption, string search,
|
||||
string searchCriteria, CancellationToken token)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
var results = new List<SearchResult>();
|
||||
|
||||
var path = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(search);
|
||||
|
||||
var folderList = new List<Result>();
|
||||
var fileList = new List<Result>();
|
||||
|
||||
try
|
||||
{
|
||||
var directoryInfo = new System.IO.DirectoryInfo(path);
|
||||
|
||||
foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, enumerationOption))
|
||||
{
|
||||
if (fileSystemInfo is System.IO.DirectoryInfo)
|
||||
results.Add(new SearchResult
|
||||
{
|
||||
folderList.Add(ResultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName,
|
||||
fileSystemInfo.FullName, query, 0, true, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
fileList.Add(ResultManager.CreateFileResult(fileSystemInfo.FullName, query, 0, true, false));
|
||||
}
|
||||
FullPath = fileSystemInfo.FullName,
|
||||
Type = fileSystemInfo switch
|
||||
{
|
||||
System.IO.DirectoryInfo {Parent: null} => ResultType.Volume,
|
||||
System.IO.DirectoryInfo => ResultType.Folder,
|
||||
FileInfo => ResultType.File,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(fileSystemInfo))
|
||||
},
|
||||
WindowsIndexed = false
|
||||
});
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (token.IsCancellationRequested)
|
||||
return results;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(nameof(DirectoryInfoSearch), "Error occured while searching path", e);
|
||||
|
||||
results.Add(
|
||||
new Result
|
||||
{
|
||||
Title = string.Format(SearchManager.Context.API.GetTranslation(
|
||||
"plugin_explorer_directoryinfosearch_error"),
|
||||
e.Message),
|
||||
Score = 501,
|
||||
IcoPath = Constants.ExplorerIconImagePath
|
||||
});
|
||||
|
||||
return results;
|
||||
throw;
|
||||
}
|
||||
|
||||
// Initial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
|
||||
return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList();
|
||||
return results.OrderBy(r=>r.Type).ThenBy(r=>r.FullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,216 @@
|
|||
using Flow.Launcher.Plugin.Everything.Everything;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms.Design;
|
||||
using Flow.Launcher.Plugin.Explorer.Exceptions;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
||||
{
|
||||
|
||||
public static class EverythingApi
|
||||
{
|
||||
|
||||
private const int BufferSize = 4096;
|
||||
|
||||
private static 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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [match path].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [match path]; otherwise, <c>false</c>.</value>
|
||||
public static bool MatchPath
|
||||
{
|
||||
get => EverythingApiDllImport.Everything_GetMatchPath();
|
||||
set => EverythingApiDllImport.Everything_SetMatchPath(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [match case].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [match case]; otherwise, <c>false</c>.</value>
|
||||
public static bool MatchCase
|
||||
{
|
||||
get => EverythingApiDllImport.Everything_GetMatchCase();
|
||||
set => EverythingApiDllImport.Everything_SetMatchCase(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [match whole word].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [match whole word]; otherwise, <c>false</c>.</value>
|
||||
public static bool MatchWholeWord
|
||||
{
|
||||
get => EverythingApiDllImport.Everything_GetMatchWholeWord();
|
||||
set => EverythingApiDllImport.Everything_SetMatchWholeWord(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [enable regex].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [enable regex]; otherwise, <c>false</c>.</value>
|
||||
public static bool EnableRegex
|
||||
{
|
||||
get => EverythingApiDllImport.Everything_GetRegex();
|
||||
set => EverythingApiDllImport.Everything_SetRegex(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the sort option is Fast Sort.
|
||||
/// </summary>
|
||||
public static bool IsFastSortOption(SortOption sortOption)
|
||||
{
|
||||
var fastSortOptionEnabled = EverythingApiDllImport.Everything_IsFastSort(sortOption);
|
||||
|
||||
// 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<bool> IsEverythingRunningAsync(CancellationToken token = default)
|
||||
{
|
||||
await _semaphore.WaitAsync(token);
|
||||
|
||||
try
|
||||
{
|
||||
EverythingApiDllImport.Everything_GetMajorVersion();
|
||||
var result = EverythingApiDllImport.Everything_GetLastError() != StateCode.IPCError;
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches the specified key word and reset the everything API afterwards
|
||||
/// </summary>
|
||||
/// <param name="option">Search Criteria</param>
|
||||
/// <param name="token">when cancelled the current search will stop and exit (and would not reset)</param>
|
||||
/// <returns>An IAsyncEnumerable that will enumerate all results searched by the specific query and option</returns>
|
||||
public static async IAsyncEnumerable<SearchResult> SearchAsync(EverythingSearchOption option,
|
||||
[EnumeratorCancellation] CancellationToken token)
|
||||
{
|
||||
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");
|
||||
|
||||
await _semaphore.WaitAsync(token);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
yield break;
|
||||
|
||||
if (option.Keyword.StartsWith("@"))
|
||||
{
|
||||
EverythingApiDllImport.Everything_SetRegex(true);
|
||||
option.Keyword = option.Keyword[1..];
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append(option.Keyword);
|
||||
|
||||
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);
|
||||
|
||||
if (token.IsCancellationRequested) yield break;
|
||||
|
||||
if (!EverythingApiDllImport.Everything_QueryW(true))
|
||||
{
|
||||
CheckAndThrowExceptionOnError();
|
||||
yield break;
|
||||
}
|
||||
|
||||
for (var idx = 0; idx < EverythingApiDllImport.Everything_GetNumResults(); ++idx)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
EverythingApiDllImport.Everything_GetResultFullPathNameW(idx, buffer, BufferSize);
|
||||
|
||||
var result = new SearchResult
|
||||
{
|
||||
FullPath = buffer.ToString(),
|
||||
Type = EverythingApiDllImport.Everything_IsFolderResult(idx) ? ResultType.Folder :
|
||||
EverythingApiDllImport.Everything_IsFileResult(idx) ? ResultType.File :
|
||||
ResultType.Volume
|
||||
};
|
||||
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EverythingApiDllImport.Everything_Reset();
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckAndThrowExceptionOnError()
|
||||
{
|
||||
switch (EverythingApiDllImport.Everything_GetLastError())
|
||||
{
|
||||
case StateCode.CreateThreadError:
|
||||
throw new CreateThreadException();
|
||||
case StateCode.CreateWindowError:
|
||||
throw new CreateWindowException();
|
||||
case StateCode.InvalidCallError:
|
||||
throw new InvalidCallException();
|
||||
case StateCode.InvalidIndexError:
|
||||
throw new InvalidIndexException();
|
||||
case StateCode.IPCError:
|
||||
throw new IPCErrorException();
|
||||
case StateCode.MemoryError:
|
||||
throw new MemoryErrorException();
|
||||
case StateCode.RegisterClassExError:
|
||||
throw new RegisterClassExException();
|
||||
case StateCode.OK:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
using Flow.Launcher.Plugin.Everything.Everything;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
||||
{
|
||||
public static class EverythingApiDllImport
|
||||
{
|
||||
public static void Load(string directory)
|
||||
{
|
||||
var path = Path.Combine(directory, DLL);
|
||||
int code = LoadLibrary(path);
|
||||
if (code == 0)
|
||||
{
|
||||
int err = Marshal.GetLastPInvokeError();
|
||||
Marshal.ThrowExceptionForHR(err);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern int LoadLibrary(string name);
|
||||
|
||||
private const string DLL = "Everything.dll";
|
||||
|
||||
[DllImport(DLL, CharSet = CharSet.Unicode)]
|
||||
internal static extern int Everything_SetSearchW(string lpSearchString);
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern void Everything_SetMatchPath(bool bEnable);
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern void Everything_SetMatchCase(bool bEnable);
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern void Everything_SetMatchWholeWord(bool bEnable);
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern void Everything_SetRegex(bool bEnable);
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern void Everything_SetMax(int dwMax);
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern void Everything_SetOffset(int dwOffset);
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern bool Everything_GetMatchPath();
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern bool Everything_GetMatchCase();
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern bool Everything_GetMatchWholeWord();
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern bool Everything_GetRegex();
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern uint Everything_GetMax();
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern uint Everything_GetOffset();
|
||||
|
||||
[DllImport(DLL, CharSet = CharSet.Unicode)]
|
||||
internal static extern string Everything_GetSearchW();
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern EverythingApi.StateCode Everything_GetLastError();
|
||||
|
||||
[DllImport(DLL, CharSet = CharSet.Unicode)]
|
||||
internal static extern bool Everything_QueryW(bool bWait);
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern void Everything_SortResultsByPath();
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern int Everything_GetNumFileResults();
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern int Everything_GetMajorVersion();
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern int Everything_GetNumFolderResults();
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern int Everything_GetNumResults();
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern int Everything_GetTotFileResults();
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern int Everything_GetTotFolderResults();
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern int Everything_GetTotResults();
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern bool Everything_IsVolumeResult(int nIndex);
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern bool Everything_IsFolderResult(int nIndex);
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern bool Everything_IsFileResult(int nIndex);
|
||||
|
||||
[DllImport(DLL, CharSet = CharSet.Unicode)]
|
||||
internal static extern void Everything_GetResultFullPathNameW(int nIndex, StringBuilder lpString, int nMaxCount);
|
||||
|
||||
[DllImport(DLL)]
|
||||
internal static extern void Everything_Reset();
|
||||
|
||||
// Everything 1.4
|
||||
|
||||
[DllImport(DLL)]
|
||||
public static extern void Everything_SetSort(SortOption dwSortType);
|
||||
[DllImport(DLL)]
|
||||
public static extern bool Everything_IsFastSort(SortOption dwSortType);
|
||||
[DllImport(DLL)]
|
||||
public static extern SortOption Everything_GetSort();
|
||||
[DllImport(DLL)]
|
||||
public static extern uint Everything_GetResultListSort();
|
||||
[DllImport(DLL)]
|
||||
public static extern void Everything_SetRequestFlags(uint dwRequestFlags);
|
||||
[DllImport(DLL)]
|
||||
public static extern uint Everything_GetRequestFlags();
|
||||
[DllImport(DLL)]
|
||||
public static extern uint Everything_GetResultListRequestFlags();
|
||||
[DllImport("Everything64.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr Everything_GetResultExtension(uint nIndex);
|
||||
[DllImport(DLL)]
|
||||
public static extern bool Everything_GetResultSize(uint nIndex, out long lpFileSize);
|
||||
[DllImport(DLL)]
|
||||
public static extern bool Everything_GetResultDateCreated(uint nIndex, out long lpFileTime);
|
||||
[DllImport(DLL)]
|
||||
public static extern bool Everything_GetResultDateModified(uint nIndex, out long lpFileTime);
|
||||
[DllImport(DLL)]
|
||||
public static extern bool Everything_GetResultDateAccessed(uint nIndex, out long lpFileTime);
|
||||
[DllImport(DLL)]
|
||||
public static extern uint Everything_GetResultAttributes(uint nIndex);
|
||||
[DllImport(DLL, CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr Everything_GetResultFileListFileName(uint nIndex);
|
||||
[DllImport(DLL)]
|
||||
public static extern uint Everything_GetResultRunCount(uint nIndex);
|
||||
[DllImport(DLL)]
|
||||
public static extern bool Everything_GetResultDateRun(uint nIndex, out long lpFileTime);
|
||||
[DllImport(DLL)]
|
||||
public static extern bool Everything_GetResultDateRecentlyChanged(uint nIndex, out long lpFileTime);
|
||||
[DllImport(DLL, CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr Everything_GetResultHighlightedFileName(uint nIndex);
|
||||
[DllImport(DLL, CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr Everything_GetResultHighlightedPath(uint nIndex);
|
||||
[DllImport(DLL, CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr Everything_GetResultHighlightedFullPathAndFileName(uint nIndex);
|
||||
[DllImport(DLL)]
|
||||
public static extern uint Everything_GetRunCountFromFileName(string lpFileName);
|
||||
[DllImport(DLL)]
|
||||
public static extern bool Everything_SetRunCountFromFileName(string lpFileName, uint dwRunCount);
|
||||
[DllImport(DLL)]
|
||||
public static extern uint Everything_IncRunCountFromFileName(string lpFileName);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
using Droplex;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything;
|
||||
|
||||
public static class EverythingDownloadHelper
|
||||
{
|
||||
public static async Task<string> PromptDownloadIfNotInstallAsync(string installedLocation, IPublicAPI api)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(installedLocation) && installedLocation.FileExists())
|
||||
return installedLocation;
|
||||
|
||||
installedLocation = GetInstalledPath();
|
||||
|
||||
if (string.IsNullOrEmpty(installedLocation))
|
||||
{
|
||||
if (System.Windows.Forms.MessageBox.Show(
|
||||
string.Format(api.GetTranslation("flowlauncher_plugin_everything_installing_select"), Environment.NewLine),
|
||||
api.GetTranslation("flowlauncher_plugin_everything_installing_title"),
|
||||
System.Windows.Forms.MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes)
|
||||
{
|
||||
var dlg = new System.Windows.Forms.OpenFileDialog
|
||||
{
|
||||
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
|
||||
};
|
||||
|
||||
var result = dlg.ShowDialog();
|
||||
if (result == System.Windows.Forms.DialogResult.OK && !string.IsNullOrEmpty(dlg.FileName))
|
||||
installedLocation = dlg.FileName;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(installedLocation))
|
||||
{
|
||||
return installedLocation;
|
||||
}
|
||||
|
||||
api.ShowMsg(api.GetTranslation("flowlauncher_plugin_everything_installing_title"),
|
||||
api.GetTranslation("flowlauncher_plugin_everything_installing_subtitle"), "", useMainWindowAsOwner: false);
|
||||
|
||||
await DroplexPackage.Drop(App.Everything1_4_1_1009).ConfigureAwait(false);
|
||||
|
||||
api.ShowMsg(api.GetTranslation("flowlauncher_plugin_everything_installing_title"),
|
||||
api.GetTranslation("flowlauncher_plugin_everything_installationsuccess_subtitle"), "", useMainWindowAsOwner: false);
|
||||
|
||||
installedLocation = "C:\\Program Files\\Everything\\Everything.exe";
|
||||
|
||||
FilesFolders.OpenPath(installedLocation);
|
||||
|
||||
return installedLocation;
|
||||
|
||||
}
|
||||
|
||||
internal static string GetInstalledPath()
|
||||
{
|
||||
using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall");
|
||||
if (key is not null)
|
||||
{
|
||||
foreach (var subKey in key.GetSubKeyNames().Select(keyName => key.OpenSubKey(keyName)))
|
||||
{
|
||||
if (subKey?.GetValue("DisplayName") is not string displayName || !displayName.Contains("Everything"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (subKey.GetValue("UninstallString") is not string uninstallString)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Path.GetDirectoryName(uninstallString) is not { } uninstallDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return Path.Combine(uninstallDirectory, "Everything.exe");
|
||||
}
|
||||
}
|
||||
|
||||
var scoopInstalledPath = Environment.ExpandEnvironmentVariables(@"%userprofile%\scoop\apps\everything\current\Everything.exe");
|
||||
return File.Exists(scoopInstalledPath) ? scoopInstalledPath : string.Empty;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Plugin.Explorer.Exceptions;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.IProvider;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
||||
{
|
||||
public class EverythingSearchManager : IIndexProvider, IContentIndexProvider, IPathIndexProvider
|
||||
{
|
||||
private Settings Settings { get; }
|
||||
|
||||
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)!,
|
||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_click_to_launch_or_install"),
|
||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_is_not_running"),
|
||||
ClickToInstallEverythingAsync)
|
||||
{
|
||||
ErrorIcon = Constants.EverythingErrorImagePath
|
||||
};
|
||||
}
|
||||
catch (DllNotFoundException)
|
||||
{
|
||||
throw new EngineNotAvailableException(
|
||||
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
||||
"Please check whether your system is x86 or x64",
|
||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue"))
|
||||
{
|
||||
ErrorIcon = Constants.GeneralSearchErrorImagePath
|
||||
};
|
||||
}
|
||||
}
|
||||
private async ValueTask<bool> ClickToInstallEverythingAsync(ActionContext _)
|
||||
{
|
||||
var installedPath = await EverythingDownloadHelper.PromptDownloadIfNotInstallAsync(Settings.EverythingInstalledPath, Main.Context.API);
|
||||
if (installedPath == null)
|
||||
{
|
||||
Main.Context.API.ShowMsgError("Unable to find Everything.exe");
|
||||
return false;
|
||||
}
|
||||
Settings.EverythingInstalledPath = installedPath;
|
||||
Process.Start(installedPath, "-startup");
|
||||
return true;
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<SearchResult> SearchAsync(string search, [EnumeratorCancellation] CancellationToken token)
|
||||
{
|
||||
await ThrowIfEverythingNotAvailableAsync(token);
|
||||
if (token.IsCancellationRequested)
|
||||
yield break;
|
||||
var option = new EverythingSearchOption(search, Settings.SortOption);
|
||||
await foreach (var result in EverythingApi.SearchAsync(option, token))
|
||||
yield return result;
|
||||
}
|
||||
public async IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch,
|
||||
string contentSearch, [EnumeratorCancellation] CancellationToken token)
|
||||
{
|
||||
await ThrowIfEverythingNotAvailableAsync(token);
|
||||
if (!Settings.EnableEverythingContentSearch)
|
||||
{
|
||||
throw new EngineNotAvailableException(Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
||||
"Click to Enable Everything Content Search (only applicable to Everything 1.5+ with indexed content)",
|
||||
"Everything Content Search is not enabled.",
|
||||
_ =>
|
||||
{
|
||||
Settings.EnableEverythingContentSearch = true;
|
||||
return ValueTask.FromResult(true);
|
||||
})
|
||||
{
|
||||
ErrorIcon = Constants.EverythingErrorImagePath
|
||||
};
|
||||
}
|
||||
if (token.IsCancellationRequested)
|
||||
yield break;
|
||||
|
||||
var option = new EverythingSearchOption(plainSearch,
|
||||
Settings.SortOption,
|
||||
true,
|
||||
contentSearch);
|
||||
|
||||
await foreach (var result in EverythingApi.SearchAsync(option, token))
|
||||
{
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
public async IAsyncEnumerable<SearchResult> EnumerateAsync(string path, string search, bool recursive, [EnumeratorCancellation] CancellationToken token)
|
||||
{
|
||||
await ThrowIfEverythingNotAvailableAsync(token);
|
||||
if (token.IsCancellationRequested)
|
||||
yield break;
|
||||
|
||||
var option = new EverythingSearchOption(search,
|
||||
Settings.SortOption,
|
||||
ParentPath: path,
|
||||
IsRecursive: recursive);
|
||||
|
||||
await foreach (var result in EverythingApi.SearchAsync(option, token))
|
||||
{
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using Flow.Launcher.Plugin.Everything.Everything;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
||||
{
|
||||
public record struct EverythingSearchOption(string Keyword,
|
||||
SortOption SortOption,
|
||||
bool IsContentSearch = false,
|
||||
string ContentSearchKeyword = default,
|
||||
string ParentPath = default,
|
||||
bool IsRecursive = true,
|
||||
int Offset = 0,
|
||||
int MaxCount = 100);
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class CreateThreadException : ApplicationException
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class CreateWindowException : ApplicationException
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class IPCErrorException : ApplicationException
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class InvalidCallException : ApplicationException
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class InvalidIndexException : ApplicationException
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MemoryErrorException : ApplicationException
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class RegisterClassExException : ApplicationException
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Everything.Everything
|
||||
{
|
||||
public enum SortOption : uint
|
||||
{
|
||||
NAME_ASCENDING = 1u,
|
||||
NAME_DESCENDING = 2u,
|
||||
PATH_ASCENDING = 3u,
|
||||
PATH_DESCENDING = 4u,
|
||||
SIZE_ASCENDING = 5u,
|
||||
SIZE_DESCENDING = 6u,
|
||||
EXTENSION_ASCENDING = 7u,
|
||||
EXTENSION_DESCENDING = 8u,
|
||||
TYPE_NAME_ASCENDING = 9u,
|
||||
TYPE_NAME_DESCENDING = 10u,
|
||||
DATE_CREATED_ASCENDING = 11u,
|
||||
DATE_CREATED_DESCENDING = 12u,
|
||||
DATE_MODIFIED_ASCENDING = 13u,
|
||||
DATE_MODIFIED_DESCENDING = 14u,
|
||||
ATTRIBUTES_ASCENDING = 15u,
|
||||
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,
|
||||
DATE_ACCESSED_ASCENDING = 23u,
|
||||
DATE_ACCESSED_DESCENDING = 24u,
|
||||
DATE_RUN_ASCENDING = 25u,
|
||||
DATE_RUN_DESCENDING = 26u
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.IProvider
|
||||
{
|
||||
public interface IContentIndexProvider
|
||||
{
|
||||
public IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token = default);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.IProvider
|
||||
{
|
||||
public interface IIndexProvider
|
||||
{
|
||||
public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.IProvider
|
||||
{
|
||||
public interface IPathIndexProvider
|
||||
{
|
||||
public IAsyncEnumerable<SearchResult> EnumerateAsync(string path, string search, bool recursive, CancellationToken token);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks
|
|||
{
|
||||
private const int quickAccessResultScore = 100;
|
||||
|
||||
internal static List<Result> AccessLinkListMatched(Query query, List<AccessLink> accessLinks)
|
||||
internal static List<Result> AccessLinkListMatched(Query query, IEnumerable<AccessLink> accessLinks)
|
||||
{
|
||||
if (string.IsNullOrEmpty(query.Search))
|
||||
return new List<Result>();
|
||||
|
|
@ -17,7 +17,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks
|
|||
|
||||
var queriedAccessLinks =
|
||||
accessLinks
|
||||
.Where(x => x.Name.Contains(search, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(x => x.Name.Contains(search, StringComparison.OrdinalIgnoreCase) || x.Path.Contains(search, StringComparison.OrdinalIgnoreCase))
|
||||
.OrderBy(x => x.Type)
|
||||
.ThenBy(x => x.Name);
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks
|
|||
}).ToList();
|
||||
}
|
||||
|
||||
internal static List<Result> AccessLinkListAll(Query query, List<AccessLink> accessLinks)
|
||||
internal static List<Result> AccessLinkListAll(Query query, IEnumerable<AccessLink> accessLinks)
|
||||
=> accessLinks
|
||||
.OrderBy(x => x.Type)
|
||||
.ThenBy(x => x.Name)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -35,13 +37,25 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
return $"{keyword}{formatted_path}";
|
||||
}
|
||||
|
||||
internal static Result CreateFolderResult(string title, string subtitle, string path, Query query, int score = 0, bool showIndexState = false, bool windowsIndexed = false)
|
||||
public static Result CreateResult(Query query, SearchResult result)
|
||||
{
|
||||
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),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
internal static Result CreateFolderResult(string title, string subtitle, string path, Query query, int score = 0, bool windowsIndexed = false)
|
||||
{
|
||||
return new Result
|
||||
{
|
||||
Title = title,
|
||||
IcoPath = path,
|
||||
SubTitle = subtitle,
|
||||
SubTitle = Path.GetDirectoryName(path),
|
||||
AutoCompleteText = GetPathWithActionKeyword(path, ResultType.Folder),
|
||||
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData,
|
||||
Action = c =>
|
||||
|
|
@ -61,17 +75,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
}
|
||||
|
||||
Context.API.ChangeQuery(GetPathWithActionKeyword(path, ResultType.Folder));
|
||||
|
||||
|
||||
return false;
|
||||
},
|
||||
Score = score,
|
||||
TitleToolTip = Constants.ToolTipOpenDirectory,
|
||||
TitleToolTip = InternationalizationManager.Instance.GetTranslation("plugin_explorer_plugin_ToolTipOpenDirectory"),
|
||||
SubTitleToolTip = path,
|
||||
ContextData = new SearchResult
|
||||
{
|
||||
Type = ResultType.Folder,
|
||||
FullPath = path,
|
||||
ShowIndexState = showIndexState,
|
||||
WindowsIndexed = windowsIndexed
|
||||
}
|
||||
};
|
||||
|
|
@ -80,15 +93,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
internal static Result CreateDriveSpaceDisplayResult(string path, bool windowsIndexed = false)
|
||||
{
|
||||
var progressBarColor = "#26a0da";
|
||||
int progressValue = 0;
|
||||
var title = string.Empty; // hide title when use progress bar,
|
||||
var driveLetter = path.Substring(0, 1).ToUpper();
|
||||
var driveLetter = path[..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;
|
||||
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;
|
||||
|
||||
progressValue = Convert.ToInt32(UsingSize);
|
||||
int? progressValue = Convert.ToInt32(usingSize);
|
||||
|
||||
if (progressValue >= 90)
|
||||
progressBarColor = "#da2626";
|
||||
|
|
@ -111,15 +123,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
SubTitleToolTip = path,
|
||||
ContextData = new SearchResult
|
||||
{
|
||||
Type = ResultType.Folder,
|
||||
Type = ResultType.Volume,
|
||||
FullPath = path,
|
||||
ShowIndexState = true,
|
||||
WindowsIndexed = windowsIndexed
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static string toReadableSize(long pDrvSize, int pi)
|
||||
private static string ToReadableSize(long pDrvSize, int pi)
|
||||
{
|
||||
int mok = 0;
|
||||
double drvSize = pDrvSize;
|
||||
|
|
@ -140,24 +151,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
else if (mok == 4)
|
||||
Space = " TB";
|
||||
|
||||
var returnStr = string.Format("{0}{1}", Convert.ToInt32(drvSize), Space);
|
||||
var returnStr = $"{Convert.ToInt32(drvSize)}{Space}";
|
||||
if (mok != 0)
|
||||
{
|
||||
switch (pi)
|
||||
returnStr = pi switch
|
||||
{
|
||||
case 1:
|
||||
returnStr = string.Format("{0:F1}{1}", drvSize, Space);
|
||||
break;
|
||||
case 2:
|
||||
returnStr = string.Format("{0:F2}{1}", drvSize, Space);
|
||||
break;
|
||||
case 3:
|
||||
returnStr = string.Format("{0:F3}{1}", drvSize, Space);
|
||||
break;
|
||||
default:
|
||||
returnStr = string.Format("{0}{1}", Convert.ToInt32(drvSize), Space);
|
||||
break;
|
||||
}
|
||||
1 => $"{drvSize:F1}{Space}",
|
||||
2 => $"{drvSize:F2}{Space}",
|
||||
3 => $"{drvSize:F3}{Space}",
|
||||
_ => $"{Convert.ToInt32(drvSize)}{Space}"
|
||||
};
|
||||
}
|
||||
|
||||
return returnStr;
|
||||
|
|
@ -165,25 +168,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)"
|
||||
|
|
@ -195,32 +186,29 @@ 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,
|
||||
ShowIndexState = true,
|
||||
FullPath = path,
|
||||
WindowsIndexed = windowsIndexed
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
internal static Result CreateFileResult(string filePath, Query query, int score = 0, bool showIndexState = false, bool windowsIndexed = false)
|
||||
internal static Result CreateFileResult(string filePath, Query query, int score = 0, bool windowsIndexed = false)
|
||||
{
|
||||
var result = new Result
|
||||
{
|
||||
Title = Path.GetFileName(filePath),
|
||||
SubTitle = filePath,
|
||||
SubTitle = Path.GetDirectoryName(filePath),
|
||||
IcoPath = filePath,
|
||||
AutoCompleteText = GetPathWithActionKeyword(filePath, ResultType.File),
|
||||
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, Path.GetFileName(filePath)).MatchData,
|
||||
|
|
@ -231,7 +219,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
{
|
||||
if (File.Exists(filePath) && c.SpecialKeyState.CtrlPressed && c.SpecialKeyState.ShiftPressed)
|
||||
{
|
||||
Task.Run(() =>
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -264,13 +252,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
return true;
|
||||
},
|
||||
TitleToolTip = Constants.ToolTipOpenContainingFolder,
|
||||
TitleToolTip = InternationalizationManager.Instance.GetTranslation("plugin_explorer_plugin_ToolTipOpenContainingFolder"),
|
||||
SubTitleToolTip = filePath,
|
||||
ContextData = new SearchResult
|
||||
{
|
||||
Type = ResultType.File,
|
||||
FullPath = filePath,
|
||||
ShowIndexState = showIndexState,
|
||||
WindowsIndexed = windowsIndexed
|
||||
}
|
||||
};
|
||||
|
|
@ -278,16 +265,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
}
|
||||
}
|
||||
|
||||
internal class SearchResult
|
||||
{
|
||||
public string FullPath { get; set; }
|
||||
public ResultType Type { get; set; }
|
||||
|
||||
public bool WindowsIndexed { get; set; }
|
||||
|
||||
public bool ShowIndexState { get; set; }
|
||||
}
|
||||
|
||||
public enum ResultType
|
||||
{
|
||||
Volume,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.Everything;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Plugin.Explorer.Exceptions;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search
|
||||
{
|
||||
|
|
@ -29,52 +30,81 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
public bool Equals(Result x, Result y)
|
||||
{
|
||||
return x.SubTitle == y.SubTitle;
|
||||
return x.Title == y.Title && x.SubTitle == y.SubTitle;
|
||||
}
|
||||
|
||||
public int GetHashCode(Result obj)
|
||||
{
|
||||
return obj.SubTitle.GetHashCode();
|
||||
return HashCode.Combine(obj.Title.GetHashCode(), obj.SubTitle?.GetHashCode() ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<List<Result>> SearchAsync(Query query, CancellationToken token)
|
||||
{
|
||||
var querySearch = query.Search;
|
||||
|
||||
var results = new HashSet<Result>(PathEqualityComparator.Instance);
|
||||
|
||||
// This allows the user to type the below action keywords and see/search the list of quick folder links
|
||||
if (ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)
|
||||
|| ActionKeywordMatch(query, Settings.ActionKeyword.QuickAccessActionKeyword)
|
||||
|| ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword))
|
||||
|| ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword)
|
||||
|| ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword)
|
||||
|| ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword))
|
||||
{
|
||||
if (string.IsNullOrEmpty(query.Search))
|
||||
if (string.IsNullOrEmpty(query.Search) && ActionKeywordMatch(query, Settings.ActionKeyword.QuickAccessActionKeyword))
|
||||
return QuickAccess.AccessLinkListAll(query, Settings.QuickAccessLinks);
|
||||
|
||||
var quickaccessLinks = QuickAccess.AccessLinkListMatched(query, Settings.QuickAccessLinks);
|
||||
var quickAccessLinks = QuickAccess.AccessLinkListMatched(query, Settings.QuickAccessLinks);
|
||||
|
||||
results.UnionWith(quickaccessLinks);
|
||||
results.UnionWith(quickAccessLinks);
|
||||
}
|
||||
|
||||
if (IsFileContentSearch(query.ActionKeyword))
|
||||
return await WindowsIndexFileContentSearchAsync(query, querySearch, token).ConfigureAwait(false);
|
||||
|
||||
if (ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword) ||
|
||||
ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword))
|
||||
else
|
||||
{
|
||||
results.UnionWith(await PathSearchAsync(query, token).ConfigureAwait(false));
|
||||
return new List<Result>();
|
||||
}
|
||||
|
||||
if ((ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword) ||
|
||||
ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)) &&
|
||||
querySearch.Length > 0 &&
|
||||
!querySearch.IsLocationPathString())
|
||||
IAsyncEnumerable<SearchResult> searchResults = null;
|
||||
|
||||
bool isPathSearch = query.Search.IsLocationPathString();
|
||||
|
||||
switch (isPathSearch)
|
||||
{
|
||||
results.UnionWith(await WindowsIndexFilesAndFoldersSearchAsync(query, querySearch, token)
|
||||
.ConfigureAwait(false));
|
||||
case true
|
||||
when ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword)
|
||||
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
|
||||
|
||||
results.UnionWith(await PathSearchAsync(query, token).ConfigureAwait(false));
|
||||
|
||||
return results.ToList();
|
||||
|
||||
case false
|
||||
when ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword):
|
||||
|
||||
// Intentionally require enabling of Everything's content search due to its slowness
|
||||
if (Settings.ContentIndexProvider is EverythingSearchManager && !Settings.EnableEverythingContentSearch)
|
||||
return EverythingContentSearchResult(query);
|
||||
|
||||
searchResults = Settings.ContentIndexProvider.ContentSearchAsync("", query.Search, token);
|
||||
|
||||
break;
|
||||
|
||||
case false
|
||||
when ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword)
|
||||
|| ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
|
||||
|
||||
searchResults = Settings.IndexProvider.SearchAsync(query.Search, token);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (searchResults == null)
|
||||
return results.ToList();
|
||||
|
||||
await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false))
|
||||
results.Add(ResultManager.CreateResult(query, search));
|
||||
|
||||
results.RemoveWhere(r => Settings.IndexSearchExcludedSubdirectoryPaths.Any(
|
||||
excludedPath => r.SubTitle.StartsWith(excludedPath.Path, StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
|
|
@ -93,12 +123,31 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
Settings.ActionKeyword.IndexSearchActionKeyword => Settings.IndexSearchKeywordEnabled &&
|
||||
keyword == Settings.IndexSearchActionKeyword,
|
||||
Settings.ActionKeyword.QuickAccessActionKeyword => Settings.QuickAccessKeywordEnabled &&
|
||||
keyword == Settings.QuickAccessActionKeyword,
|
||||
_ => throw new NotImplementedException()
|
||||
keyword == Settings.QuickAccessActionKeyword,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(allowedActionKeyword), allowedActionKeyword, "actionKeyword out of range")
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<Result>> PathSearchAsync(Query query, CancellationToken token = default)
|
||||
private static List<Result> EverythingContentSearchResult(Query query)
|
||||
{
|
||||
return new List<Result>()
|
||||
{
|
||||
new()
|
||||
{
|
||||
Title = "Do you want to enable content search for Everything?",
|
||||
SubTitle = "It can be very slow without index (which is only supported in Everything v1.5+)",
|
||||
IcoPath = "Images/index_error.png",
|
||||
Action = c =>
|
||||
{
|
||||
Settings.EnableEverythingContentSearch = true;
|
||||
Context.API.ChangeQuery(query.RawQuery, true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<List<Result>> PathSearchAsync(Query query, CancellationToken token = default)
|
||||
{
|
||||
var querySearch = query.Search;
|
||||
|
||||
|
|
@ -118,118 +167,68 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath);
|
||||
|
||||
// Check that actual location exists, otherwise directory search will throw directory not found exception
|
||||
if (!FilesFolders.LocationExists(FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath)))
|
||||
if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).LocationExists())
|
||||
return results.ToList();
|
||||
|
||||
var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath);
|
||||
var useIndexSearch = Settings.IndexSearchEngine is Settings.IndexSearchEngineOption.WindowsIndex
|
||||
&& UseWindowsIndexForDirectorySearch(locationPath);
|
||||
|
||||
if (locationPath.EndsWith(":\\"))
|
||||
var retrievedDirectoryPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath);
|
||||
|
||||
results.Add(retrievedDirectoryPath.EndsWith(":\\")
|
||||
? ResultManager.CreateDriveSpaceDisplayResult(retrievedDirectoryPath, useIndexSearch)
|
||||
: ResultManager.CreateOpenCurrentFolderResult(retrievedDirectoryPath, useIndexSearch));
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return new List<Result>();
|
||||
|
||||
IEnumerable<SearchResult> directoryResult;
|
||||
|
||||
var recursiveIndicatorIndex = query.Search.IndexOf('>');
|
||||
|
||||
if (recursiveIndicatorIndex > 0 && Settings.PathEnumerationEngine != Settings.PathEnumerationEngineOption.DirectEnumeration)
|
||||
{
|
||||
results.Add(ResultManager.CreateDriveSpaceDisplayResult(locationPath, useIndexSearch));
|
||||
directoryResult =
|
||||
await Settings.PathEnumerator.EnumerateAsync(
|
||||
query.Search[..recursiveIndicatorIndex],
|
||||
query.Search[(recursiveIndicatorIndex + 1)..],
|
||||
true,
|
||||
token)
|
||||
.ToListAsync(cancellationToken: token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(ResultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch));
|
||||
try
|
||||
{
|
||||
directoryResult = DirectoryInfoSearch.TopLevelDirectorySearch(query, query.Search, token);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new SearchException("DirectoryInfoSearch", e.Message, e);
|
||||
}
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var directoryResult = await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync,
|
||||
DirectoryInfoClassSearch,
|
||||
useIndexSearch,
|
||||
query,
|
||||
locationPath,
|
||||
token).ConfigureAwait(false);
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
results.UnionWith(directoryResult);
|
||||
results.UnionWith(directoryResult.Select(searchResult => ResultManager.CreateResult(query, searchResult)));
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
private async Task<List<Result>> WindowsIndexFileContentSearchAsync(Query query, string querySearchString,
|
||||
CancellationToken token)
|
||||
{
|
||||
var queryConstructor = new QueryConstructor(Settings);
|
||||
public static bool IsFileContentSearch(string actionKeyword) => actionKeyword == Settings.FileContentSearchActionKeyword;
|
||||
|
||||
if (string.IsNullOrEmpty(querySearchString))
|
||||
return new List<Result>();
|
||||
|
||||
return await IndexSearch.WindowsIndexSearchAsync(
|
||||
querySearchString,
|
||||
queryConstructor.CreateQueryHelper,
|
||||
queryConstructor.QueryForFileContentSearch,
|
||||
Settings.IndexSearchExcludedSubdirectoryPaths,
|
||||
query,
|
||||
token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public bool IsFileContentSearch(string actionKeyword)
|
||||
{
|
||||
return actionKeyword == Settings.FileContentSearchActionKeyword;
|
||||
}
|
||||
|
||||
private List<Result> DirectoryInfoClassSearch(Query query, string querySearch, CancellationToken token)
|
||||
{
|
||||
return DirectoryInfoSearch.TopLevelDirectorySearch(query, querySearch, token);
|
||||
}
|
||||
|
||||
public async Task<List<Result>> TopLevelDirectorySearchBehaviourAsync(
|
||||
Func<Query, string, CancellationToken, Task<List<Result>>> windowsIndexSearch,
|
||||
Func<Query, string, CancellationToken, List<Result>> directoryInfoClassSearch,
|
||||
bool useIndexSearch,
|
||||
Query query,
|
||||
string querySearchString,
|
||||
CancellationToken token)
|
||||
{
|
||||
if (!useIndexSearch)
|
||||
return directoryInfoClassSearch(query, querySearchString, token);
|
||||
|
||||
return await windowsIndexSearch(query, querySearchString, token);
|
||||
}
|
||||
|
||||
private async Task<List<Result>> WindowsIndexFilesAndFoldersSearchAsync(Query query, string querySearchString,
|
||||
CancellationToken token)
|
||||
{
|
||||
var queryConstructor = new QueryConstructor(Settings);
|
||||
|
||||
return await IndexSearch.WindowsIndexSearchAsync(
|
||||
querySearchString,
|
||||
queryConstructor.CreateQueryHelper,
|
||||
queryConstructor.QueryForAllFilesAndFolders,
|
||||
Settings.IndexSearchExcludedSubdirectoryPaths,
|
||||
query,
|
||||
token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<List<Result>> WindowsIndexTopLevelFolderSearchAsync(Query query, string path,
|
||||
CancellationToken token)
|
||||
{
|
||||
var queryConstructor = new QueryConstructor(Settings);
|
||||
|
||||
return await IndexSearch.WindowsIndexSearchAsync(
|
||||
path,
|
||||
queryConstructor.CreateQueryHelper,
|
||||
queryConstructor.QueryForTopLevelDirectorySearch,
|
||||
Settings.IndexSearchExcludedSubdirectoryPaths,
|
||||
query,
|
||||
token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private bool UseWindowsIndexForDirectorySearch(string locationPath)
|
||||
{
|
||||
var pathToDirectory = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath);
|
||||
|
||||
if (!Settings.UseWindowsIndexForDirectorySearch)
|
||||
return false;
|
||||
|
||||
if (Settings.IndexSearchExcludedSubdirectoryPaths
|
||||
.Any(x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory)
|
||||
.StartsWith(x.Path, StringComparison.OrdinalIgnoreCase)))
|
||||
return false;
|
||||
|
||||
return IndexSearch.PathIsIndexed(pathToDirectory);
|
||||
return !Settings.IndexSearchExcludedSubdirectoryPaths.Any(
|
||||
x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory).StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))
|
||||
&& WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search
|
||||
{
|
||||
public record struct SearchResult
|
||||
{
|
||||
public string FullPath { get; init; }
|
||||
public ResultType Type { get; init; }
|
||||
public int Score { get; init; }
|
||||
|
||||
public bool WindowsIndexed { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,227 +0,0 @@
|
|||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
||||
using Microsoft.Search.Interop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.OleDb;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
||||
{
|
||||
internal static class IndexSearch
|
||||
{
|
||||
|
||||
// Reserved keywords in oleDB
|
||||
private const string reservedStringPattern = @"^[`\@\@\#\#\*\^,\&\&\/\\\$\%_;\[\]]+$";
|
||||
|
||||
internal static async Task<List<Result>> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
var fileResults = new List<Result>();
|
||||
|
||||
try
|
||||
{
|
||||
await using var conn = new OleDbConnection(connectionString);
|
||||
await conn.OpenAsync(token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
await using var command = new OleDbCommand(indexQueryString, conn);
|
||||
// Results return as an OleDbDataReader.
|
||||
await using var dataReaderResults = await command.ExecuteReaderAsync(token) as OleDbDataReader;
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (dataReaderResults.HasRows)
|
||||
{
|
||||
while (await dataReaderResults.ReadAsync(token))
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value)
|
||||
{
|
||||
// # is URI syntax for the fragment component, need to be encoded so LocalPath returns complete path
|
||||
var encodedFragmentPath = dataReaderResults
|
||||
.GetString(1)
|
||||
.Replace("#", "%23", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var path = new Uri(encodedFragmentPath).LocalPath;
|
||||
|
||||
if (dataReaderResults.GetString(2) == "Directory")
|
||||
{
|
||||
results.Add(ResultManager.CreateFolderResult(
|
||||
dataReaderResults.GetString(0),
|
||||
path,
|
||||
path,
|
||||
query, 0, true, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
fileResults.Add(ResultManager.CreateFileResult(path, query, 0, true, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// return empty result when cancelled
|
||||
return results;
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
// Internal error from ExecuteReader(): Connection closed.
|
||||
LogException("Internal error from ExecuteReader()", e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogException("General error from performing index search", e);
|
||||
}
|
||||
|
||||
results.AddRange(fileResults);
|
||||
|
||||
// Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
|
||||
return results;
|
||||
}
|
||||
|
||||
internal async static Task<List<Result>> WindowsIndexSearchAsync(
|
||||
string searchString,
|
||||
Func<CSearchQueryHelper> createQueryHelper,
|
||||
Func<string, string> constructQuery,
|
||||
List<AccessLink> exclusionList,
|
||||
Query query,
|
||||
CancellationToken token)
|
||||
{
|
||||
var regexMatch = Regex.Match(searchString, reservedStringPattern);
|
||||
|
||||
if (regexMatch.Success)
|
||||
return new List<Result>();
|
||||
|
||||
try
|
||||
{
|
||||
var constructedQuery = constructQuery(searchString);
|
||||
|
||||
return RemoveResultsInExclusionList(
|
||||
await ExecuteWindowsIndexSearchAsync(constructedQuery, createQueryHelper().ConnectionString, query, token).ConfigureAwait(false),
|
||||
exclusionList,
|
||||
token);
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// Occurs because the Windows Indexing (WSearch) is turned off in services and unable to be used by Explorer plugin
|
||||
if (!SearchManager.Settings.WarnWindowsSearchServiceOff)
|
||||
return new List<Result>();
|
||||
|
||||
return ResultForWindexSearchOff(query.RawQuery);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Result> RemoveResultsInExclusionList(List<Result> results, List<AccessLink> exclusionList, CancellationToken token)
|
||||
{
|
||||
var indexExclusionListCount = exclusionList.Count;
|
||||
|
||||
if (indexExclusionListCount == 0)
|
||||
return results;
|
||||
|
||||
var filteredResults = new List<Result>();
|
||||
|
||||
for (var index = 0; index < results.Count; index++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var excludeResult = false;
|
||||
|
||||
for (var i = 0; i < indexExclusionListCount; i++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (results[index].SubTitle.StartsWith(exclusionList[i].Path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
excludeResult = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!excludeResult)
|
||||
filteredResults.Add(results[index]);
|
||||
}
|
||||
|
||||
return filteredResults;
|
||||
}
|
||||
|
||||
internal static bool PathIsIndexed(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var csm = new CSearchManager();
|
||||
var indexManager = csm.GetCatalog("SystemIndex").GetCrawlScopeManager();
|
||||
return indexManager.IncludedInCrawlScope(path) > 0;
|
||||
}
|
||||
catch(COMException)
|
||||
{
|
||||
// Occurs because the Windows Indexing (WSearch) is turned off in services and unable to be used by Explorer plugin
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Result> ResultForWindexSearchOff(string rawQuery)
|
||||
{
|
||||
var api = SearchManager.Context.API;
|
||||
|
||||
return new List<Result>
|
||||
{
|
||||
new Result
|
||||
{
|
||||
Title = api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
|
||||
SubTitle = api.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
|
||||
Action = c =>
|
||||
{
|
||||
SearchManager.Settings.WarnWindowsSearchServiceOff = false;
|
||||
|
||||
var pluginsManagerPlugin= api.GetAllPlugins().FirstOrDefault(x => x.Metadata.ID == "9f8f9b14-2518-4907-b211-35ab6290dee7");
|
||||
|
||||
var actionKeywordCount = pluginsManagerPlugin.Metadata.ActionKeywords.Count;
|
||||
|
||||
if (actionKeywordCount > 1)
|
||||
LogException("PluginsManager's action keyword has increased to more than 1, this does not allow for determining the " +
|
||||
"right action keyword. Explorer's code for managing Windows Search service not running exception needs to be updated",
|
||||
new InvalidOperationException());
|
||||
|
||||
if (MessageBox.Show(string.Format(api.GetTranslation("plugin_explorer_alternative"), Environment.NewLine),
|
||||
api.GetTranslation("plugin_explorer_alternative_title"),
|
||||
MessageBoxButton.YesNo) == MessageBoxResult.Yes
|
||||
&& actionKeywordCount == 1)
|
||||
{
|
||||
api.ChangeQuery(string.Format("{0} install everything", pluginsManagerPlugin.Metadata.ActionKeywords[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clears the warning message because same query string will not alter the displayed result list
|
||||
api.ChangeQuery(string.Empty);
|
||||
|
||||
api.ChangeQuery(rawQuery);
|
||||
}
|
||||
|
||||
var mainWindow = Application.Current.MainWindow;
|
||||
mainWindow.Show();
|
||||
mainWindow.Focus();
|
||||
|
||||
return false;
|
||||
},
|
||||
IcoPath = Constants.ExplorerIconImagePath
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void LogException(string message, Exception e)
|
||||
{
|
||||
#if DEBUG // Please investigate and handle error from index search
|
||||
throw e;
|
||||
#else
|
||||
Log.Exception($"|Flow.Launcher.Plugin.Explorer.IndexSearch|{message}", e);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +1,30 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using Microsoft.Search.Interop;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
||||
{
|
||||
public class QueryConstructor
|
||||
{
|
||||
private readonly Settings settings;
|
||||
private Settings Settings { get; }
|
||||
|
||||
private const string SystemIndex = "SystemIndex";
|
||||
|
||||
public CSearchQueryHelper BaseQueryHelper { get; }
|
||||
|
||||
public QueryConstructor(Settings settings)
|
||||
{
|
||||
this.settings = settings;
|
||||
Settings = settings;
|
||||
BaseQueryHelper = CreateBaseQuery();
|
||||
}
|
||||
|
||||
public CSearchQueryHelper CreateBaseQuery()
|
||||
|
||||
private CSearchQueryHelper CreateBaseQuery()
|
||||
{
|
||||
var baseQuery = CreateQueryHelper();
|
||||
|
||||
// Set the number of results we want. Don't set this property if all results are needed.
|
||||
baseQuery.QueryMaxResults = settings.MaxResult;
|
||||
baseQuery.QueryMaxResults = Settings.MaxResult;
|
||||
|
||||
// Set list of columns we want to display, getting the path presently
|
||||
baseQuery.QuerySelectColumns = "System.FileName, System.ItemUrl, System.ItemType";
|
||||
|
|
@ -32,7 +38,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
return baseQuery;
|
||||
}
|
||||
|
||||
internal CSearchQueryHelper CreateQueryHelper()
|
||||
internal static CSearchQueryHelper CreateQueryHelper()
|
||||
{
|
||||
// This uses the Microsoft.Search.Interop assembly
|
||||
var manager = new CSearchManager();
|
||||
|
|
@ -42,98 +48,67 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
|
||||
// Get the ISearchQueryHelper which will help us to translate AQS --> SQL necessary to query the indexer
|
||||
var queryHelper = catalogManager.GetQueryHelper();
|
||||
|
||||
|
||||
return queryHelper;
|
||||
}
|
||||
|
||||
///<summary>
|
||||
/// Set the required WHERE clause restriction to search on the first level of a specified directory.
|
||||
///</summary>
|
||||
public string QueryWhereRestrictionsForTopLevelDirectorySearch(string path)
|
||||
{
|
||||
var searchDepth = $"directory='file:";
|
||||
|
||||
return QueryWhereRestrictionsFromLocationPath(path, searchDepth);
|
||||
}
|
||||
|
||||
///<summary>
|
||||
/// Set the required WHERE clause restriction to search all files and subfolders of a specified directory.
|
||||
///</summary>
|
||||
public string QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(string path)
|
||||
{
|
||||
var searchDepth = $"scope='file:";
|
||||
|
||||
return QueryWhereRestrictionsFromLocationPath(path, searchDepth);
|
||||
}
|
||||
|
||||
private string QueryWhereRestrictionsFromLocationPath(string path, string searchDepth)
|
||||
{
|
||||
if (path.EndsWith(Constants.DirectorySeperator))
|
||||
return searchDepth + $"{path}'";
|
||||
|
||||
var indexOfSeparator = path.LastIndexOf(Constants.DirectorySeperator);
|
||||
|
||||
var itemName = path.Substring(indexOfSeparator + 1);
|
||||
|
||||
if (itemName.StartsWith(Constants.AllFilesFolderSearchWildcard))
|
||||
itemName = itemName.Substring(1);
|
||||
|
||||
var previousLevelDirectory = path.Substring(0, indexOfSeparator);
|
||||
|
||||
if (string.IsNullOrEmpty(itemName))
|
||||
return $"{searchDepth}{previousLevelDirectory}'";
|
||||
|
||||
return $"(System.FileName LIKE '{itemName}%' OR CONTAINS(System.FileName,'\"{itemName}*\"',1033)) AND {searchDepth}{previousLevelDirectory}'";
|
||||
}
|
||||
public static string TopLevelDirectoryConstraint(ReadOnlySpan<char> path) => $"directory='file:{path}'";
|
||||
public static string RecursiveDirectoryConstraint(ReadOnlySpan<char> path) => $"scope='file:{path}'";
|
||||
|
||||
|
||||
///<summary>
|
||||
/// Search will be performed on all folders and files on the first level of a specified directory.
|
||||
///</summary>
|
||||
public string QueryForTopLevelDirectorySearch(string path)
|
||||
public string Directory(ReadOnlySpan<char> path, ReadOnlySpan<char> searchString = default, bool recursive = false)
|
||||
{
|
||||
string query = "SELECT TOP " + settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE ";
|
||||
var queryConstraint = searchString.IsWhiteSpace() ? "" : $"AND ({FileName} LIKE '{searchString}%' OR CONTAINS({FileName},'\"{searchString}*\"'))";
|
||||
|
||||
if (path.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > path.LastIndexOf(Constants.DirectorySeperator))
|
||||
return query + QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path) + QueryOrderByFileNameRestriction;
|
||||
var scopeConstraint = recursive
|
||||
? RecursiveDirectoryConstraint(path)
|
||||
: TopLevelDirectoryConstraint(path);
|
||||
|
||||
return query + QueryWhereRestrictionsForTopLevelDirectorySearch(path) + QueryOrderByFileNameRestriction;
|
||||
var query = $"SELECT TOP {Settings.MaxResult} {BaseQueryHelper.QuerySelectColumns} FROM {SystemIndex} WHERE {scopeConstraint} {queryConstraint} ORDER BY {FileName}";
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
///<summary>
|
||||
/// Search will be performed on all folders and files based on user's search keywords.
|
||||
///</summary>
|
||||
public string QueryForAllFilesAndFolders(string userSearchString)
|
||||
public string FilesAndFolders(ReadOnlySpan<char> userSearchString)
|
||||
{
|
||||
if (userSearchString.IsWhiteSpace())
|
||||
userSearchString = "*";
|
||||
|
||||
// Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause
|
||||
return CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch
|
||||
+ QueryOrderByFileNameRestriction;
|
||||
return $"{BaseQueryHelper.GenerateSQLFromUserQuery(userSearchString.ToString())} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
|
||||
}
|
||||
|
||||
///<summary>
|
||||
/// Set the required WHERE clause restriction to search for all files and folders.
|
||||
///</summary>
|
||||
public const string QueryWhereRestrictionsForAllFilesAndFoldersSearch = "scope='file:'";
|
||||
public const string RestrictionsForAllFilesAndFoldersSearch = "scope='file:'";
|
||||
|
||||
public const string QueryOrderByFileNameRestriction = " ORDER BY System.FileName";
|
||||
/// <summary>
|
||||
/// Order identifier: file name
|
||||
/// </summary>
|
||||
public const string FileName = "System.FileName";
|
||||
|
||||
|
||||
///<summary>
|
||||
/// Search will be performed on all indexed file contents for the specified search keywords.
|
||||
///</summary>
|
||||
public string QueryForFileContentSearch(string userSearchString)
|
||||
public string FileContent(ReadOnlySpan<char> userSearchString)
|
||||
{
|
||||
string query = "SELECT TOP " + settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE ";
|
||||
string query =
|
||||
$"SELECT TOP {Settings.MaxResult} {BaseQueryHelper.QuerySelectColumns} FROM {SystemIndex} WHERE {RestrictionsForFileContentSearch(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
|
||||
|
||||
return query + QueryWhereRestrictionsForFileContentSearch(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch
|
||||
+ QueryOrderByFileNameRestriction;
|
||||
return query;
|
||||
}
|
||||
|
||||
///<summary>
|
||||
/// Set the required WHERE clause restriction to search within file content.
|
||||
///</summary>
|
||||
public string QueryWhereRestrictionsForFileContentSearch(string searchQuery)
|
||||
{
|
||||
return $"FREETEXT('{searchQuery}')";
|
||||
}
|
||||
public static string RestrictionsForFileContentSearch(ReadOnlySpan<char> searchQuery) => $"FREETEXT('{searchQuery}')";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Microsoft.Search.Interop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.OleDb;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Plugin.Explorer.Exceptions;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
||||
{
|
||||
internal static class WindowsIndex
|
||||
{
|
||||
|
||||
// Reserved keywords in oleDB
|
||||
private static Regex _reservedPatternMatcher = new(@"^[`\@\@\#\#\*\^,\&\&\/\\\$\%_;\[\]]+$", RegexOptions.Compiled);
|
||||
|
||||
private static async IAsyncEnumerable<SearchResult> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, [EnumeratorCancellation] CancellationToken token)
|
||||
{
|
||||
await using var conn = new OleDbConnection(connectionString);
|
||||
await conn.OpenAsync(token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
await using var command = new OleDbCommand(indexQueryString, conn);
|
||||
// Results return as an OleDbDataReader.
|
||||
OleDbDataReader dataReaderAttempt;
|
||||
try
|
||||
{
|
||||
dataReaderAttempt = await command.ExecuteReaderAsync(token) as OleDbDataReader;
|
||||
}
|
||||
catch (OleDbException e)
|
||||
{
|
||||
Log.Exception($"|WindowsIndex.ExecuteWindowsIndexSearchAsync|Failed to execute windows index search query: {indexQueryString}", e);
|
||||
yield break;
|
||||
}
|
||||
await using var dataReader = dataReaderAttempt;
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (dataReader is not { HasRows: true })
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
while (await dataReader.ReadAsync(token))
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (dataReader.GetValue(0) == DBNull.Value || dataReader.GetValue(1) == DBNull.Value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// # is URI syntax for the fragment component, need to be encoded so LocalPath returns complete path
|
||||
var encodedFragmentPath = dataReader
|
||||
.GetString(1)
|
||||
.Replace("#", "%23", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var path = new Uri(encodedFragmentPath).LocalPath;
|
||||
|
||||
yield return new SearchResult
|
||||
{
|
||||
FullPath = path,
|
||||
Type = dataReader.GetString(2) == "Directory" ? ResultType.Folder : ResultType.File,
|
||||
WindowsIndexed = true
|
||||
};
|
||||
}
|
||||
|
||||
// Initial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
|
||||
}
|
||||
|
||||
internal static IAsyncEnumerable<SearchResult> WindowsIndexSearchAsync(
|
||||
string connectionString,
|
||||
string search,
|
||||
CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
return _reservedPatternMatcher.IsMatch(search)
|
||||
? AsyncEnumerable.Empty<SearchResult>()
|
||||
: ExecuteWindowsIndexSearchAsync(search, connectionString, token);
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
throw new SearchException("Windows Index", e.Message, e);
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// Occurs because the Windows Indexing (WSearch) is turned off in services and unable to be used by Explorer plugin
|
||||
|
||||
if (!SearchManager.Settings.WarnWindowsSearchServiceOff)
|
||||
return AsyncEnumerable.Empty<SearchResult>();
|
||||
|
||||
var api = SearchManager.Context.API;
|
||||
|
||||
throw new EngineNotAvailableException(
|
||||
"Windows Index",
|
||||
api.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
|
||||
api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
|
||||
c =>
|
||||
{
|
||||
SearchManager.Settings.WarnWindowsSearchServiceOff = false;
|
||||
|
||||
// Clears the warning message so user is not mistaken that it has not worked
|
||||
api.ChangeQuery(string.Empty);
|
||||
|
||||
return ValueTask.FromResult(false);
|
||||
})
|
||||
{
|
||||
ErrorIcon = Constants.WindowsIndexErrorImagePath
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool PathIsIndexed(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var csm = new CSearchManager();
|
||||
var indexManager = csm.GetCatalog("SystemIndex").GetCrawlScopeManager();
|
||||
return indexManager.IncludedInCrawlScope(path) > 0;
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// Occurs because the Windows Indexing (WSearch) is turned off in services and unable to be used by Explorer plugin
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.IProvider;
|
||||
using Microsoft.Search.Interop;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
||||
{
|
||||
public class WindowsIndexSearchManager : IIndexProvider, IContentIndexProvider, IPathIndexProvider
|
||||
{
|
||||
private Settings Settings { get; }
|
||||
private QueryConstructor QueryConstructor { get; }
|
||||
|
||||
private CSearchQueryHelper QueryHelper { get; }
|
||||
public WindowsIndexSearchManager(Settings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
QueryConstructor = new QueryConstructor(Settings);
|
||||
QueryHelper = QueryConstructor.CreateQueryHelper();
|
||||
}
|
||||
|
||||
private IAsyncEnumerable<SearchResult> WindowsIndexFileContentSearchAsync(
|
||||
ReadOnlySpan<char> querySearchString,
|
||||
CancellationToken token)
|
||||
{
|
||||
if (querySearchString.IsEmpty)
|
||||
return AsyncEnumerable.Empty<SearchResult>();
|
||||
|
||||
return WindowsIndex.WindowsIndexSearchAsync(
|
||||
QueryHelper.ConnectionString,
|
||||
QueryConstructor.FileContent(querySearchString),
|
||||
token);
|
||||
}
|
||||
|
||||
private IAsyncEnumerable<SearchResult> WindowsIndexFilesAndFoldersSearchAsync(
|
||||
ReadOnlySpan<char> querySearchString,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return WindowsIndex.WindowsIndexSearchAsync(
|
||||
QueryHelper.ConnectionString,
|
||||
QueryConstructor.FilesAndFolders(querySearchString),
|
||||
token);
|
||||
}
|
||||
|
||||
private IAsyncEnumerable<SearchResult> WindowsIndexTopLevelFolderSearchAsync(
|
||||
ReadOnlySpan<char> search,
|
||||
ReadOnlySpan<char> path,
|
||||
bool recursive,
|
||||
CancellationToken token)
|
||||
{
|
||||
var queryConstructor = new QueryConstructor(Settings);
|
||||
|
||||
return WindowsIndex.WindowsIndexSearchAsync(
|
||||
QueryConstructor.CreateQueryHelper().ConnectionString,
|
||||
queryConstructor.Directory(path, search, recursive),
|
||||
token);
|
||||
}
|
||||
public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token)
|
||||
{
|
||||
return WindowsIndexFilesAndFoldersSearchAsync(search, token: token);
|
||||
}
|
||||
public IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token)
|
||||
{
|
||||
return WindowsIndexFileContentSearchAsync(contentSearch, token);
|
||||
}
|
||||
public IAsyncEnumerable<SearchResult> EnumerateAsync(string path, string search, bool recursive, CancellationToken token)
|
||||
{
|
||||
return WindowsIndexTopLevelFolderSearchAsync(search, path, recursive, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,15 @@
|
|||
using Flow.Launcher.Plugin.Everything.Everything;
|
||||
using Flow.Launcher.Plugin.Explorer.Search;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.Everything;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.IProvider;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer
|
||||
{
|
||||
|
|
@ -9,14 +17,19 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
{
|
||||
public int MaxResult { get; set; } = 100;
|
||||
|
||||
public List<AccessLink> QuickAccessLinks { get; set; } = new List<AccessLink>();
|
||||
public ObservableCollection<AccessLink> QuickAccessLinks { get; set; } = new();
|
||||
|
||||
// as at v1.7.0 this is to maintain backwards compatibility, need to be removed afterwards.
|
||||
public List<AccessLink> QuickFolderAccessLinks { get; set; } = new List<AccessLink>();
|
||||
public ObservableCollection<AccessLink> IndexSearchExcludedSubdirectoryPaths { get; set; } = new ObservableCollection<AccessLink>();
|
||||
|
||||
public bool UseWindowsIndexForDirectorySearch { get; set; } = false;
|
||||
public string EditorPath { get; set; } = "";
|
||||
|
||||
public string ShellPath { get; set; } = "cmd";
|
||||
|
||||
|
||||
public bool UseLocationAsWorkingDir { get; set; } = false;
|
||||
|
||||
public bool ShowWindowsContextMenu { get; set; } = true;
|
||||
|
||||
public List<AccessLink> IndexSearchExcludedSubdirectoryPaths { get; set; } = new List<AccessLink>();
|
||||
|
||||
public string SearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign;
|
||||
|
||||
|
|
@ -38,8 +51,92 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
|
||||
public bool QuickAccessKeywordEnabled { get; set; }
|
||||
|
||||
|
||||
public bool WarnWindowsSearchServiceOff { get; set; } = true;
|
||||
|
||||
private EverythingSearchManager _everythingManagerInstance;
|
||||
private WindowsIndexSearchManager _windowsIndexSearchManager;
|
||||
|
||||
#region SearchEngine
|
||||
|
||||
private EverythingSearchManager EverythingManagerInstance => _everythingManagerInstance ??= new EverythingSearchManager(this);
|
||||
private WindowsIndexSearchManager WindowsIndexSearchManager => _windowsIndexSearchManager ??= new WindowsIndexSearchManager(this);
|
||||
|
||||
|
||||
public IndexSearchEngineOption IndexSearchEngine { get; set; } = IndexSearchEngineOption.WindowsIndex;
|
||||
[JsonIgnore]
|
||||
public IIndexProvider IndexProvider => IndexSearchEngine switch
|
||||
{
|
||||
IndexSearchEngineOption.Everything => EverythingManagerInstance,
|
||||
IndexSearchEngineOption.WindowsIndex => WindowsIndexSearchManager,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(IndexSearchEngine))
|
||||
};
|
||||
|
||||
public PathEnumerationEngineOption PathEnumerationEngine { get; set; } = PathEnumerationEngineOption.WindowsIndex;
|
||||
|
||||
[JsonIgnore]
|
||||
public IPathIndexProvider PathEnumerator => PathEnumerationEngine switch
|
||||
{
|
||||
PathEnumerationEngineOption.Everything => EverythingManagerInstance,
|
||||
PathEnumerationEngineOption.WindowsIndex => WindowsIndexSearchManager,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(PathEnumerationEngine))
|
||||
};
|
||||
|
||||
public ContentIndexSearchEngineOption ContentSearchEngine { get; set; } = ContentIndexSearchEngineOption.WindowsIndex;
|
||||
[JsonIgnore]
|
||||
public IContentIndexProvider ContentIndexProvider => ContentSearchEngine switch
|
||||
{
|
||||
ContentIndexSearchEngineOption.Everything => EverythingManagerInstance,
|
||||
ContentIndexSearchEngineOption.WindowsIndex => WindowsIndexSearchManager,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(ContentSearchEngine))
|
||||
};
|
||||
|
||||
public enum PathEnumerationEngineOption
|
||||
{
|
||||
[Description("plugin_explorer_engine_windows_index")]
|
||||
WindowsIndex,
|
||||
[Description("plugin_explorer_engine_everything")]
|
||||
Everything,
|
||||
[Description("plugin_explorer_path_enumeration_engine_none")]
|
||||
DirectEnumeration
|
||||
}
|
||||
|
||||
public enum IndexSearchEngineOption
|
||||
{
|
||||
[Description("plugin_explorer_engine_windows_index")]
|
||||
WindowsIndex,
|
||||
[Description("plugin_explorer_engine_everything")]
|
||||
Everything,
|
||||
}
|
||||
|
||||
public enum ContentIndexSearchEngineOption
|
||||
{
|
||||
[Description("plugin_explorer_engine_windows_index")]
|
||||
WindowsIndex,
|
||||
[Description("plugin_explorer_engine_everything")]
|
||||
Everything,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Everything Settings
|
||||
|
||||
public string EverythingInstalledPath { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public SortOption[] SortOptions { get; set; } = Enum.GetValues<SortOption>();
|
||||
|
||||
public SortOption SortOption { get; set; } = SortOption.NAME_ASCENDING;
|
||||
|
||||
public bool EnableEverythingContentSearch { get; set; } = false;
|
||||
|
||||
public bool EverythingEnabled => IndexSearchEngine == IndexSearchEngineOption.Everything ||
|
||||
PathEnumerationEngine == PathEnumerationEngineOption.Everything ||
|
||||
ContentSearchEngine == ContentIndexSearchEngineOption.Everything;
|
||||
|
||||
#endregion
|
||||
|
||||
internal enum ActionKeyword
|
||||
{
|
||||
SearchActionKeyword,
|
||||
|
|
@ -89,4 +186,4 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
_ => throw new ArgumentOutOfRangeException(nameof(actionKeyword), actionKeyword, "ActionKeyword enabled status not defined")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Views
|
||||
{
|
||||
public class ActionKeywordModel : INotifyPropertyChanged
|
||||
{
|
||||
private static Settings _settings;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public static void Init(Settings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
internal ActionKeywordModel(Settings.ActionKeyword actionKeyword, string description)
|
||||
{
|
||||
KeywordProperty = actionKeyword;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public string Description { get; private init; }
|
||||
|
||||
internal Settings.ActionKeyword KeywordProperty { get; }
|
||||
|
||||
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
private string? keyword;
|
||||
|
||||
public string Keyword
|
||||
{
|
||||
get => keyword ??= _settings.GetActionKeyword(KeywordProperty);
|
||||
set
|
||||
{
|
||||
keyword = value;
|
||||
_settings.SetActionKeyword(KeywordProperty, value);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
private bool? enabled;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => enabled ??= _settings.GetActionKeywordEnabled(KeywordProperty);
|
||||
set
|
||||
{
|
||||
enabled = value;
|
||||
_settings.SetActionKeywordEnabled(KeywordProperty, value);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.ViewModels;
|
||||
|
||||
public class EnumBindingModel<T> where T : struct, Enum
|
||||
{
|
||||
public static IReadOnlyList<EnumBindingModel<T>> CreateList()
|
||||
{
|
||||
return Enum.GetValues<T>()
|
||||
.Select(value => new EnumBindingModel<T>
|
||||
{
|
||||
Value = value, LocalizationKey = GetDescriptionAttr(value)
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public EnumBindingModel<T> From(T value)
|
||||
{
|
||||
var name = value.ToString();
|
||||
var description = GetDescriptionAttr(value);
|
||||
|
||||
return new EnumBindingModel<T>
|
||||
{
|
||||
Name = name,
|
||||
LocalizationKey = description,
|
||||
Value = value
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetDescriptionAttr(T source)
|
||||
{
|
||||
var fi = source.GetType().GetField(source.ToString());
|
||||
|
||||
var attributes = (DescriptionAttribute[])fi?.GetCustomAttributes(
|
||||
typeof(DescriptionAttribute), false);
|
||||
|
||||
return attributes is { Length: > 0 } ? attributes[0].Description : source.ToString();
|
||||
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
private string LocalizationKey { get; set; }
|
||||
public string Description => Main.Context.API.GetTranslation(LocalizationKey);
|
||||
public T Value { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
||||
{
|
||||
internal class RelayCommand : ICommand
|
||||
{
|
||||
private Action<object> _action;
|
||||
|
||||
public RelayCommand(Action<object> action)
|
||||
{
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public virtual bool CanExecute(object parameter)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public event EventHandler CanExecuteChanged;
|
||||
|
||||
public virtual void Execute(object parameter)
|
||||
{
|
||||
_action?.Invoke(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,40 @@
|
|||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
#nullable enable
|
||||
using Flow.Launcher.Plugin.Explorer.Search;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.Everything;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
||||
using Flow.Launcher.Plugin.Explorer.Views;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Input;
|
||||
using MessageBox = System.Windows.Forms.MessageBox;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
||||
{
|
||||
public class SettingsViewModel
|
||||
public class SettingsViewModel : BaseModel
|
||||
{
|
||||
internal Settings Settings { get; set; }
|
||||
public Settings Settings { get; set; }
|
||||
|
||||
internal PluginInitContext Context { get; set; }
|
||||
|
||||
public IReadOnlyList<EnumBindingModel<Settings.IndexSearchEngineOption>> IndexSearchEngines { get; set; }
|
||||
public IReadOnlyList<EnumBindingModel<Settings.ContentIndexSearchEngineOption>> ContentIndexSearchEngines { get; set; }
|
||||
public IReadOnlyList<EnumBindingModel<Settings.PathEnumerationEngineOption>> PathEnumerationEngines { get; set; }
|
||||
|
||||
public SettingsViewModel(PluginInitContext context, Settings settings)
|
||||
{
|
||||
Context = context;
|
||||
Settings = settings;
|
||||
|
||||
InitializeEngineSelection();
|
||||
InitializeActionKeywordModels();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -25,11 +43,267 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
Context.API.SaveSettingJsonStorage<Settings>();
|
||||
}
|
||||
|
||||
internal void RemoveLinkFromQuickAccess(AccessLink selectedRow) => Settings.QuickAccessLinks.Remove(selectedRow);
|
||||
#region Engine Selection
|
||||
|
||||
internal void RemoveAccessLinkFromExcludedIndexPaths(AccessLink selectedRow) => Settings.IndexSearchExcludedSubdirectoryPaths.Remove(selectedRow);
|
||||
private EnumBindingModel<Settings.IndexSearchEngineOption> _selectedIndexSearchEngine;
|
||||
private EnumBindingModel<Settings.ContentIndexSearchEngineOption> _selectedContentSearchEngine;
|
||||
private EnumBindingModel<Settings.PathEnumerationEngineOption> _selectedPathEnumerationEngine;
|
||||
|
||||
internal void OpenWindowsIndexingOptions()
|
||||
|
||||
public EnumBindingModel<Settings.IndexSearchEngineOption> SelectedIndexSearchEngine
|
||||
{
|
||||
get => _selectedIndexSearchEngine;
|
||||
set
|
||||
{
|
||||
_selectedIndexSearchEngine = value;
|
||||
Settings.IndexSearchEngine = value.Value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public EnumBindingModel<Settings.ContentIndexSearchEngineOption> SelectedContentSearchEngine
|
||||
{
|
||||
get => _selectedContentSearchEngine;
|
||||
set
|
||||
{
|
||||
_selectedContentSearchEngine = value;
|
||||
Settings.ContentSearchEngine = value.Value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public EnumBindingModel<Settings.PathEnumerationEngineOption> SelectedPathEnumerationEngine
|
||||
{
|
||||
get => _selectedPathEnumerationEngine;
|
||||
set
|
||||
{
|
||||
_selectedPathEnumerationEngine = value;
|
||||
Settings.PathEnumerationEngine = value.Value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(IndexSearchEngines),
|
||||
nameof(ContentIndexSearchEngines),
|
||||
nameof(PathEnumerationEngines),
|
||||
nameof(_selectedIndexSearchEngine),
|
||||
nameof(_selectedContentSearchEngine),
|
||||
nameof(_selectedPathEnumerationEngine))]
|
||||
private void InitializeEngineSelection()
|
||||
{
|
||||
IndexSearchEngines = EnumBindingModel<Settings.IndexSearchEngineOption>.CreateList();
|
||||
ContentIndexSearchEngines = EnumBindingModel<Settings.ContentIndexSearchEngineOption>.CreateList();
|
||||
PathEnumerationEngines = EnumBindingModel<Settings.PathEnumerationEngineOption>.CreateList();
|
||||
|
||||
_selectedIndexSearchEngine = IndexSearchEngines.First(x => x.Value == Settings.IndexSearchEngine);
|
||||
_selectedContentSearchEngine = ContentIndexSearchEngines.First(x => x.Value == Settings.ContentSearchEngine);
|
||||
_selectedPathEnumerationEngine = PathEnumerationEngines.First(x => x.Value == Settings.PathEnumerationEngine);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region ActionKeyword
|
||||
|
||||
[MemberNotNull(nameof(ActionKeywordsModels))]
|
||||
private void InitializeActionKeywordModels()
|
||||
{
|
||||
ActionKeywordsModels = new List<ActionKeywordModel>
|
||||
{
|
||||
new(Settings.ActionKeyword.SearchActionKeyword,
|
||||
Context.API.GetTranslation("plugin_explorer_actionkeywordview_search")),
|
||||
new(Settings.ActionKeyword.FileContentSearchActionKeyword,
|
||||
Context.API.GetTranslation("plugin_explorer_actionkeywordview_filecontentsearch")),
|
||||
new(Settings.ActionKeyword.PathSearchActionKeyword,
|
||||
Context.API.GetTranslation("plugin_explorer_actionkeywordview_pathsearch")),
|
||||
new(Settings.ActionKeyword.IndexSearchActionKeyword,
|
||||
Context.API.GetTranslation("plugin_explorer_actionkeywordview_indexsearch")),
|
||||
new(Settings.ActionKeyword.QuickAccessActionKeyword,
|
||||
Context.API.GetTranslation("plugin_explorer_actionkeywordview_quickaccess"))
|
||||
};
|
||||
}
|
||||
|
||||
public IReadOnlyList<ActionKeywordModel> ActionKeywordsModels { get; set; }
|
||||
|
||||
public ActionKeywordModel? SelectedActionKeyword { get; set; }
|
||||
|
||||
public ICommand EditActionKeywordCommand => new RelayCommand(EditActionKeyword);
|
||||
|
||||
private void EditActionKeyword(object obj)
|
||||
{
|
||||
if (SelectedActionKeyword is not { } actionKeyword)
|
||||
{
|
||||
ShowUnselectedMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
var actionKeywordWindow = new ActionKeywordSetting(actionKeyword, Context.API);
|
||||
|
||||
if (!(actionKeywordWindow.ShowDialog() ?? false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (actionKeyword.Enabled, actionKeywordWindow.KeywordEnabled)
|
||||
{
|
||||
case (true, false):
|
||||
Context.API.RemoveActionKeyword(Context.CurrentPluginMetadata.ID, actionKeyword.Keyword);
|
||||
break;
|
||||
case (true, true):
|
||||
// same keyword will have dialog result false
|
||||
Context.API.RemoveActionKeyword(Context.CurrentPluginMetadata.ID, actionKeyword.Keyword);
|
||||
Context.API.AddActionKeyword(Context.CurrentPluginMetadata.ID, actionKeywordWindow.ActionKeyword);
|
||||
break;
|
||||
case (false, true):
|
||||
Context.API.AddActionKeyword(Context.CurrentPluginMetadata.ID, actionKeywordWindow.ActionKeyword);
|
||||
break;
|
||||
case (false, false):
|
||||
throw new ArgumentException(
|
||||
$"Both false in {nameof(actionKeyword)}.{nameof(actionKeyword.Enabled)} and {nameof(actionKeywordWindow)}.{nameof(actionKeywordWindow.KeywordEnabled)} should suggest that the ShowDialog() result is false");
|
||||
}
|
||||
|
||||
(actionKeyword.Keyword, actionKeyword.Enabled) = (actionKeywordWindow.ActionKeyword, actionKeywordWindow.KeywordEnabled);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AccessLinks
|
||||
|
||||
public AccessLink? SelectedQuickAccessLink { get; set; }
|
||||
public AccessLink? SelectedIndexSearchExcludedPath { get; set; }
|
||||
|
||||
|
||||
|
||||
public ICommand RemoveLinkCommand => new RelayCommand(RemoveLink);
|
||||
public ICommand EditLinkCommand => new RelayCommand(EditLink);
|
||||
public ICommand AddLinkCommand => new RelayCommand(AddLink);
|
||||
|
||||
public void AppendLink(string containerName, AccessLink link)
|
||||
{
|
||||
var container = containerName switch
|
||||
{
|
||||
"QuickAccessLink" => Settings.QuickAccessLinks,
|
||||
"IndexSearchExcludedPaths" => Settings.IndexSearchExcludedSubdirectoryPaths,
|
||||
_ => throw new ArgumentException($"Unknown container name: {containerName}")
|
||||
};
|
||||
container.Add(link);
|
||||
}
|
||||
|
||||
private void EditLink(object commandParameter)
|
||||
{
|
||||
var (selectedLink, collection) = commandParameter switch
|
||||
{
|
||||
"QuickAccessLink" => (SelectedQuickAccessLink, Settings.QuickAccessLinks),
|
||||
"IndexSearchExcludedPaths" => (SelectedIndexSearchExcludedPath, Settings.IndexSearchExcludedSubdirectoryPaths),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(commandParameter))
|
||||
};
|
||||
|
||||
if (selectedLink is null)
|
||||
{
|
||||
ShowUnselectedMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
var path = PromptUserSelectPath(selectedLink.Type,
|
||||
selectedLink.Type == ResultType.Folder
|
||||
? selectedLink.Path
|
||||
: Path.GetDirectoryName(selectedLink.Path));
|
||||
|
||||
if (path is null)
|
||||
return;
|
||||
|
||||
collection.Remove(selectedLink);
|
||||
collection.Add(new AccessLink
|
||||
{
|
||||
Path = path, Type = selectedLink.Type,
|
||||
});
|
||||
}
|
||||
|
||||
private void ShowUnselectedMessage()
|
||||
{
|
||||
var warning = Context.API.GetTranslation("plugin_explorer_make_selection_warning");
|
||||
MessageBox.Show(warning);
|
||||
}
|
||||
|
||||
|
||||
private void AddLink(object commandParameter)
|
||||
{
|
||||
var container = commandParameter switch
|
||||
{
|
||||
"QuickAccessLink" => Settings.QuickAccessLinks,
|
||||
"IndexSearchExcludedPaths" => Settings.IndexSearchExcludedSubdirectoryPaths,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(commandParameter))
|
||||
};
|
||||
|
||||
ArgumentNullException.ThrowIfNull(container);
|
||||
|
||||
var folderBrowserDialog = new FolderBrowserDialog();
|
||||
|
||||
if (folderBrowserDialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var newAccessLink = new AccessLink
|
||||
{
|
||||
Path = folderBrowserDialog.SelectedPath
|
||||
};
|
||||
|
||||
container.Add(newAccessLink);
|
||||
}
|
||||
|
||||
private void RemoveLink(object obj)
|
||||
{
|
||||
if (obj is not string container) return;
|
||||
|
||||
switch (container)
|
||||
{
|
||||
case "QuickAccessLink":
|
||||
if (SelectedQuickAccessLink == null) return;
|
||||
Settings.QuickAccessLinks.Remove(SelectedQuickAccessLink);
|
||||
break;
|
||||
case "IndexSearchExcludedPaths":
|
||||
if (SelectedIndexSearchExcludedPath == null) return;
|
||||
Settings.IndexSearchExcludedSubdirectoryPaths.Remove(SelectedIndexSearchExcludedPath);
|
||||
break;
|
||||
}
|
||||
Save();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string? PromptUserSelectPath(ResultType type, string? initialDirectory = null)
|
||||
{
|
||||
string? path = null;
|
||||
|
||||
if (type is ResultType.Folder)
|
||||
{
|
||||
var folderBrowserDialog = new FolderBrowserDialog();
|
||||
|
||||
if (initialDirectory is not null)
|
||||
folderBrowserDialog.InitialDirectory = initialDirectory;
|
||||
|
||||
if (folderBrowserDialog.ShowDialog() != DialogResult.OK)
|
||||
return path;
|
||||
|
||||
path = folderBrowserDialog.SelectedPath;
|
||||
}
|
||||
else if (type is ResultType.File)
|
||||
{
|
||||
var openFileDialog = new OpenFileDialog();
|
||||
if (initialDirectory is not null)
|
||||
openFileDialog.InitialDirectory = initialDirectory;
|
||||
|
||||
if (openFileDialog.ShowDialog() != DialogResult.OK)
|
||||
return path;
|
||||
|
||||
path = openFileDialog.FileName;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
internal static void OpenWindowsIndexingOptions()
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
|
|
@ -41,27 +315,106 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
Process.Start(psi);
|
||||
}
|
||||
|
||||
internal void UpdateActionKeyword(Settings.ActionKeyword modifiedActionKeyword, string newActionKeyword, string oldActionKeyword)
|
||||
private ICommand? _openEditorPathCommand;
|
||||
|
||||
public ICommand OpenEditorPath => _openEditorPathCommand ??= new RelayCommand(_ =>
|
||||
{
|
||||
PluginManager.ReplaceActionKeyword(Context.CurrentPluginMetadata.ID, oldActionKeyword, newActionKeyword);
|
||||
}
|
||||
var path = PromptUserSelectPath(ResultType.File, Settings.EditorPath != null ? Path.GetDirectoryName(Settings.EditorPath) : null);
|
||||
if (path is null)
|
||||
return;
|
||||
|
||||
internal bool IsActionKeywordAlreadyAssigned(string newActionKeyword)
|
||||
EditorPath = path;
|
||||
});
|
||||
|
||||
private ICommand? _openShellPathCommand;
|
||||
|
||||
public ICommand OpenShellPath => _openShellPathCommand ??= new RelayCommand(_ =>
|
||||
{
|
||||
return PluginManager.ActionKeywordRegistered(newActionKeyword);
|
||||
}
|
||||
var path = PromptUserSelectPath(ResultType.File, Settings.EditorPath != null ? Path.GetDirectoryName(Settings.EditorPath) : null);
|
||||
if (path is null)
|
||||
return;
|
||||
|
||||
internal bool IsNewActionKeywordGlobal(string newActionKeyword) => newActionKeyword == Query.GlobalPluginWildcardSign;
|
||||
ShellPath = path;
|
||||
});
|
||||
|
||||
public bool UseWindowsIndexForDirectorySearch {
|
||||
get
|
||||
{
|
||||
return Settings.UseWindowsIndexForDirectorySearch;
|
||||
}
|
||||
|
||||
public string EditorPath
|
||||
{
|
||||
get => Settings.EditorPath;
|
||||
set
|
||||
{
|
||||
Settings.UseWindowsIndexForDirectorySearch = value;
|
||||
Settings.EditorPath = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string ShellPath
|
||||
{
|
||||
get => Settings.ShellPath;
|
||||
set
|
||||
{
|
||||
Settings.ShellPath = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Everything FastSortWarning
|
||||
|
||||
public Visibility FastSortWarningVisibility
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return EverythingApi.IsFastSortOption(Settings.SortOption) ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
catch (IPCErrorException)
|
||||
{
|
||||
// this error occurs if the Everything service is not running, in this instance show the warning and
|
||||
// update the message to let user know in the settings panel.
|
||||
return Visibility.Visible;
|
||||
}
|
||||
catch (DllNotFoundException)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
public string SortOptionWarningMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
// 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
|
||||
: Context.API.GetTranslation("flowlauncher_plugin_everything_nonfastsort_warning");
|
||||
}
|
||||
catch (IPCErrorException)
|
||||
{
|
||||
return Context.API.GetTranslation("flowlauncher_plugin_everything_is_not_running");
|
||||
}
|
||||
catch (DllNotFoundException)
|
||||
{
|
||||
return Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string EverythingInstalledPath
|
||||
{
|
||||
get => Settings.EverythingInstalledPath;
|
||||
set
|
||||
{
|
||||
Settings.EverythingInstalledPath = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,8 +77,6 @@
|
|||
Text="{DynamicResource plugin_explorer_actionkeyword_current}" />
|
||||
<TextBox
|
||||
Name="TxtCurrentActionKeyword"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Width="135"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
|
|
@ -97,7 +95,7 @@
|
|||
Name="ChkActionKeywordEnabled"
|
||||
Width="auto"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding Enabled}"
|
||||
IsChecked="{Binding KeywordEnabled, Mode=TwoWay}"
|
||||
ToolTip="{DynamicResource plugin_explorer_actionkeyword_enabled_tooltip}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ using Flow.Launcher.Plugin.Explorer.ViewModels;
|
|||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
|
|
@ -11,11 +13,9 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
|||
/// <summary>
|
||||
/// Interaction logic for ActionKeywordSetting.xaml
|
||||
/// </summary>
|
||||
public partial class ActionKeywordSetting : Window
|
||||
public partial class ActionKeywordSetting : INotifyPropertyChanged
|
||||
{
|
||||
private SettingsViewModel settingsViewModel;
|
||||
|
||||
public ActionKeywordView CurrentActionKeyword { get; set; }
|
||||
private ActionKeywordModel CurrentActionKeyword { get; }
|
||||
|
||||
public string ActionKeyword
|
||||
{
|
||||
|
|
@ -23,24 +23,27 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
|||
set
|
||||
{
|
||||
// Set Enable to be true if user change ActionKeyword
|
||||
Enabled = true;
|
||||
actionKeyword = value;
|
||||
KeywordEnabled = true;
|
||||
_ = SetField(ref actionKeyword, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
public bool KeywordEnabled
|
||||
{
|
||||
get => _keywordEnabled;
|
||||
set => SetField(ref _keywordEnabled, value);
|
||||
}
|
||||
|
||||
private string actionKeyword;
|
||||
private readonly IPublicAPI api;
|
||||
private bool _keywordEnabled;
|
||||
|
||||
public ActionKeywordSetting(SettingsViewModel settingsViewModel,
|
||||
ActionKeywordView selectedActionKeyword)
|
||||
public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword, IPublicAPI api)
|
||||
{
|
||||
this.settingsViewModel = settingsViewModel;
|
||||
|
||||
CurrentActionKeyword = selectedActionKeyword;
|
||||
|
||||
this.api = api;
|
||||
ActionKeyword = selectedActionKeyword.Keyword;
|
||||
Enabled = selectedActionKeyword.Enabled;
|
||||
KeywordEnabled = selectedActionKeyword.Enabled;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
|
|
@ -52,56 +55,38 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
|||
if (string.IsNullOrEmpty(ActionKeyword))
|
||||
ActionKeyword = Query.GlobalPluginWildcardSign;
|
||||
|
||||
if (CurrentActionKeyword.Keyword == ActionKeyword && CurrentActionKeyword.Enabled == Enabled)
|
||||
if (CurrentActionKeyword.Keyword == ActionKeyword && CurrentActionKeyword.Enabled == KeywordEnabled)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (ActionKeyword == Query.GlobalPluginWildcardSign)
|
||||
switch (CurrentActionKeyword.KeywordProperty)
|
||||
switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled)
|
||||
{
|
||||
case Settings.ActionKeyword.FileContentSearchActionKeyword:
|
||||
MessageBox.Show(settingsViewModel.Context.API.GetTranslation("plugin_explorer_globalActionKeywordInvalid"));
|
||||
case (Settings.ActionKeyword.FileContentSearchActionKeyword, true):
|
||||
MessageBox.Show(api.GetTranslation("plugin_explorer_globalActionKeywordInvalid"));
|
||||
return;
|
||||
case Settings.ActionKeyword.QuickAccessActionKeyword:
|
||||
MessageBox.Show(settingsViewModel.Context.API.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid"));
|
||||
case (Settings.ActionKeyword.QuickAccessActionKeyword, true):
|
||||
MessageBox.Show(api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid"));
|
||||
return;
|
||||
}
|
||||
|
||||
var oldActionKeyword = CurrentActionKeyword.Keyword;
|
||||
|
||||
if (!Enabled || !settingsViewModel.IsActionKeywordAlreadyAssigned(ActionKeyword))
|
||||
if (!KeywordEnabled || !api.ActionKeywordAssigned(ActionKeyword))
|
||||
{
|
||||
// Update View Data
|
||||
CurrentActionKeyword.Keyword = Enabled == true ? ActionKeyword : Query.GlobalPluginWildcardSign;
|
||||
CurrentActionKeyword.Enabled = Enabled;
|
||||
|
||||
switch (Enabled)
|
||||
{
|
||||
// reset to global so it does not take up an action keyword when disabled
|
||||
// not for null Enable plugin
|
||||
case false when oldActionKeyword != Query.GlobalPluginWildcardSign:
|
||||
settingsViewModel.UpdateActionKeyword(CurrentActionKeyword.KeywordProperty,
|
||||
Query.GlobalPluginWildcardSign, oldActionKeyword);
|
||||
break;
|
||||
default:
|
||||
settingsViewModel.UpdateActionKeyword(CurrentActionKeyword.KeywordProperty,
|
||||
CurrentActionKeyword.Keyword, oldActionKeyword);
|
||||
break;
|
||||
}
|
||||
|
||||
DialogResult = true;
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
// The keyword is not valid, so show message
|
||||
MessageBox.Show(settingsViewModel.Context.API.GetTranslation("newActionKeywordsHasBeenAssigned"));
|
||||
MessageBox.Show(api.GetTranslation("newActionKeywordsHasBeenAssigned"));
|
||||
}
|
||||
|
||||
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
private void TxtCurrentActionKeyword_OnKeyDown(object sender, KeyEventArgs e)
|
||||
|
|
@ -113,5 +98,18 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
private bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(field, value))
|
||||
return false;
|
||||
field = value;
|
||||
OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
using Flow.Launcher.Plugin.Everything.Everything;
|
||||
using Flow.Launcher.Plugin.Explorer.Helper;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Views.Converters;
|
||||
|
||||
public class EnumNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value is SortOption option ? option.GetTranslatedName() : value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -2,20 +2,111 @@
|
|||
x:Class="Flow.Launcher.Plugin.Explorer.Views.ExplorerSettings"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:Flow.Launcher.Plugin.Explorer.Views.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:qa="clr-namespace:Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
xmlns:viewModels="clr-namespace:Flow.Launcher.Plugin.Explorer.ViewModels"
|
||||
xmlns:views="clr-namespace:Flow.Launcher.Plugin.Explorer.Views"
|
||||
d:DataContext="{d:DesignInstance viewModels:SettingsViewModel}"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<DataTemplate x:Key="ListViewTemplateAccessLinks">
|
||||
<Style x:Key="ExplorerTabItem" TargetType="{x:Type TabItem}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TabItem}">
|
||||
|
||||
<Border
|
||||
x:Name="LayoutRoot"
|
||||
Margin="0,0,0,0"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="True">
|
||||
<Grid>
|
||||
<Border
|
||||
x:Name="TabSeparator"
|
||||
Width="0"
|
||||
Margin="{DynamicResource TabViewItemSeparatorMargin}"
|
||||
HorizontalAlignment="Right"
|
||||
BorderBrush="{DynamicResource TabViewItemSeparator}"
|
||||
BorderThickness="1" />
|
||||
<Border
|
||||
x:Name="TabContainer"
|
||||
Grid.Column="1"
|
||||
Padding="{DynamicResource TabViewItemHeaderPadding}"
|
||||
Background="{DynamicResource TabViewItemHeaderBackground}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
ContentSource="Header"
|
||||
Focusable="False"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
TextElement.FontSize="{DynamicResource TabViewItemHeaderFontSize}"
|
||||
TextElement.FontWeight="{TemplateBinding FontWeight}"
|
||||
TextElement.Foreground="{DynamicResource Color05B}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<!-- PointerOver -->
|
||||
<Trigger Property="IsMouseOver" Value="False">
|
||||
<Setter TargetName="TabContainer" Property="Background" Value="{DynamicResource Color06B}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource Color08B}" />
|
||||
</Trigger>
|
||||
<!-- PointerOver -->
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="TabContainer" Property="Background" Value="{DynamicResource Color06B}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource TabViewItemHeaderForegroundPointerOver}" />
|
||||
|
||||
</Trigger>
|
||||
<!-- Selected -->
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="TabContainer" Property="Background" Value="{DynamicResource Color00B}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource Color05B}" />
|
||||
|
||||
<Setter Property="Panel.ZIndex" Value="1" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.FontWeight" Value="SemiBold" />
|
||||
</Trigger>
|
||||
<!-- PointerOverSelected -->
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True" />
|
||||
<Condition Property="IsSelected" Value="True" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter TargetName="TabContainer" Property="Background" Value="{DynamicResource Color00B}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource Color05B}" />
|
||||
|
||||
<Setter Property="Panel.ZIndex" Value="1" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.FontWeight" Value="SemiBold" />
|
||||
</MultiTrigger>
|
||||
<!-- Disabled -->
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource TabViewItemHeaderForegroundDisabled}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<DataTemplate x:Key="ListViewTemplateAccessLinks" DataType="qa:AccessLink">
|
||||
<TextBlock Margin="0,5,0,5" Text="{Binding Path, Mode=OneTime}" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="ListViewTemplateExcludedPaths">
|
||||
<TextBlock Margin="0,5,0,5" Text="{Binding Path, Mode=OneTime}" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="ListViewActionKeywords" DataType="views:ActionKeywordView">
|
||||
<DataTemplate x:Key="ListViewActionKeywords" DataType="{x:Type views:ActionKeywordModel}">
|
||||
<Grid>
|
||||
<TextBlock
|
||||
Margin="0,5,0,0"
|
||||
|
|
@ -37,7 +128,7 @@
|
|||
<TextBlock
|
||||
Margin="250,5,0,0"
|
||||
IsEnabled="{Binding Enabled}"
|
||||
Text="{Binding Keyword, Mode=OneTime}">
|
||||
Text="{Binding Keyword}">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Style.Triggers>
|
||||
|
|
@ -53,102 +144,367 @@
|
|||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<converters:EnumNameConverter x:Key="EnumNameConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="100" />
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer
|
||||
Grid.Row="0"
|
||||
Margin="20,35,0,0"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel>
|
||||
<Expander
|
||||
Name="expActionKeywords"
|
||||
Collapsed="expActionKeywords_Collapsed"
|
||||
Expanded="expActionKeywords_Click"
|
||||
Header="{DynamicResource plugin_explorer_manageactionkeywords_header}">
|
||||
<ListView x:Name="lbxActionKeywords" ItemTemplate="{StaticResource ListViewActionKeywords}" />
|
||||
</Expander>
|
||||
<Expander
|
||||
Name="expAccessLinks"
|
||||
Margin="0,10,0,0"
|
||||
Collapsed="expAccessLinks_Collapsed"
|
||||
Expanded="expAccessLinks_Click"
|
||||
Header="{DynamicResource plugin_explorer_quickaccesslinks_header}">
|
||||
<ListView
|
||||
x:Name="lbxAccessLinks"
|
||||
AllowDrop="True"
|
||||
DragEnter="lbxAccessLinks_DragEnter"
|
||||
Drop="lbxAccessLinks_Drop"
|
||||
ItemTemplate="{StaticResource ListViewTemplateAccessLinks}" />
|
||||
</Expander>
|
||||
<Expander
|
||||
x:Name="expExcludedPaths"
|
||||
Margin="0,10,0,0"
|
||||
Collapsed="expExcludedPaths_Collapsed"
|
||||
Expanded="expExcludedPaths_Click"
|
||||
Header="{DynamicResource plugin_explorer_indexsearchexcludedpaths_header}">
|
||||
<Grid Margin="0">
|
||||
<TabControl
|
||||
x:Name="TabView"
|
||||
MinHeight="0"
|
||||
Background="{DynamicResource Color00B}"
|
||||
BorderThickness="0"
|
||||
SelectedIndex="0"
|
||||
TabStripPlacement="Top">
|
||||
<TabItem
|
||||
Width="Auto"
|
||||
Header="{DynamicResource plugin_explorer_generalsetting_header}"
|
||||
Style="{DynamicResource ExplorerTabItem}">
|
||||
<StackPanel Grid.Row="0" Margin="30,10,0,0">
|
||||
<StackPanel>
|
||||
<CheckBox
|
||||
Name="UseWindowsIndexForDirectorySearch"
|
||||
Margin="12,10,0,0"
|
||||
Content="{DynamicResource plugin_explorer_usewindowsindexfordirectorysearch}"
|
||||
IsChecked="{Binding UseWindowsIndexForDirectorySearch}"
|
||||
ToolTip="{DynamicResource plugin_explorer_usewindowsindexfordirectorysearch_tooltip}" />
|
||||
<ListView
|
||||
x:Name="lbxExcludedPaths"
|
||||
AllowDrop="True"
|
||||
DragEnter="lbxAccessLinks_DragEnter"
|
||||
Drop="lbxAccessLinks_Drop"
|
||||
ItemTemplate="{StaticResource ListViewTemplateExcludedPaths}" />
|
||||
<StackPanel Orientation="Vertical">
|
||||
<CheckBox
|
||||
Margin="0,10,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Content="{DynamicResource plugin_explorer_use_location_as_working_dir}"
|
||||
IsChecked="{Binding Settings.UseLocationAsWorkingDir}" />
|
||||
<StackPanel Margin="0,10,0,10" Orientation="Horizontal">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="A" />
|
||||
<ColumnDefinition Width="*" SharedSizeGroup="B" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="0,6,16,6"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{DynamicResource plugin_explorer_editor_path}"
|
||||
TextBlock.Foreground="{DynamicResource Color05B}" />
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="0,6,6,6"
|
||||
Orientation="Horizontal">
|
||||
<TextBox
|
||||
Width="250"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding EditorPath}"
|
||||
TextWrapping="NoWrap" />
|
||||
<Button
|
||||
MinWidth="50"
|
||||
Margin="5,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenEditorPath}"
|
||||
Content="..." />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,16,6,6"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{DynamicResource plugin_explorer_shell_path}"
|
||||
TextBlock.Foreground="{DynamicResource Color05B}" />
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Orientation="Horizontal">
|
||||
<TextBox
|
||||
Width="250"
|
||||
Margin="0,10,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding ShellPath}"
|
||||
TextWrapping="NoWrap" />
|
||||
<Button
|
||||
MinWidth="50"
|
||||
Margin="5,10,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenShellPath}"
|
||||
Content="..." />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,0,0,5" Orientation="Horizontal">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="C" />
|
||||
<ColumnDefinition Width="*" SharedSizeGroup="D" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="0,0,10,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{DynamicResource plugin_explorer_Index_Search_Engine}"
|
||||
TextBlock.Foreground="{DynamicResource Color05B}" />
|
||||
<ComboBox
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Width="250"
|
||||
Margin="10,15,10,10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{Binding IndexSearchEngines}"
|
||||
SelectedItem="{Binding SelectedIndexSearchEngine}"
|
||||
DisplayMemberPath="Description"/>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,0,10,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{DynamicResource plugin_explorer_Content_Search_Engine}"
|
||||
TextBlock.Foreground="{DynamicResource Color05B}" />
|
||||
<ComboBox
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="250"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{Binding ContentIndexSearchEngines}"
|
||||
SelectedItem="{Binding SelectedContentSearchEngine}"
|
||||
DisplayMemberPath="Description"/>
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="0,0,10,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{DynamicResource plugin_explorer_Directory_Recursive_Search_Engine}"
|
||||
TextBlock.Foreground="{DynamicResource Color05B}" />
|
||||
<ComboBox
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="250"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{Binding PathEnumerationEngines}"
|
||||
DisplayMemberPath="Description"
|
||||
SelectedItem="{Binding SelectedPathEnumerationEngine}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button
|
||||
Margin="0,10,10,20"
|
||||
Padding="20,10,20,10"
|
||||
Click="btnOpenIndexingOptions_Click"
|
||||
Content="{DynamicResource plugin_explorer_Open_Window_Index_Option}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="450" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnIndexingOptions"
|
||||
MinWidth="130"
|
||||
Margin="10"
|
||||
Click="btnOpenIndexingOptions_Click"
|
||||
Content="{DynamicResource plugin_explorer_manageindexoptions}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnDelete"
|
||||
MinWidth="100"
|
||||
Margin="10"
|
||||
Click="btnDelete_Click"
|
||||
Content="{DynamicResource plugin_explorer_delete}" />
|
||||
<Button
|
||||
x:Name="btnEdit"
|
||||
MinWidth="100"
|
||||
Margin="10"
|
||||
Click="btnEdit_Click"
|
||||
Content="{DynamicResource plugin_explorer_edit}" />
|
||||
<Button
|
||||
x:Name="btnAdd"
|
||||
MinWidth="100"
|
||||
Margin="10"
|
||||
Click="btnAdd_Click"
|
||||
Content="{DynamicResource plugin_explorer_add}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</TabItem>
|
||||
<TabItem
|
||||
Width="Auto"
|
||||
Header="{DynamicResource plugin_explorer_everything_setting_header}"
|
||||
Style="{DynamicResource ExplorerTabItem}">
|
||||
<StackPanel Margin="10" Orientation="Vertical">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Grid Margin="20,10,0,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="F" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,0,20,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{DynamicResource plugin_explorer_everything_sort_option}"
|
||||
TextBlock.Foreground="{DynamicResource Color05B}" />
|
||||
<ComboBox
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{Binding Settings.SortOptions, Mode=OneWay}"
|
||||
SelectedItem="{Binding Settings.SortOption}"
|
||||
SelectionChanged="EverythingSortOptionChanged">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<TextBlock Text="{Binding Converter={StaticResource EnumNameConverter}}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="0,15,20,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{DynamicResource plugin_explorer_everything_installed_path}"
|
||||
TextBlock.Foreground="{DynamicResource Color05B}" />
|
||||
<TextBox
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="250"
|
||||
Margin="0,15,0,0"
|
||||
Text="{Binding EverythingInstalledPath}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Name="tbFastSortWarning"
|
||||
Margin="20,10,10,10"
|
||||
VerticalAlignment="Center"
|
||||
DockPanel.Dock="Right"
|
||||
Foreground="Orange"
|
||||
Text="{Binding SortOptionWarningMessage, Mode=OneTime}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding FastSortWarningVisibility, Mode=OneTime}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</TabItem>
|
||||
<TabItem
|
||||
Width="Auto"
|
||||
Header="{DynamicResource plugin_explorer_manageactionkeywords_header}"
|
||||
Style="{DynamicResource ExplorerTabItem}">
|
||||
<DockPanel HorizontalAlignment="Stretch">
|
||||
<Border
|
||||
Margin="10,10,10,5"
|
||||
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
|
||||
BorderThickness="1"
|
||||
DockPanel.Dock="Top">
|
||||
<ListView
|
||||
ItemTemplate="{StaticResource ListViewActionKeywords}"
|
||||
ItemsSource="{Binding ActionKeywordsModels}"
|
||||
SelectedItem="{Binding SelectedActionKeyword}" />
|
||||
</Border>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Right"
|
||||
DockPanel.Dock="Bottom"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
MinWidth="100"
|
||||
Margin="10"
|
||||
Command="{Binding EditActionKeywordCommand}"
|
||||
Content="{DynamicResource plugin_explorer_edit}" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
<TabItem
|
||||
Name="expAccessLinks"
|
||||
Width="Auto"
|
||||
Header="{DynamicResource plugin_explorer_quickaccesslinks_header}"
|
||||
Style="{DynamicResource ExplorerTabItem}">
|
||||
<ScrollViewer>
|
||||
<DockPanel HorizontalAlignment="Stretch">
|
||||
<Border
|
||||
Margin="10,10,10,5"
|
||||
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
|
||||
BorderThickness="1"
|
||||
DockPanel.Dock="Top">
|
||||
<ListView
|
||||
x:Name="lbxAccessLinks"
|
||||
Height="200"
|
||||
AllowDrop="True"
|
||||
BorderThickness="1"
|
||||
DragEnter="lbxAccessLinks_DragEnter"
|
||||
Drop="LbxAccessLinks_OnDrop"
|
||||
ItemTemplate="{StaticResource ListViewTemplateAccessLinks}"
|
||||
ItemsSource="{Binding Settings.QuickAccessLinks}"
|
||||
SelectedItem="{Binding SelectedQuickAccessLink}" />
|
||||
</Border>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Right"
|
||||
DockPanel.Dock="Bottom"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
MinWidth="100"
|
||||
Margin="10"
|
||||
Command="{Binding RemoveLinkCommand}"
|
||||
CommandParameter="QuickAccessLink"
|
||||
Content="{DynamicResource plugin_explorer_delete}" />
|
||||
<Button
|
||||
MinWidth="100"
|
||||
Margin="10"
|
||||
Command="{Binding EditLinkCommand}"
|
||||
CommandParameter="QuickAccessLink"
|
||||
Content="{DynamicResource plugin_explorer_edit}" />
|
||||
<Button
|
||||
MinWidth="100"
|
||||
Margin="10"
|
||||
Command="{Binding AddLinkCommand}"
|
||||
CommandParameter="QuickAccessLink"
|
||||
Content="{DynamicResource plugin_explorer_add}" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem
|
||||
Width="Auto"
|
||||
Header="{DynamicResource plugin_explorer_indexsearchexcludedpaths_header}"
|
||||
Style="{DynamicResource ExplorerTabItem}">
|
||||
<ScrollViewer>
|
||||
<DockPanel HorizontalAlignment="Stretch">
|
||||
<Border
|
||||
Margin="10,10,10,5"
|
||||
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
|
||||
BorderThickness="1"
|
||||
DockPanel.Dock="Top">
|
||||
<ListView
|
||||
Name="lbxExcludedPaths"
|
||||
Height="200"
|
||||
AllowDrop="True"
|
||||
DragEnter="lbxAccessLinks_DragEnter"
|
||||
Drop="LbxExcludedPaths_OnDrop"
|
||||
ItemTemplate="{StaticResource ListViewTemplateAccessLinks}"
|
||||
ItemsSource="{Binding Settings.IndexSearchExcludedSubdirectoryPaths}"
|
||||
SelectedItem="{Binding SelectedIndexSearchExcludedPath}" />
|
||||
</Border>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Right"
|
||||
DockPanel.Dock="Bottom"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
MinWidth="100"
|
||||
Margin="10"
|
||||
Command="{Binding RemoveLinkCommand}"
|
||||
CommandParameter="IndexSearchExcludedPaths"
|
||||
Content="{DynamicResource plugin_explorer_delete}" />
|
||||
<Button
|
||||
MinWidth="100"
|
||||
Margin="10"
|
||||
Command="{Binding EditLinkCommand}"
|
||||
CommandParameter="IndexSearchExcludedPaths"
|
||||
Content="{DynamicResource plugin_explorer_edit}" />
|
||||
<Button
|
||||
MinWidth="100"
|
||||
Margin="10"
|
||||
Command="{Binding AddLinkCommand}"
|
||||
CommandParameter="IndexSearchExcludedPaths"
|
||||
Content="{DynamicResource plugin_explorer_add}" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
|
@ -1,16 +1,14 @@
|
|||
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
||||
using Flow.Launcher.Plugin.Explorer.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Controls;
|
||||
using DataFormats = System.Windows.DataFormats;
|
||||
using DragDropEffects = System.Windows.DragDropEffects;
|
||||
using DragEventArgs = System.Windows.DragEventArgs;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Views
|
||||
{
|
||||
|
|
@ -21,280 +19,46 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
|||
{
|
||||
private readonly SettingsViewModel viewModel;
|
||||
|
||||
private List<ActionKeywordView> actionKeywordsListView;
|
||||
private List<ActionKeywordModel> actionKeywordsListView;
|
||||
|
||||
|
||||
public ExplorerSettings(SettingsViewModel viewModel)
|
||||
{
|
||||
DataContext = viewModel;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
this.viewModel = viewModel;
|
||||
|
||||
DataContext = viewModel;
|
||||
|
||||
lbxAccessLinks.ItemsSource = this.viewModel.Settings.QuickAccessLinks;
|
||||
ActionKeywordModel.Init(viewModel.Settings);
|
||||
|
||||
lbxExcludedPaths.ItemsSource = this.viewModel.Settings.IndexSearchExcludedSubdirectoryPaths;
|
||||
|
||||
actionKeywordsListView = new List<ActionKeywordView>
|
||||
{
|
||||
new(Settings.ActionKeyword.SearchActionKeyword,
|
||||
viewModel.Context.API.GetTranslation("plugin_explorer_actionkeywordview_search")),
|
||||
new(Settings.ActionKeyword.FileContentSearchActionKeyword,
|
||||
viewModel.Context.API.GetTranslation("plugin_explorer_actionkeywordview_filecontentsearch")),
|
||||
new(Settings.ActionKeyword.PathSearchActionKeyword,
|
||||
viewModel.Context.API.GetTranslation("plugin_explorer_actionkeywordview_pathsearch")),
|
||||
new(Settings.ActionKeyword.IndexSearchActionKeyword,
|
||||
viewModel.Context.API.GetTranslation("plugin_explorer_actionkeywordview_indexsearch")),
|
||||
new(Settings.ActionKeyword.QuickAccessActionKeyword,
|
||||
viewModel.Context.API.GetTranslation("plugin_explorer_actionkeywordview_quickaccess"))
|
||||
};
|
||||
|
||||
lbxActionKeywords.ItemsSource = actionKeywordsListView;
|
||||
|
||||
ActionKeywordView.Init(viewModel.Settings);
|
||||
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
public void RefreshView()
|
||||
{
|
||||
lbxAccessLinks.Items.SortDescriptions.Add(new SortDescription("Path", ListSortDirection.Ascending));
|
||||
|
||||
lbxExcludedPaths.Items.SortDescriptions.Add(new SortDescription("Path", ListSortDirection.Ascending));
|
||||
}
|
||||
|
||||
SetButtonVisibilityToHidden();
|
||||
|
||||
if (expAccessLinks.IsExpanded || expExcludedPaths.IsExpanded || expActionKeywords.IsExpanded)
|
||||
|
||||
private void AccessLinkDragDrop(string containerName, DragEventArgs e)
|
||||
{
|
||||
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
|
||||
|
||||
if (files == null || !files.Any())
|
||||
{
|
||||
if (!expActionKeywords.IsExpanded)
|
||||
btnAdd.Visibility = Visibility.Visible;
|
||||
|
||||
if (expActionKeywords.IsExpanded
|
||||
&& btnEdit.Visibility == Visibility.Hidden)
|
||||
btnEdit.Visibility = Visibility.Visible;
|
||||
|
||||
if (lbxAccessLinks.Items.Count == 0 && lbxExcludedPaths.Items.Count == 0
|
||||
&& btnDelete.Visibility == Visibility.Visible
|
||||
&& btnEdit.Visibility == Visibility.Visible)
|
||||
{
|
||||
btnDelete.Visibility = Visibility.Hidden;
|
||||
btnEdit.Visibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
if (expAccessLinks.IsExpanded
|
||||
&& lbxAccessLinks.Items.Count > 0
|
||||
&& btnDelete.Visibility == Visibility.Hidden
|
||||
&& btnEdit.Visibility == Visibility.Hidden)
|
||||
{
|
||||
btnDelete.Visibility = Visibility.Visible;
|
||||
btnEdit.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
if (expExcludedPaths.IsExpanded
|
||||
&& lbxExcludedPaths.Items.Count > 0
|
||||
&& btnDelete.Visibility == Visibility.Hidden
|
||||
&& btnEdit.Visibility == Visibility.Hidden)
|
||||
{
|
||||
btnDelete.Visibility = Visibility.Visible;
|
||||
btnEdit.Visibility = Visibility.Visible;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
lbxAccessLinks.Items.Refresh();
|
||||
|
||||
lbxExcludedPaths.Items.Refresh();
|
||||
|
||||
lbxActionKeywords.Items.Refresh();
|
||||
}
|
||||
|
||||
private void expActionKeywords_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (expActionKeywords.IsExpanded)
|
||||
expActionKeywords.Height = 205;
|
||||
|
||||
if (expExcludedPaths.IsExpanded)
|
||||
expExcludedPaths.IsExpanded = false;
|
||||
|
||||
if (expAccessLinks.IsExpanded)
|
||||
expAccessLinks.IsExpanded = false;
|
||||
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
private void expActionKeywords_Collapsed(object sender, RoutedEventArgs e)
|
||||
{
|
||||
expActionKeywords.Height = double.NaN;
|
||||
SetButtonVisibilityToHidden();
|
||||
}
|
||||
|
||||
private void expAccessLinks_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (expAccessLinks.IsExpanded)
|
||||
expAccessLinks.Height = 205;
|
||||
|
||||
if (expExcludedPaths.IsExpanded)
|
||||
expExcludedPaths.IsExpanded = false;
|
||||
|
||||
if (expActionKeywords.IsExpanded)
|
||||
expActionKeywords.IsExpanded = false;
|
||||
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
private void expAccessLinks_Collapsed(object sender, RoutedEventArgs e)
|
||||
{
|
||||
expAccessLinks.Height = double.NaN;
|
||||
SetButtonVisibilityToHidden();
|
||||
}
|
||||
|
||||
private void expExcludedPaths_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (expExcludedPaths.IsExpanded)
|
||||
expAccessLinks.Height = double.NaN;
|
||||
|
||||
if (expAccessLinks.IsExpanded)
|
||||
expAccessLinks.IsExpanded = false;
|
||||
|
||||
if (expActionKeywords.IsExpanded)
|
||||
expActionKeywords.IsExpanded = false;
|
||||
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
private void expExcludedPaths_Collapsed(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetButtonVisibilityToHidden();
|
||||
}
|
||||
|
||||
private void btnDelete_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectedRow = lbxAccessLinks.SelectedItem as AccessLink ?? lbxExcludedPaths.SelectedItem as AccessLink;
|
||||
|
||||
if (selectedRow != null)
|
||||
foreach (var s in files)
|
||||
{
|
||||
string msg = string.Format(viewModel.Context.API.GetTranslation("plugin_explorer_delete_folder_link"),
|
||||
selectedRow.Path);
|
||||
|
||||
if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
if (Directory.Exists(s))
|
||||
{
|
||||
if (expAccessLinks.IsExpanded)
|
||||
viewModel.RemoveLinkFromQuickAccess(selectedRow);
|
||||
|
||||
if (expExcludedPaths.IsExpanded)
|
||||
viewModel.RemoveAccessLinkFromExcludedIndexPaths(selectedRow);
|
||||
|
||||
RefreshView();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string warning = viewModel.Context.API.GetTranslation("plugin_explorer_select_folder_link_warning");
|
||||
MessageBox.Show(warning);
|
||||
}
|
||||
}
|
||||
|
||||
private void btnEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (lbxActionKeywords.SelectedItem is ActionKeywordView)
|
||||
{
|
||||
var selectedActionKeyword = lbxActionKeywords.SelectedItem as ActionKeywordView;
|
||||
|
||||
var actionKeywordWindow = new ActionKeywordSetting(viewModel,
|
||||
selectedActionKeyword);
|
||||
|
||||
actionKeywordWindow.ShowDialog();
|
||||
|
||||
RefreshView();
|
||||
}
|
||||
else
|
||||
{
|
||||
var selectedRow = lbxAccessLinks.SelectedItem as AccessLink ??
|
||||
lbxExcludedPaths.SelectedItem as AccessLink;
|
||||
|
||||
if (selectedRow != null)
|
||||
{
|
||||
var folderBrowserDialog = new FolderBrowserDialog();
|
||||
folderBrowserDialog.SelectedPath = selectedRow.Path;
|
||||
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
|
||||
var newFolderLink = new AccessLink
|
||||
{
|
||||
if (expAccessLinks.IsExpanded)
|
||||
{
|
||||
var link = viewModel.Settings.QuickAccessLinks.First(x => x.Path == selectedRow.Path);
|
||||
link.Path = folderBrowserDialog.SelectedPath;
|
||||
}
|
||||
|
||||
if (expExcludedPaths.IsExpanded)
|
||||
{
|
||||
var link = viewModel.Settings.IndexSearchExcludedSubdirectoryPaths.First(x =>
|
||||
x.Path == selectedRow.Path);
|
||||
link.Path = folderBrowserDialog.SelectedPath;
|
||||
}
|
||||
}
|
||||
|
||||
RefreshView();
|
||||
Path = s
|
||||
};
|
||||
viewModel.AppendLink(containerName, newFolderLink);
|
||||
}
|
||||
else
|
||||
{
|
||||
string warning = viewModel.Context.API.GetTranslation("plugin_explorer_make_selection_warning");
|
||||
MessageBox.Show(warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void btnAdd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var folderBrowserDialog = new FolderBrowserDialog();
|
||||
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
var newAccessLink = new AccessLink {Path = folderBrowserDialog.SelectedPath};
|
||||
|
||||
AddAccessLink(newAccessLink);
|
||||
}
|
||||
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
private void lbxAccessLinks_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
|
||||
|
||||
if (files != null && files.Count() > 0)
|
||||
{
|
||||
if (expAccessLinks.IsExpanded && viewModel.Settings.QuickAccessLinks == null)
|
||||
viewModel.Settings.QuickAccessLinks = new List<AccessLink>();
|
||||
|
||||
foreach (string s in files)
|
||||
{
|
||||
if (Directory.Exists(s))
|
||||
{
|
||||
var newFolderLink = new AccessLink {Path = s};
|
||||
|
||||
AddAccessLink(newFolderLink);
|
||||
}
|
||||
|
||||
RefreshView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddAccessLink(AccessLink newAccessLink)
|
||||
{
|
||||
if (expAccessLinks.IsExpanded
|
||||
&& !viewModel.Settings.QuickAccessLinks.Any(x => x.Path == newAccessLink.Path))
|
||||
{
|
||||
if (viewModel.Settings.QuickAccessLinks == null)
|
||||
viewModel.Settings.QuickAccessLinks = new List<AccessLink>();
|
||||
|
||||
viewModel.Settings.QuickAccessLinks.Add(newAccessLink);
|
||||
}
|
||||
|
||||
if (expExcludedPaths.IsExpanded
|
||||
&& !viewModel.Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x.Path == newAccessLink.Path))
|
||||
{
|
||||
if (viewModel.Settings.IndexSearchExcludedSubdirectoryPaths == null)
|
||||
viewModel.Settings.IndexSearchExcludedSubdirectoryPaths = new List<AccessLink>();
|
||||
|
||||
viewModel.Settings.IndexSearchExcludedSubdirectoryPaths.Add(newAccessLink);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -312,46 +76,23 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
|||
|
||||
private void btnOpenIndexingOptions_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
viewModel.OpenWindowsIndexingOptions();
|
||||
SettingsViewModel.OpenWindowsIndexingOptions();
|
||||
}
|
||||
|
||||
public void SetButtonVisibilityToHidden()
|
||||
private void EverythingSortOptionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
btnDelete.Visibility = Visibility.Hidden;
|
||||
btnEdit.Visibility = Visibility.Hidden;
|
||||
btnAdd.Visibility = Visibility.Hidden;
|
||||
if (tbFastSortWarning is not null)
|
||||
{
|
||||
tbFastSortWarning.Visibility = viewModel.FastSortWarningVisibility;
|
||||
tbFastSortWarning.Text = viewModel.SortOptionWarningMessage;
|
||||
}
|
||||
}
|
||||
private void LbxAccessLinks_OnDrop(object sender, DragEventArgs e)
|
||||
{
|
||||
AccessLinkDragDrop("QuickAccessLink", e);
|
||||
}
|
||||
private void LbxExcludedPaths_OnDrop(object sender, DragEventArgs e)
|
||||
{
|
||||
AccessLinkDragDrop("IndexSearchExcludedPath", e);
|
||||
}
|
||||
}
|
||||
|
||||
public class ActionKeywordView
|
||||
{
|
||||
private static Settings _settings;
|
||||
|
||||
public static void Init(Settings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
internal ActionKeywordView(Settings.ActionKeyword actionKeyword, string description)
|
||||
{
|
||||
KeywordProperty = actionKeyword;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public string Description { get; private init; }
|
||||
|
||||
internal Settings.ActionKeyword KeywordProperty { get; }
|
||||
|
||||
public string Keyword
|
||||
{
|
||||
get => _settings.GetActionKeyword(KeywordProperty);
|
||||
set => _settings.SetActionKeyword(KeywordProperty, value);
|
||||
}
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _settings.GetActionKeywordEnabled(KeywordProperty);
|
||||
set => _settings.SetActionKeywordEnabled(KeywordProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
<RootNamespace>Flow.Launcher.Plugin.Shell</RootNamespace>
|
||||
<AssemblyName>Flow.Launcher.Plugin.Shell</AssemblyName>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<UseWpf>true</UseWpf>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
|
|
@ -54,10 +55,6 @@
|
|||
<Content Include="Images\*.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Page Include="ShellSetting.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
<RootNamespace>Flow.Launcher.Plugin.Sys</RootNamespace>
|
||||
<AssemblyName>Flow.Launcher.Plugin.Sys</AssemblyName>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<UseWpf>true</UseWpf>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
|
|
@ -48,10 +49,6 @@
|
|||
<Content Include="Images\*.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Page Include="SysSettings.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||