diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
index fa3f10fa7..87c390d34 100644
--- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj
+++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
@@ -57,10 +57,7 @@
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs
index 35486e794..513d85c96 100644
--- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs
+++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs
@@ -1,11 +1,12 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
+using System.Threading.Tasks;
+using System.Windows.Forms;
using Flow.Launcher.Infrastructure;
-using Flow.Launcher.Infrastructure.Exception;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
@@ -29,6 +30,8 @@ namespace Flow.Launcher.Core.Plugin
public static IEnumerable DotNetPlugins(List source)
{
+ var erroredPlugins = new List();
+
var plugins = new List();
var metadatas = source.Where(o => AllowedLanguage.IsDotNet(o.Language));
@@ -50,20 +53,34 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
- Log.Exception($"|PluginsLoader.DotNetPlugins|Couldn't load assembly for {metadata.Name}", e);
+ erroredPlugins.Add(metadata.Name);
+
+ Log.Exception($"|PluginsLoader.DotNetPlugins|Couldn't load assembly for the plugin: {metadata.Name}", e);
return;
}
- var types = assembly.GetTypes();
+
Type type;
try
{
+ var types = assembly.GetTypes();
+
type = types.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(typeof(IPlugin)));
}
catch (InvalidOperationException e)
{
- Log.Exception($"|PluginsLoader.DotNetPlugins|Can't find class implement IPlugin for <{metadata.Name}>", e);
+ erroredPlugins.Add(metadata.Name);
+
+ Log.Exception($"|PluginsLoader.DotNetPlugins|Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
return;
}
+ catch (ReflectionTypeLoadException e)
+ {
+ erroredPlugins.Add(metadata.Name);
+
+ Log.Exception($"|PluginsLoader.DotNetPlugins|The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
+ return;
+ }
+
IPlugin plugin;
try
{
@@ -71,7 +88,9 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
- Log.Exception($"|PluginsLoader.DotNetPlugins|Can't create instance for <{metadata.Name}>", e);
+ erroredPlugins.Add(metadata.Name);
+
+ Log.Exception($"|PluginsLoader.DotNetPlugins|The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
return;
}
#endif
@@ -85,6 +104,26 @@ namespace Flow.Launcher.Core.Plugin
metadata.InitTime += milliseconds;
}
+
+ if (erroredPlugins.Count > 0)
+ {
+ var errorPluginString = "";
+
+ var errorMessage = "The following "
+ + (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ")
+ + "errored and cannot be loaded:";
+
+ erroredPlugins.ForEach(x => errorPluginString += x + Environment.NewLine);
+
+ Task.Run(() =>
+ {
+ MessageBox.Show($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
+ $"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
+ $"Please refer to the logs for more information","",
+ MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ });
+ }
+
return plugins;
}
diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
index 82fbb31d5..ff4700e94 100644
--- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
+++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
@@ -1,4 +1,4 @@
-
+
netcoreapp3.1
@@ -12,7 +12,26 @@
false
false
-
+
+
+ 1.0.0
+ 1.0.0-beta3
+ 1.0.0
+ 1.0.0
+ Flow.Launcher.Plugin
+ Flow-Launcher
+ MIT
+ https://github.com/Flow-Launcher/Flow.Launcher
+ Reference this library if you want to develop a Flow Launcher plugin
+ flowlauncher
+ true
+ true
+
+
+
+ true
+
+
true
full
@@ -26,7 +45,7 @@
- pdbonly
+ embedded
true
..\Output\Release\
TRACE
@@ -37,28 +56,15 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
index 17c2dc511..ce067e8a0 100644
--- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
+++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
@@ -127,5 +127,81 @@ namespace Flow.Launcher.Plugin.SharedCommands
#endif
}
}
+
+ ///
+ /// This checks whether a given string is a directory path or network location string.
+ /// It does not check if location actually exists.
+ ///
+ public static bool IsLocationPathString(string querySearchString)
+ {
+ if (string.IsNullOrEmpty(querySearchString))
+ return false;
+
+ // // shared folder location, and not \\\location\
+ if (querySearchString.Length >= 3
+ && querySearchString.StartsWith(@"\\")
+ && char.IsLetter(querySearchString[2]))
+ return true;
+
+ // c:\
+ if (querySearchString.Length == 3
+ && char.IsLetter(querySearchString[0])
+ && querySearchString[1] == ':'
+ && querySearchString[2] == '\\')
+ return true;
+
+ // c:\\
+ if (querySearchString.Length >= 4
+ && char.IsLetter(querySearchString[0])
+ && querySearchString[1] == ':'
+ && querySearchString[2] == '\\'
+ && char.IsLetter(querySearchString[3]))
+ return true;
+
+ return false;
+ }
+
+ ///
+ /// Gets the previous level directory from a path string.
+ /// Checks that previous level directory exists and returns it
+ /// as a path string, or empty string if doesn't exit
+ ///
+ public static string GetPreviousExistingDirectory(Func locationExists, string path)
+ {
+ var previousDirectoryPath = "";
+ var index = path.LastIndexOf('\\');
+ if (index > 0 && index < (path.Length - 1))
+ {
+ previousDirectoryPath = path.Substring(0, index + 1);
+ if (!locationExists(previousDirectoryPath))
+ {
+ return "";
+ }
+ }
+ else
+ {
+ return "";
+ }
+
+ return previousDirectoryPath;
+ }
+
+ ///
+ /// Returns the previous level directory if path incomplete (does not end with '\').
+ /// Does not check if previous level directory exists.
+ /// Returns passed in string if is complete path
+ ///
+ public static string ReturnPreviousDirectoryIfIncompleteString(string path)
+ {
+ if (!path.EndsWith("\\"))
+ {
+ // not full path, get previous level directory string
+ var indexOfSeparator = path.LastIndexOf('\\');
+
+ return path.Substring(0, indexOfSeparator + 1);
+ }
+
+ return path;
+ }
}
}
diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj
index 2dbf1a9d3..e970c47b9 100644
--- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj
+++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj
@@ -39,6 +39,7 @@
+
@@ -56,8 +57,4 @@
-
-
-
-
\ No newline at end of file
diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs
new file mode 100644
index 000000000..0a1dece4f
--- /dev/null
+++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs
@@ -0,0 +1,279 @@
+using Flow.Launcher.Plugin;
+using Flow.Launcher.Plugin.Explorer;
+using Flow.Launcher.Plugin.Explorer.Search;
+using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo;
+using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex;
+using Flow.Launcher.Plugin.SharedCommands;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+
+namespace Flow.Launcher.Test.Plugins
+{
+ ///
+ /// These tests require the use of CSearchManager class from Microsoft.Search.Interop.
+ /// Windows Search service needs to be running to complete the tests
+ ///
+ [TestFixture]
+ public class ExplorerTest
+ {
+ private List MethodWindowsIndexSearchReturnsZeroResults(Query dummyQuery, string dummyString)
+ {
+ return new List();
+ }
+
+ private List MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString)
+ {
+ return new List
+ {
+ new Result
+ {
+ Title="Result 1"
+ },
+
+ new Result
+ {
+ Title="Result 2"
+ }
+ };
+ }
+
+ private bool PreviousLocationExistsReturnsTrue(string dummyString) => true;
+
+ private bool PreviousLocationNotExistReturnsFalse(string dummyString) => false;
+
+ [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);
+
+ // Then
+ Assert.IsTrue(result == expectedString,
+ $"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
+ $"Actual: {result}{Environment.NewLine}");
+ }
+
+ [TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType FROM SystemIndex WHERE directory='file:C:\\'")]
+ [TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\'")]
+ public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString)
+ {
+ // Given
+ var queryConstructor = new QueryConstructor(new Settings());
+
+ //When
+ var queryString = queryConstructor.QueryForTopLevelDirectorySearch(folderPath);
+
+ // Then
+ Assert.IsTrue(queryString == expectedString,
+ $"Expected string: {expectedString}{Environment.NewLine} " +
+ $"Actual string was: {queryString}{Environment.NewLine}");
+ }
+
+ [TestCase("C:\\SomeFolder\\flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType " +
+ "FROM SystemIndex WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
+ "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033))" +
+ " AND directory='file:C:\\SomeFolder'")]
+ public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString(
+ string userSearchString, string expectedString)
+ {
+ // Given
+ var queryConstructor = new QueryConstructor(new Settings());
+
+ //When
+ var queryString = queryConstructor.QueryForTopLevelDirectorySearch(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}");
+ }
+
+ [TestCase("scope='file:'")]
+ public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString)
+ {
+ // Given
+ var queryConstructor = new QueryConstructor(new Settings());
+
+ //When
+ var resultString = queryConstructor.QueryWhereRestrictionsForAllFilesAndFoldersSearch();
+
+ // Then
+ Assert.IsTrue(resultString == expectedString,
+ $"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
+ $"Actual string was: {resultString}{Environment.NewLine}");
+ }
+
+ [TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemPathDisplay\", \"System.ItemType\" " +
+ "FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
+ "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:'")]
+ public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString(
+ string userSearchString, string expectedString)
+ {
+ // Given
+ var queryConstructor = new QueryConstructor(new Settings());
+
+ //When
+ var resultString = queryConstructor.QueryForAllFilesAndFolders(userSearchString);
+
+ // Then
+ Assert.IsTrue(resultString == expectedString,
+ $"Expected query string: {expectedString}{Environment.NewLine} " +
+ $"Actual string was: {resultString}{Environment.NewLine}");
+ }
+
+ [TestCase]
+ public void GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldContinueDirectoryInfoClassSearch()
+ {
+ // Given
+ var searchManager = new SearchManager(new Settings(), new PluginInitContext());
+
+ // When
+ var results = searchManager.TopLevelDirectorySearchBehaviour(
+ MethodWindowsIndexSearchReturnsZeroResults,
+ MethodDirectoryInfoClassSearchReturnsTwoResults,
+ false,
+ new Query(),
+ "string not used");
+
+ // 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 void GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearch()
+ {
+ // Given
+ var searchManager = new SearchManager(new Settings(), new PluginInitContext());
+
+ // When
+ var results = searchManager.TopLevelDirectorySearchBehaviour(
+ MethodWindowsIndexSearchReturnsZeroResults,
+ MethodDirectoryInfoClassSearchReturnsTwoResults,
+ true,
+ new Query(),
+ "string not used");
+
+ // 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}");
+ }
+
+ [TestCase(@"c:\\", false)]
+ [TestCase(@"i:\", true)]
+ [TestCase(@"\c:\", false)]
+ [TestCase(@"cc:\", false)]
+ [TestCase(@"\\\SomeNetworkLocation\", false)]
+ [TestCase("RandomFile", false)]
+ public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult)
+ {
+ // When, Given
+ var result = FilesFolders.IsLocationPathString(querySearchString);
+
+ //Then
+ Assert.IsTrue(result == expectedResult,
+ $"Expected query search string check result is: {expectedResult} {Environment.NewLine} " +
+ $"Actual check result is {result} {Environment.NewLine}");
+
+ }
+
+ [TestCase(@"C:\SomeFolder\SomeApp", true, @"C:\SomeFolder\")]
+ [TestCase(@"C:\SomeFolder\SomeApp\SomeFile", true, @"C:\SomeFolder\SomeApp\")]
+ [TestCase(@"C:\NonExistentFolder\SomeApp", false, "")]
+ public void GivenAPartialPath_WhenPreviousLevelDirectoryExists_ThenShouldReturnThePreviousDirectoryPathString(
+ string path, bool previousDirectoryExists, string expectedString)
+ {
+ // When
+ Func previousLocationExists = null;
+ if (previousDirectoryExists)
+ {
+ previousLocationExists = PreviousLocationExistsReturnsTrue;
+ }
+ else
+ {
+ previousLocationExists = PreviousLocationNotExistReturnsFalse;
+ }
+
+ // Given
+ var previousDirectoryPath = FilesFolders.GetPreviousExistingDirectory(previousLocationExists, path);
+
+ //Then
+ Assert.IsTrue(previousDirectoryPath == expectedString,
+ $"Expected path string: {expectedString} {Environment.NewLine} " +
+ $"Actual path string is {previousDirectoryPath} {Environment.NewLine}");
+ }
+
+ [TestCase(@"C:\NonExistentFolder\SomeApp", @"C:\NonExistentFolder\")]
+ [TestCase(@"C:\NonExistentFolder\SomeApp\", @"C:\NonExistentFolder\SomeApp\")]
+ public void WhenGivenAPath_ThenShouldReturnThePreviousDirectoryPathIfIncompleteOrOriginalString(
+ string path, string expectedString)
+ {
+ var returnedPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path);
+
+ //Then
+ Assert.IsTrue(returnedPath == expectedString,
+ $"Expected path string: {expectedString} {Environment.NewLine} " +
+ $"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)
+ {
+ // Given
+ var queryConstructor = new QueryConstructor(new Settings());
+
+ //When
+ var resultString = queryConstructor.QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path);
+
+ // Then
+ Assert.IsTrue(resultString == expectedString,
+ $"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
+ $"Actual string was: {resultString}{Environment.NewLine}");
+ }
+
+ [TestCase("c:\\somefolder\\>somefile","*somefile*")]
+ [TestCase("c:\\somefolder\\somefile", "somefile*")]
+ [TestCase("c:\\somefolder\\", "*")]
+ public void GivenDirectoryInfoSearch_WhenSearchPatternHotKeyIsSearchAll_ThenSearchCriteriaShouldUseCriteriaString(string path, string expectedString)
+ {
+ // Given
+ var criteriaConstructor = new DirectoryInfoSearch(new PluginInitContext());
+
+ //When
+ var resultString = criteriaConstructor.ConstructSearchCriteria(path);
+
+ // Then
+ Assert.IsTrue(resultString == expectedString,
+ $"Expected criteria string: {expectedString}{Environment.NewLine} " +
+ $"Actual criteria string was: {resultString}{Environment.NewLine}");
+ }
+ }
+}
diff --git a/Flow.Launcher.sln b/Flow.Launcher.sln
index 2827cf585..b244c97de 100644
--- a/Flow.Launcher.sln
+++ b/Flow.Launcher.sln
@@ -1,4 +1,3 @@
-
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29806.167
@@ -18,9 +17,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher", "Flow.Launc
{1EE20B48-82FB-48A2-8086-675D6DDAB4F0} = {1EE20B48-82FB-48A2-8086-675D6DDAB4F0}
{0B9DE348-9361-4940-ADB6-F5953BFFCCEC} = {0B9DE348-9361-4940-ADB6-F5953BFFCCEC}
{FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4}
+ {F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}
{59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E}
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4} = {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}
{F35190AA-4758-4D9E-A193-E3BDF6AD3567} = {F35190AA-4758-4D9E-A193-E3BDF6AD3567}
{9B130CC5-14FB-41FF-B310-0A95B6894C37} = {9B130CC5-14FB-41FF-B310-0A95B6894C37}
{FDED22C8-B637-42E8-824A-63B5B6E05A3A} = {FDED22C8-B637-42E8-824A-63B5B6E05A3A}
@@ -41,8 +40,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.WebSea
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.ControlPanel", "Plugins\Flow.Launcher.Plugin.ControlPanel\Flow.Launcher.Plugin.ControlPanel.csproj", "{1EE20B48-82FB-48A2-8086-675D6DDAB4F0}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Folder", "Plugins\Flow.Launcher.Plugin.Folder\Flow.Launcher.Plugin.Folder.csproj", "{787B8AA6-CA93-4C84-96FE-DF31110AD1C4}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.PluginIndicator", "Plugins\Flow.Launcher.Plugin.PluginIndicator\Flow.Launcher.Plugin.PluginIndicator.csproj", "{FDED22C8-B637-42E8-824A-63B5B6E05A3A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Sys", "Plugins\Flow.Launcher.Plugin.Sys\Flow.Launcher.Plugin.Sys.csproj", "{0B9DE348-9361-4940-ADB6-F5953BFFCCEC}"
@@ -58,12 +55,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.gitattributes = .gitattributes
.gitignore = .gitignore
appveyor.yml = appveyor.yml
+ Scripts\flowlauncher.nuspec = Scripts\flowlauncher.nuspec
LICENSE = LICENSE
Scripts\post_build.ps1 = Scripts\post_build.ps1
README.md = README.md
SolutionAssemblyInfo.cs = SolutionAssemblyInfo.cs
- Scripts\flowlauncher.nuspec = Scripts\flowlauncher.nuspec
- Scripts\flowlauncher.plugin.nuspec = Scripts\flowlauncher.plugin.nuspec
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloWorldCSharp", "Plugins\HelloWorldCSharp\HelloWorldCSharp.csproj", "{03FFA443-5F50-48D5-8869-F3DF316803AA}"
@@ -76,6 +72,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Calcul
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "HelloWorldFSharp", "Plugins\HelloWorldFSharp\HelloWorldFSharp.fsproj", "{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Explorer", "Plugins\Flow.Launcher.Plugin.Explorer\Flow.Launcher.Plugin.Explorer.csproj", "{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -194,18 +192,6 @@ Global
{1EE20B48-82FB-48A2-8086-675D6DDAB4F0}.Release|x64.Build.0 = Release|Any CPU
{1EE20B48-82FB-48A2-8086-675D6DDAB4F0}.Release|x86.ActiveCfg = Release|Any CPU
{1EE20B48-82FB-48A2-8086-675D6DDAB4F0}.Release|x86.Build.0 = Release|Any CPU
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Debug|x64.ActiveCfg = Debug|Any CPU
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Debug|x64.Build.0 = Debug|Any CPU
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Debug|x86.ActiveCfg = Debug|Any CPU
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Debug|x86.Build.0 = Debug|Any CPU
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Release|Any CPU.Build.0 = Release|Any CPU
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Release|x64.ActiveCfg = Release|Any CPU
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Release|x64.Build.0 = Release|Any CPU
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Release|x86.ActiveCfg = Release|Any CPU
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Release|x86.Build.0 = Release|Any CPU
{FDED22C8-B637-42E8-824A-63B5B6E05A3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FDED22C8-B637-42E8-824A-63B5B6E05A3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FDED22C8-B637-42E8-824A-63B5B6E05A3A}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -327,6 +313,18 @@ Global
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|x64.Build.0 = Release|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|x86.ActiveCfg = Release|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|x86.Build.0 = Release|Any CPU
+ {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|x64.Build.0 = Debug|Any CPU
+ {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|x86.Build.0 = Debug|Any CPU
+ {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|x64.ActiveCfg = Release|Any CPU
+ {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|x64.Build.0 = Release|Any CPU
+ {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|x86.ActiveCfg = Release|Any CPU
+ {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -336,7 +334,6 @@ Global
{FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{403B57F2-1856-4FC7-8A24-36AB346B763E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{1EE20B48-82FB-48A2-8086-675D6DDAB4F0} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{FDED22C8-B637-42E8-824A-63B5B6E05A3A} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{0B9DE348-9361-4940-ADB6-F5953BFFCCEC} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{A3DCCBCA-ACC1-421D-B16E-210896234C26} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
@@ -347,6 +344,7 @@ Global
{9B130CC5-14FB-41FF-B310-0A95B6894C37} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{59BD9891-3837-438A-958D-ADC7F91F6F7E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
+ {F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F26ACB50-3F6C-4907-B0C9-1ADACC1D0DED}
diff --git a/Flow.Launcher/ActionKeywords.xaml b/Flow.Launcher/ActionKeywords.xaml
index 39574964b..9d032efd9 100644
--- a/Flow.Launcher/ActionKeywords.xaml
+++ b/Flow.Launcher/ActionKeywords.xaml
@@ -1,4 +1,4 @@
-
+ Height="250" Width="500">
-
+
-
+
-
-
- Old ActionKeywords:
-
-
-
+
+
+
+
-
+
-
-
-
diff --git a/Flow.Launcher/ActionKeywords.xaml.cs b/Flow.Launcher/ActionKeywords.xaml.cs
index d90273147..4a2368347 100644
--- a/Flow.Launcher/ActionKeywords.xaml.cs
+++ b/Flow.Launcher/ActionKeywords.xaml.cs
@@ -4,30 +4,33 @@ using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure.Exception;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
+using Flow.Launcher.ViewModel;
namespace Flow.Launcher
{
public partial class ActionKeywords : Window
{
- private PluginPair _plugin;
- private Settings _settings;
- private readonly Internationalization _translater = InternationalizationManager.Instance;
+ private readonly PluginPair plugin;
+ private Settings settings;
+ private readonly Internationalization translater = InternationalizationManager.Instance;
+ private readonly PluginViewModel pluginViewModel;
- public ActionKeywords(string pluginId, Settings settings)
+ public ActionKeywords(string pluginId, Settings settings, PluginViewModel pluginViewModel)
{
InitializeComponent();
- _plugin = PluginManager.GetPluginForId(pluginId);
- _settings = settings;
- if (_plugin == null)
+ plugin = PluginManager.GetPluginForId(pluginId);
+ this.settings = settings;
+ this.pluginViewModel = pluginViewModel;
+ if (plugin == null)
{
- MessageBox.Show(_translater.GetTranslation("cannotFindSpecifiedPlugin"));
+ MessageBox.Show(translater.GetTranslation("cannotFindSpecifiedPlugin"));
Close();
}
}
private void ActionKeyword_OnLoaded(object sender, RoutedEventArgs e)
{
- tbOldActionKeyword.Text = string.Join(Query.ActionKeywordSeperater, _plugin.Metadata.ActionKeywords.ToArray());
+ tbOldActionKeyword.Text = string.Join(Query.ActionKeywordSeperater, plugin.Metadata.ActionKeywords.ToArray());
tbAction.Focus();
}
@@ -38,19 +41,17 @@ namespace Flow.Launcher
private void btnDone_OnClick(object sender, RoutedEventArgs _)
{
- var oldActionKeyword = _plugin.Metadata.ActionKeywords[0];
+ var oldActionKeyword = plugin.Metadata.ActionKeywords[0];
var newActionKeyword = tbAction.Text.Trim();
newActionKeyword = newActionKeyword.Length > 0 ? newActionKeyword : "*";
- if (!PluginManager.ActionKeywordRegistered(newActionKeyword))
+ if (!pluginViewModel.IsActionKeywordRegistered(newActionKeyword))
{
- var id = _plugin.Metadata.ID;
- PluginManager.ReplaceActionKeyword(id, oldActionKeyword, newActionKeyword);
- MessageBox.Show(_translater.GetTranslation("success"));
+ pluginViewModel.ChangeActionKeyword(newActionKeyword, oldActionKeyword);
Close();
}
else
{
- string msg = _translater.GetTranslation("newActionKeywordsHasBeenAssigned");
+ string msg = translater.GetTranslation("newActionKeywordsHasBeenAssigned");
MessageBox.Show(msg);
}
}
diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml b/Flow.Launcher/CustomQueryHotkeySetting.xaml
index 0eb0da1da..5f4cdff19 100644
--- a/Flow.Launcher/CustomQueryHotkeySetting.xaml
+++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml
@@ -1,4 +1,4 @@
-
+
+
+
+
+
+
@@ -18,7 +24,7 @@
-
+
@@ -29,9 +35,9 @@
-
-
+
diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs
index 441bba509..bf5c7d769 100644
--- a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs
+++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs
@@ -1,13 +1,13 @@
-using System;
-using System.Collections.Generic;
+using Flow.Launcher.Core.Resource;
+using Flow.Launcher.Infrastructure.Hotkey;
+using Flow.Launcher.Infrastructure.UserSettings;
+using NHotkey;
+using NHotkey.Wpf;
+using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
-using NHotkey;
-using NHotkey.Wpf;
-using Flow.Launcher.Core.Resource;
-using Flow.Launcher.Infrastructure.Hotkey;
-using Flow.Launcher.Infrastructure.UserSettings;
+using System.Windows.Input;
namespace Flow.Launcher
{
@@ -125,5 +125,10 @@ namespace Flow.Launcher
MessageBox.Show(errorMsg);
}
}
+
+ private void cmdEsc_OnPress(object sender, ExecutedRoutedEventArgs e)
+ {
+ Close();
+ }
}
}
diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj
index 65b0e8ef4..987a685ac 100644
--- a/Flow.Launcher/Flow.Launcher.csproj
+++ b/Flow.Launcher/Flow.Launcher.csproj
@@ -62,6 +62,14 @@
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
@@ -74,13 +82,12 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
+
diff --git a/Flow.Launcher/Languages/da.xaml b/Flow.Launcher/Languages/da.xaml
index 296f6aa8b..19e1951d8 100644
--- a/Flow.Launcher/Languages/da.xaml
+++ b/Flow.Launcher/Languages/da.xaml
@@ -36,8 +36,8 @@
Nøgleord
Plugin bibliotek
Forfatter
- Initaliseringstid: {0}ms
- Søgetid: {0}ms
+ Initaliseringstid:
+ Søgetid:
Tema
diff --git a/Flow.Launcher/Languages/de.xaml b/Flow.Launcher/Languages/de.xaml
index 91b7c7982..39dc9b377 100644
--- a/Flow.Launcher/Languages/de.xaml
+++ b/Flow.Launcher/Languages/de.xaml
@@ -1,4 +1,4 @@
-
@@ -36,8 +36,8 @@
Aktionsschlüsselwörter
Pluginordner
Autor
- Initialisierungszeit: {0}ms
- Abfragezeit: {0}ms
+ Initialisierungszeit:
+ Abfragezeit:
Theme
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index 1b69c1b90..0bf211bd9 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -1,4 +1,4 @@
-
@@ -40,11 +40,13 @@
Plugin
Find more plugins
Disable
- Action keywords
+ Action keyword:
+ Current action keyword:
+ New action keyword:
Plugin Directory
Author
- Init time: {0}ms
- Query time: {0}ms
+ Init time:
+ Query time:
Theme
diff --git a/Flow.Launcher/Languages/fr.xaml b/Flow.Launcher/Languages/fr.xaml
index f43cbb498..a8f280bd1 100644
--- a/Flow.Launcher/Languages/fr.xaml
+++ b/Flow.Launcher/Languages/fr.xaml
@@ -40,8 +40,8 @@
Mot-clé d'action :
Répertoire
Auteur
- Chargement : {0}ms
- Utilisation : {0}ms
+ Chargement :
+ Utilisation :
Thèmes
diff --git a/Flow.Launcher/Languages/it.xaml b/Flow.Launcher/Languages/it.xaml
index aa29a8891..e85e49933 100644
--- a/Flow.Launcher/Languages/it.xaml
+++ b/Flow.Launcher/Languages/it.xaml
@@ -1,4 +1,4 @@
-
@@ -40,8 +40,8 @@
Parole chiave
Cartella Plugin
Autore
- Tempo di avvio: {0}ms
- Tempo ricerca: {0}ms
+ Tempo di avvio:
+ Tempo ricerca:
Tema
diff --git a/Flow.Launcher/Languages/ja.xaml b/Flow.Launcher/Languages/ja.xaml
index 34c38edf4..937a1e504 100644
--- a/Flow.Launcher/Languages/ja.xaml
+++ b/Flow.Launcher/Languages/ja.xaml
@@ -1,4 +1,4 @@
-
@@ -41,8 +41,8 @@
キーワード
プラグイン・ディレクトリ
作者
- 初期化時間: {0}ms
- クエリ時間: {0}ms
+ 初期化時間:
+ クエリ時間:
テーマ
diff --git a/Flow.Launcher/Languages/ko.xaml b/Flow.Launcher/Languages/ko.xaml
index 21f1f0273..6c755dbae 100644
--- a/Flow.Launcher/Languages/ko.xaml
+++ b/Flow.Launcher/Languages/ko.xaml
@@ -1,4 +1,4 @@
-
@@ -40,8 +40,8 @@
액션 키워드
플러그인 디렉토리
저자
- 초기화 시간: {0}ms
- 쿼리 시간: {0}ms
+ 초기화 시간:
+ 쿼리 시간:
테마
diff --git a/Flow.Launcher/Languages/nb-NO.xaml b/Flow.Launcher/Languages/nb-NO.xaml
index 1dbfcf5d4..19f6cc36b 100644
--- a/Flow.Launcher/Languages/nb-NO.xaml
+++ b/Flow.Launcher/Languages/nb-NO.xaml
@@ -40,8 +40,8 @@
Handlingsnøkkelord
Utvidelseskatalog
Forfatter
- Oppstartstid: {0}ms
- Spørringstid: {0}ms
+ Oppstartstid:
+ Spørringstid:
Tema
diff --git a/Flow.Launcher/Languages/nl.xaml b/Flow.Launcher/Languages/nl.xaml
index 7d24d9067..ca7fed180 100644
--- a/Flow.Launcher/Languages/nl.xaml
+++ b/Flow.Launcher/Languages/nl.xaml
@@ -1,4 +1,4 @@
-
@@ -36,8 +36,8 @@
Action terfwoorden
Plugin map
Auteur
- Init tijd: {0}ms
- Query tijd: {0}ms
+ Init tijd:
+ Query tijd:
Thema
diff --git a/Flow.Launcher/Languages/pl.xaml b/Flow.Launcher/Languages/pl.xaml
index 4f26cfaaf..4f3042be3 100644
--- a/Flow.Launcher/Languages/pl.xaml
+++ b/Flow.Launcher/Languages/pl.xaml
@@ -1,4 +1,4 @@
-
@@ -36,8 +36,8 @@
Wyzwalacze
Folder wtyczki
Autor
- Czas ładowania: {0}ms
- Czas zapytania: {0}ms
+ Czas ładowania:
+ Czas zapytania:
Skórka
diff --git a/Flow.Launcher/Languages/pt-br.xaml b/Flow.Launcher/Languages/pt-br.xaml
index 11d8b839e..dce921ff7 100644
--- a/Flow.Launcher/Languages/pt-br.xaml
+++ b/Flow.Launcher/Languages/pt-br.xaml
@@ -1,4 +1,4 @@
-
@@ -40,8 +40,8 @@
Palavras-chave de ação
Diretório de Plugins
Autor
- Tempo de inicialização: {0}ms
- Tempo de consulta: {0}ms
+ Tempo de inicialização:
+ Tempo de consulta:
Tema
diff --git a/Flow.Launcher/Languages/ru.xaml b/Flow.Launcher/Languages/ru.xaml
index b86fcf377..d0d6ff0e5 100644
--- a/Flow.Launcher/Languages/ru.xaml
+++ b/Flow.Launcher/Languages/ru.xaml
@@ -1,4 +1,4 @@
-
@@ -36,8 +36,8 @@
Ключевое слово
Папка
Автор
- Инициализация: {0}ms
- Запрос: {0}ms
+ Инициализация:
+ Запрос:
Темы
diff --git a/Flow.Launcher/Languages/sk.xaml b/Flow.Launcher/Languages/sk.xaml
index 4ddbd839d..a2ff5926d 100644
--- a/Flow.Launcher/Languages/sk.xaml
+++ b/Flow.Launcher/Languages/sk.xaml
@@ -1,4 +1,4 @@
-
@@ -41,8 +41,8 @@
Skratka akcie
Priečinok s pluginmy
Autor
- Čas inic.: {0}ms
- Čas dopytu: {0}ms
+ Čas inic.:
+ Čas dopytu:
Motív
diff --git a/Flow.Launcher/Languages/sr.xaml b/Flow.Launcher/Languages/sr.xaml
index 41f112af6..0394b398d 100644
--- a/Flow.Launcher/Languages/sr.xaml
+++ b/Flow.Launcher/Languages/sr.xaml
@@ -1,4 +1,4 @@
-
@@ -40,8 +40,8 @@
Ključne reči
Plugin direktorijum
Autor
- Vreme inicijalizacije: {0}ms
- Vreme upita: {0}ms
+ Vreme inicijalizacije:
+ Vreme upita:
Tema
diff --git a/Flow.Launcher/Languages/tr.xaml b/Flow.Launcher/Languages/tr.xaml
index 9e2624cc2..421375df9 100644
--- a/Flow.Launcher/Languages/tr.xaml
+++ b/Flow.Launcher/Languages/tr.xaml
@@ -1,4 +1,4 @@
-
@@ -42,8 +42,8 @@
Anahtar Kelimeler
Eklenti Klasörü
Yapımcı
- Açılış Süresi: {0}ms
- Sorgu Süresi: {0}ms
+ Açılış Süresi:
+ Sorgu Süresi:
Temalar
diff --git a/Flow.Launcher/Languages/uk-UA.xaml b/Flow.Launcher/Languages/uk-UA.xaml
index 0343f9d0f..b57676f8d 100644
--- a/Flow.Launcher/Languages/uk-UA.xaml
+++ b/Flow.Launcher/Languages/uk-UA.xaml
@@ -1,4 +1,4 @@
-
@@ -36,8 +36,8 @@
Ключове слово
Директорія плагіну
Автор
- Ініціалізація: {0}ms
- Запит: {0}ms
+ Ініціалізація:
+ Запит:
Теми
diff --git a/Flow.Launcher/Languages/zh-cn.xaml b/Flow.Launcher/Languages/zh-cn.xaml
index c8bdc6890..92ce60e7f 100644
--- a/Flow.Launcher/Languages/zh-cn.xaml
+++ b/Flow.Launcher/Languages/zh-cn.xaml
@@ -1,4 +1,4 @@
-
@@ -41,8 +41,8 @@
触发关键字
插件目录
作者
- 加载耗时 {0}ms
- 查询耗时 {0}ms
+ 加载耗时
+ 查询耗时
主题
diff --git a/Flow.Launcher/Languages/zh-tw.xaml b/Flow.Launcher/Languages/zh-tw.xaml
index 322eaf32f..294add207 100644
--- a/Flow.Launcher/Languages/zh-tw.xaml
+++ b/Flow.Launcher/Languages/zh-tw.xaml
@@ -1,4 +1,4 @@
-
@@ -36,8 +36,8 @@
觸發關鍵字
外掛資料夾
作者
- 載入耗時:{0}ms
- 查詢耗時:{0}ms
+ 載入耗時:
+ 查詢耗時:
主題
diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml
index 1708a5172..acd992b6c 100644
--- a/Flow.Launcher/SettingWindow.xaml
+++ b/Flow.Launcher/SettingWindow.xaml
@@ -175,14 +175,16 @@
+ Margin="20 0 0 0"/>
-
-
+
+
+
+
diff --git a/Flow.Launcher/SettingWindow.xaml.cs b/Flow.Launcher/SettingWindow.xaml.cs
index aa54bb7c5..eb5fd7de0 100644
--- a/Flow.Launcher/SettingWindow.xaml.cs
+++ b/Flow.Launcher/SettingWindow.xaml.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Diagnostics;
+using System;
using System.IO;
using System.Windows;
using System.Windows.Input;
@@ -21,17 +20,17 @@ namespace Flow.Launcher
{
private const string StartupPath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
- public readonly IPublicAPI _api;
- private Settings _settings;
- private SettingWindowViewModel _viewModel;
+ public readonly IPublicAPI API;
+ private Settings settings;
+ private SettingWindowViewModel viewModel;
public SettingWindow(IPublicAPI api, SettingWindowViewModel viewModel)
{
InitializeComponent();
- _settings = viewModel.Settings;
+ settings = viewModel.Settings;
DataContext = viewModel;
- _viewModel = viewModel;
- _api = api;
+ this.viewModel = viewModel;
+ API = api;
}
#region General
@@ -94,7 +93,7 @@ namespace Flow.Launcher
var pythonPath = Path.Combine(pythonDirectory, PluginsLoader.PythonExecutable);
if (File.Exists(pythonPath))
{
- _settings.PluginSettings.PythonDirectory = pythonDirectory;
+ settings.PluginSettings.PythonDirectory = pythonDirectory;
MessageBox.Show("Remember to restart Flow Launcher use new Python path");
}
else
@@ -111,7 +110,7 @@ namespace Flow.Launcher
private void OnHotkeyControlLoaded(object sender, RoutedEventArgs e)
{
- HotkeyControl.SetHotkey(_viewModel.Settings.Hotkey, false);
+ HotkeyControl.SetHotkey(viewModel.Settings.Hotkey, false);
}
void OnHotkeyChanged(object sender, EventArgs e)
@@ -129,8 +128,8 @@ namespace Flow.Launcher
Application.Current.MainWindow.Visibility = Visibility.Hidden;
}
});
- RemoveHotkey(_settings.Hotkey);
- _settings.Hotkey = HotkeyControl.CurrentHotkey.ToString();
+ RemoveHotkey(settings.Hotkey);
+ settings.Hotkey = HotkeyControl.CurrentHotkey.ToString();
}
}
@@ -159,7 +158,7 @@ namespace Flow.Launcher
private void OnDeleteCustomHotkeyClick(object sender, RoutedEventArgs e)
{
- var item = _viewModel.SelectedCustomPluginHotkey;
+ var item = viewModel.SelectedCustomPluginHotkey;
if (item == null)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
@@ -173,17 +172,17 @@ namespace Flow.Launcher
MessageBox.Show(deleteWarning, InternationalizationManager.Instance.GetTranslation("delete"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
- _settings.CustomPluginHotkeys.Remove(item);
+ settings.CustomPluginHotkeys.Remove(item);
RemoveHotkey(item.Hotkey);
}
}
private void OnnEditCustomHotkeyClick(object sender, RoutedEventArgs e)
{
- var item = _viewModel.SelectedCustomPluginHotkey;
+ var item = viewModel.SelectedCustomPluginHotkey;
if (item != null)
{
- CustomQueryHotkeySetting window = new CustomQueryHotkeySetting(this, _settings);
+ CustomQueryHotkeySetting window = new CustomQueryHotkeySetting(this, settings);
window.UpdateItem(item);
window.ShowDialog();
}
@@ -195,7 +194,7 @@ namespace Flow.Launcher
private void OnAddCustomeHotkeyClick(object sender, RoutedEventArgs e)
{
- new CustomQueryHotkeySetting(this, _settings).ShowDialog();
+ new CustomQueryHotkeySetting(this, settings).ShowDialog();
}
#endregion
@@ -204,17 +203,17 @@ namespace Flow.Launcher
private void OnPluginToggled(object sender, RoutedEventArgs e)
{
- var id = _viewModel.SelectedPlugin.PluginPair.Metadata.ID;
+ var id = viewModel.SelectedPlugin.PluginPair.Metadata.ID;
// used to sync the current status from the plugin manager into the setting to keep consistency after save
- _settings.PluginSettings.Plugins[id].Disabled = _viewModel.SelectedPlugin.PluginPair.Metadata.Disabled;
+ settings.PluginSettings.Plugins[id].Disabled = viewModel.SelectedPlugin.PluginPair.Metadata.Disabled;
}
private void OnPluginActionKeywordsClick(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
- var id = _viewModel.SelectedPlugin.PluginPair.Metadata.ID;
- ActionKeywords changeKeywordsWindow = new ActionKeywords(id, _settings);
+ var id = viewModel.SelectedPlugin.PluginPair.Metadata.ID;
+ ActionKeywords changeKeywordsWindow = new ActionKeywords(id, settings, viewModel.SelectedPlugin);
changeKeywordsWindow.ShowDialog();
}
}
@@ -223,7 +222,7 @@ namespace Flow.Launcher
{
if (e.ChangedButton == MouseButton.Left)
{
- var website = _viewModel.SelectedPlugin.PluginPair.Metadata.Website;
+ var website = viewModel.SelectedPlugin.PluginPair.Metadata.Website;
if (!string.IsNullOrEmpty(website))
{
var uri = new Uri(website);
@@ -239,7 +238,7 @@ namespace Flow.Launcher
{
if (e.ChangedButton == MouseButton.Left)
{
- var directory = _viewModel.SelectedPlugin.PluginPair.Metadata.PluginDirectory;
+ var directory = viewModel.SelectedPlugin.PluginPair.Metadata.PluginDirectory;
if (!string.IsNullOrEmpty(directory))
FilesFolders.OpenPath(directory);
}
@@ -250,7 +249,7 @@ namespace Flow.Launcher
private void OnTestProxyClick(object sender, RoutedEventArgs e)
{ // TODO: change to command
- var msg = _viewModel.TestProxy();
+ var msg = viewModel.TestProxy();
MessageBox.Show(msg); // TODO: add message box service
}
@@ -258,7 +257,7 @@ namespace Flow.Launcher
private async void OnCheckUpdates(object sender, RoutedEventArgs e)
{
- _viewModel.UpdateApp(); // TODO: change to command
+ viewModel.UpdateApp(); // TODO: change to command
}
private void OnRequestNavigate(object sender, RequestNavigateEventArgs e)
@@ -269,7 +268,7 @@ namespace Flow.Launcher
private void OnClosed(object sender, EventArgs e)
{
- _viewModel.Save();
+ viewModel.Save();
}
private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e)
diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs
index e79636771..eb7e0054d 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -1,8 +1,9 @@
-using System.Windows;
+using System.Windows;
using System.Windows.Media;
using Flow.Launcher.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure.Image;
+using Flow.Launcher.Core.Plugin;
namespace Flow.Launcher.ViewModel
{
@@ -22,8 +23,17 @@ namespace Flow.Launcher.ViewModel
}
}
public Visibility ActionKeywordsVisibility => PluginPair.Metadata.ActionKeywords.Count > 1 ? Visibility.Collapsed : Visibility.Visible;
- public string InitilizaTime => string.Format(_translator.GetTranslation("plugin_init_time"), PluginPair.Metadata.InitTime);
- public string QueryTime => string.Format(_translator.GetTranslation("plugin_query_time"), PluginPair.Metadata.AvgQueryTime);
+ public string InitilizaTime => PluginPair.Metadata.InitTime.ToString() + "ms";
+ public string QueryTime => PluginPair.Metadata.AvgQueryTime + "ms";
public string ActionKeywordsText => string.Join(Query.ActionKeywordSeperater, PluginPair.Metadata.ActionKeywords);
+
+ public void ChangeActionKeyword(string newActionKeyword, string oldActionKeyword)
+ {
+ PluginManager.ReplaceActionKeyword(PluginPair.Metadata.ID, oldActionKeyword, newActionKeyword);
+
+ OnPropertyChanged(nameof(ActionKeywordsText));
+ }
+
+ public bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword);
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
index b59b114fe..13daddf10 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
@@ -80,8 +80,4 @@
-
-
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj
index d0bc45383..e7cae42ae 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj
@@ -99,7 +99,6 @@
-
diff --git a/Plugins/Flow.Launcher.Plugin.Color/Flow.Launcher.Plugin.Color.csproj b/Plugins/Flow.Launcher.Plugin.Color/Flow.Launcher.Plugin.Color.csproj
index fb03fda79..19f8fb980 100644
--- a/Plugins/Flow.Launcher.Plugin.Color/Flow.Launcher.Plugin.Color.csproj
+++ b/Plugins/Flow.Launcher.Plugin.Color/Flow.Launcher.Plugin.Color.csproj
@@ -101,8 +101,4 @@
-
-
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj b/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj
index c75b7aa38..d1c185c36 100644
--- a/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj
+++ b/Plugins/Flow.Launcher.Plugin.ControlPanel/Flow.Launcher.Plugin.ControlPanel.csproj
@@ -101,8 +101,4 @@
-
-
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Everything/Flow.Launcher.Plugin.Everything.csproj b/Plugins/Flow.Launcher.Plugin.Everything/Flow.Launcher.Plugin.Everything.csproj
index 3fde34b5d..41ad9007c 100644
--- a/Plugins/Flow.Launcher.Plugin.Everything/Flow.Launcher.Plugin.Everything.csproj
+++ b/Plugins/Flow.Launcher.Plugin.Everything/Flow.Launcher.Plugin.Everything.csproj
@@ -128,8 +128,4 @@
-
-
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs
new file mode 100644
index 000000000..90a3fb2e8
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs
@@ -0,0 +1,287 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Threading.Tasks;
+using System.Windows;
+using Flow.Launcher.Infrastructure.Logger;
+using Flow.Launcher.Plugin.SharedCommands;
+using Flow.Launcher.Plugin.Explorer.Search;
+using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
+using System.Linq;
+using System.Reflection;
+
+namespace Flow.Launcher.Plugin.Explorer
+{
+ internal class ContextMenu : IContextMenu
+ {
+ private PluginInitContext Context { get; set; }
+
+ private Settings Settings { get; set; }
+
+ public ContextMenu(PluginInitContext context, Settings settings)
+ {
+ Context = context;
+ Settings = settings;
+ }
+
+ public List LoadContextMenus(Result selectedResult)
+ {
+ var contextMenus = new List();
+ if (selectedResult.ContextData is SearchResult record)
+ {
+ if (record.Type == ResultType.File)
+ contextMenus.Add(CreateOpenWithEditorResult(record));
+
+ if (record.Type == ResultType.Folder && record.WindowsIndexed)
+ contextMenus.Add(CreateAddToIndexSearchExclusionListResult(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});
+
+ var icoPath = (record.Type == ResultType.File) ? Constants.FileImagePath : Constants.FolderImagePath;
+ var fileOrFolder = (record.Type == ResultType.File) ? "file" : "folder";
+ contextMenus.Add(new Result
+ {
+ Title = Context.API.GetTranslation("plugin_explorer_copypath"),
+ SubTitle = $"Copy the current {fileOrFolder} path to clipboard",
+ Action = (context) =>
+ {
+ try
+ {
+ Clipboard.SetText(record.FullPath);
+ return true;
+ }
+ catch (Exception e)
+ {
+ var message = "Fail to set text in clipboard";
+ LogException(message, e);
+ Context.API.ShowMsg(message);
+ return false;
+ }
+ },
+ IcoPath = Constants.CopyImagePath
+ });
+
+ contextMenus.Add(new Result
+ {
+ Title = Context.API.GetTranslation("plugin_explorer_copyfilefolder") + $" {fileOrFolder}",
+ SubTitle = $"Copy the {fileOrFolder} to clipboard",
+ Action = (context) =>
+ {
+ try
+ {
+ Clipboard.SetFileDropList(new System.Collections.Specialized.StringCollection { record.FullPath });
+ return true;
+ }
+ catch (Exception e)
+ {
+ var message = $"Fail to set {fileOrFolder} in clipboard";
+ LogException(message, e);
+ Context.API.ShowMsg(message);
+ return false;
+ }
+
+ },
+ IcoPath = icoPath
+ });
+
+ if (record.Type == ResultType.File || record.Type == ResultType.Folder)
+ contextMenus.Add(new Result
+ {
+ Title = Context.API.GetTranslation("plugin_explorer_deletefilefolder") + $" {fileOrFolder}",
+ SubTitle = Context.API.GetTranslation("plugin_explorer_deletefilefolder_subtitle") + $" {fileOrFolder}",
+ Action = (context) =>
+ {
+ try
+ {
+ if (record.Type == ResultType.File)
+ File.Delete(record.FullPath);
+ else
+ Directory.Delete(record.FullPath, true);
+ }
+ catch (Exception e)
+ {
+ var message = $"Fail to delete {fileOrFolder} at {record.FullPath}";
+ LogException(message, e);
+ Context.API.ShowMsg(message);
+ return false;
+ }
+
+ return true;
+ },
+ IcoPath = Constants.DeleteFileFolderImagePath
+ });
+
+ if (record.Type == ResultType.File && CanRunAsDifferentUser(record.FullPath))
+ contextMenus.Add(new Result
+ {
+ Title = Context.API.GetTranslation("plugin_explorer_runasdifferentuser"),
+ SubTitle = Context.API.GetTranslation("plugin_explorer_runasdifferentuser_subtitle"),
+ Action = (context) =>
+ {
+ try
+ {
+ Task.Run(() => ShellCommand.RunAsDifferentUser(record.FullPath.SetProcessStartInfo()));
+ }
+ catch (FileNotFoundException e)
+ {
+ var name = "Plugin: Folder";
+ var message = $"File not found: {e.Message}";
+ Context.API.ShowMsg(name, message);
+ }
+
+ return true;
+ },
+ IcoPath = Constants.DifferentUserIconImagePath
+ });
+ }
+
+ return contextMenus;
+ }
+
+ private Result CreateOpenContainingFolderResult(SearchResult record)
+ {
+ return new Result
+ {
+ Title = Context.API.GetTranslation("plugin_explorer_opencontainingfolder"),
+ SubTitle = Context.API.GetTranslation("plugin_explorer_opencontainingfolder_subtitle"),
+ Action = _ =>
+ {
+ try
+ {
+ Process.Start("explorer.exe", $" /select,\"{record.FullPath}\"");
+ }
+ catch (Exception e)
+ {
+ var message = $"Fail to open file at {record.FullPath}";
+ LogException(message, e);
+ Context.API.ShowMsg(message);
+ return false;
+ }
+
+ return true;
+ },
+ IcoPath = Constants.FolderImagePath
+ };
+ }
+
+ private Result CreateOpenWithEditorResult(SearchResult record)
+ {
+ string editorPath = "Notepad.exe"; // TODO add the ability to create a custom editor
+
+ var name = Context.API.GetTranslation("plugin_explorer_openwitheditor")
+ + " " + Path.GetFileNameWithoutExtension(editorPath);
+
+ return new Result
+ {
+ Title = name,
+ Action = _ =>
+ {
+ try
+ {
+ Process.Start(editorPath, record.FullPath);
+ return true;
+ }
+ catch (Exception e)
+ {
+ var message = $"Failed to open editor for file at {record.FullPath}";
+ LogException(message, e);
+ Context.API.ShowMsg(message);
+ return false;
+ }
+ },
+ IcoPath = Constants.FileImagePath
+ };
+ }
+
+ private Result CreateAddToIndexSearchExclusionListResult(SearchResult record)
+ {
+ return new Result
+ {
+ Title = Context.API.GetTranslation("plugin_explorer_excludefromindexsearch"),
+ SubTitle = Context.API.GetTranslation("plugin_explorer_path") + " " + record.FullPath,
+ Action = _ =>
+ {
+ if(!Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x.Path == record.FullPath))
+ Settings.IndexSearchExcludedSubdirectoryPaths.Add(new FolderLink { Path = record.FullPath });
+
+ var pluginDirectory = Directory.GetParent(Assembly.GetExecutingAssembly().Location.ToString());
+
+ var iconPath = pluginDirectory + "\\" + Constants.ExplorerIconImagePath;
+
+ Task.Run(() =>
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_excludedfromindexsearch_msg"),
+ Context.API.GetTranslation("plugin_explorer_path") +
+ " " + record.FullPath, iconPath);
+
+ // so the new path can be persisted to storage and not wait till next ViewModel save.
+ Context.API.SaveAppAllSettings();
+ });
+
+ return false;
+ },
+ IcoPath = Constants.ExcludeFromIndexImagePath
+ };
+ }
+
+ private Result CreateOpenWindowsIndexingOptions()
+ {
+ return new Result
+ {
+ Title = Context.API.GetTranslation("plugin_explorer_openindexingoptions"),
+ SubTitle = Context.API.GetTranslation("plugin_explorer_openindexingoptions_subtitle"),
+ Action = _ =>
+ {
+ try
+ {
+ var psi = new ProcessStartInfo
+ {
+ FileName = "control.exe",
+ UseShellExecute = true,
+ Arguments = "srchadmin.dll"
+ };
+
+ Process.Start(psi);
+ return true;
+ }
+ catch (Exception e)
+ {
+ var message = Context.API.GetTranslation("plugin_explorer_openindexingoptions_errormsg");
+ LogException(message, e);
+ Context.API.ShowMsg(message);
+ return false;
+ }
+ },
+ IcoPath = Constants.IndexingOptionsIconImagePath
+ };
+ }
+
+ public void LogException(string message, Exception e)
+ {
+ Log.Exception($"|Flow.Launcher.Plugin.Folder.ContextMenu|{message}", e);
+ }
+
+ private bool CanRunAsDifferentUser(string path)
+ {
+ switch (Path.GetExtension(path))
+ {
+ case ".exe":
+ case ".bat":
+ case ".msi":
+ return true;
+
+ default:
+ return false;
+
+ }
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/Flow.Launcher.Plugin.Folder.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
similarity index 63%
rename from Plugins/Flow.Launcher.Plugin.Folder/Flow.Launcher.Plugin.Folder.csproj
rename to Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
index 67cc0294c..bd2a047eb 100644
--- a/Plugins/Flow.Launcher.Plugin.Folder/Flow.Launcher.Plugin.Folder.csproj
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
@@ -1,37 +1,23 @@
+ Library
netcoreapp3.1
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}
- Properties
- Flow.Launcher.Plugin.Folder
- Flow.Launcher.Plugin.Folder
+ true
true
false
- false
+
+
-
-
- true
- full
- false
- ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Folder\
- DEBUG;TRACE
- prompt
- 4
- false
+
+
+ ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Explorer
-
-
- pdbonly
- true
- ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.Folder\
- TRACE
- prompt
- 4
- false
+
+
+ ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.Explorer
-
+
PreserveNewest
@@ -39,73 +25,87 @@
-
- MSBuild:Compile
- Designer
-
+
+ PreserveNewest
+
+
+
+ PreserveNewest
+
+
+
+ PreserveNewest
+
+
PreserveNewest
+
Always
+
PreserveNewest
-
- Always
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+ PreserveNewest
+
+
MSBuild:Compile
Designer
PreserveNewest
+
MSBuild:Compile
Designer
PreserveNewest
+
MSBuild:Compile
Designer
PreserveNewest
+
MSBuild:Compile
Designer
PreserveNewest
+
MSBuild:Compile
Designer
PreserveNewest
+
MSBuild:Compile
Designer
PreserveNewest
+
+
+
+
+
-
-
-
- PreserveNewest
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/Images/copy.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/copy.png
similarity index 100%
rename from Plugins/Flow.Launcher.Plugin.Folder/Images/copy.png
rename to Plugins/Flow.Launcher.Plugin.Explorer/Images/copy.png
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/Images/deletefilefolder.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/deletefilefolder.png
similarity index 100%
rename from Plugins/Flow.Launcher.Plugin.Folder/Images/deletefilefolder.png
rename to Plugins/Flow.Launcher.Plugin.Explorer/Images/deletefilefolder.png
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/excludeindexpath.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/excludeindexpath.png
new file mode 100644
index 000000000..8578ac7fb
Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.Explorer/Images/excludeindexpath.png differ
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/explorer.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/explorer.png
new file mode 100644
index 000000000..552b2c5bd
Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.Explorer/Images/explorer.png differ
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/Images/file.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/file.png
similarity index 100%
rename from Plugins/Flow.Launcher.Plugin.Folder/Images/file.png
rename to Plugins/Flow.Launcher.Plugin.Explorer/Images/file.png
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/Images/folder.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/folder.png
similarity index 100%
rename from Plugins/Flow.Launcher.Plugin.Folder/Images/folder.png
rename to Plugins/Flow.Launcher.Plugin.Explorer/Images/folder.png
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/index.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/index.png
new file mode 100644
index 000000000..c63fc8069
Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.Explorer/Images/index.png differ
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/Images/user.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/user.png
similarity index 100%
rename from Plugins/Flow.Launcher.Plugin.Folder/Images/user.png
rename to Plugins/Flow.Launcher.Plugin.Explorer/Images/user.png
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/windowsindexingoptions.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/windowsindexingoptions.png
new file mode 100644
index 000000000..78a015715
Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.Explorer/Images/windowsindexingoptions.png differ
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/de.xaml
new file mode 100644
index 000000000..2209b5bed
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/de.xaml
@@ -0,0 +1,14 @@
+
+
+
+ Löschen
+ Bearbeiten
+ Hinzufügen
+
+
+ Bitte wähle eine Ordnerverknüpfung
+ Bist du sicher {0} zu löschen?
+
+
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml
new file mode 100644
index 000000000..bc3f5664a
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml
@@ -0,0 +1,39 @@
+
+
+
+ Please select a folder link
+ Are you sure you want to delete {0}?
+
+
+ Delete
+ Edit
+ Add
+ Quick Folder Access Paths
+ Index Search Excluded Paths
+ Indexing Options
+
+
+
+ Explorer
+ Search and manage files and folders. Explorer utilises Windows Index Search
+
+
+ Copy path
+ Copy
+ Delete
+ Path:
+ Delete the selected
+ Run as different user
+ Run the selected using a different user account
+ Open containing folder
+ Opens the location that contains the file or folder
+ Open With Editor:
+ Exclude current and sub-directories from Index Search
+ Excluded from Index Search
+ Open Windows Indexing Options
+ Manage indexed files and folders
+ Failed to open Windows Indexing Options
+
+
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pl.xaml
new file mode 100644
index 000000000..9a6b38f6f
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pl.xaml
@@ -0,0 +1,14 @@
+
+
+
+ Usuń
+ Edytuj
+ Dodaj
+
+
+ Musisz wybrać któryś folder z listy
+ Czy jesteś pewien że chcesz usunąć {0}?
+
+
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/tr.xaml
new file mode 100644
index 000000000..d6744fdd4
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/tr.xaml
@@ -0,0 +1,14 @@
+
+
+
+ Sil
+ Düzenle
+ Ekle
+
+
+ Lütfen bir klasör bağlantısı seçin
+ {0} bağlantısını silmek istediğinize emin misiniz?
+
+
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/zh-cn.xaml
new file mode 100644
index 000000000..7f516861e
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/zh-cn.xaml
@@ -0,0 +1,14 @@
+
+
+
+ 删除
+ 编辑
+ 添加
+
+
+ 请选择一个文件夹
+ 你确定要删除{0}吗?
+
+
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/zh-tw.xaml
new file mode 100644
index 000000000..1455aa676
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/zh-tw.xaml
@@ -0,0 +1,14 @@
+
+
+
+ 刪除
+ 編輯
+ 新增
+
+
+ 請選擇一個資料夾
+ 你確認要刪除{0}嗎?
+
+
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs
new file mode 100644
index 000000000..30a06e882
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs
@@ -0,0 +1,58 @@
+using Flow.Launcher.Infrastructure.Storage;
+using Flow.Launcher.Plugin.Explorer.Search;
+using Flow.Launcher.Plugin.Explorer.ViewModels;
+using Flow.Launcher.Plugin.Explorer.Views;
+using System.Collections.Generic;
+using System.Windows.Controls;
+
+namespace Flow.Launcher.Plugin.Explorer
+{
+ public class Main : ISettingProvider, IPlugin, ISavable, IContextMenu, IPluginI18n
+ {
+ internal PluginInitContext Context { get; set; }
+
+ internal Settings Settings;
+
+ private SettingsViewModel viewModel;
+
+ private IContextMenu contextMenu;
+
+ public Control CreateSettingPanel()
+ {
+ return new ExplorerSettings(viewModel);
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ Context = context;
+ viewModel = new SettingsViewModel(context);
+ Settings = viewModel.Settings;
+ contextMenu = new ContextMenu(Context, Settings);
+ }
+
+ public List LoadContextMenus(Result selectedResult)
+ {
+ return contextMenu.LoadContextMenus(selectedResult);
+ }
+
+ public List Query(Query query)
+ {
+ return new SearchManager(Settings, Context).Search(query);
+ }
+
+ public void Save()
+ {
+ viewModel.Save();
+ }
+
+ public string GetTranslatedPluginTitle()
+ {
+ return Context.API.GetTranslation("plugin_explorer_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return Context.API.GetTranslation("plugin_explorer_plugin_description");
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs
new file mode 100644
index 000000000..9c8a300bc
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Flow.Launcher.Plugin.Explorer.Search
+{
+ internal static class Constants
+ {
+ internal const string FolderImagePath = "Images\\folder.png";
+ internal const string FileImagePath = "Images\\file.png";
+ internal const string DeleteFileFolderImagePath = "Images\\deletefilefolder.png";
+ internal const string CopyImagePath = "Images\\copy.png";
+ internal const string IndexImagePath = "Images\\index.png";
+ internal const string ExcludeFromIndexImagePath = "Images\\excludeindexpath.png";
+ internal const string ExplorerIconImagePath = "Images\\explorer.png";
+ internal const string DifferentUserIconImagePath = "Images\\user.png";
+ internal const string IndexingOptionsIconImagePath = "Images\\windowsindexingoptions.png";
+
+ internal const string DefaultFolderSubtitleString = "Ctrl + Enter to open the directory";
+
+ internal const char AllFilesFolderSearchWildcard = '>';
+
+ internal const char DirectorySeperator = '\\';
+
+ internal const string WindowsIndexingOptions = "srchadmin.dll";
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs
new file mode 100644
index 000000000..b198395b8
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs
@@ -0,0 +1,96 @@
+using Flow.Launcher.Infrastructure.Logger;
+using Flow.Launcher.Plugin.SharedCommands;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
+{
+ public class DirectoryInfoSearch
+ {
+ private readonly ResultManager resultManager;
+
+ public DirectoryInfoSearch(PluginInitContext context)
+ {
+ resultManager = new ResultManager(context);
+ }
+
+ internal List TopLevelDirectorySearch(Query query, string search)
+ {
+ var criteria = ConstructSearchCriteria(search);
+
+ if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > search.LastIndexOf(Constants.DirectorySeperator))
+ return DirectorySearch(SearchOption.AllDirectories, query, search, criteria);
+
+ return DirectorySearch(SearchOption.TopDirectoryOnly, query, search, criteria);
+ }
+
+ public string ConstructSearchCriteria(string search)
+ {
+ string incompleteName = "";
+
+ if (!search.EndsWith(Constants.DirectorySeperator))
+ {
+ var indexOfSeparator = search.LastIndexOf(Constants.DirectorySeperator);
+
+ incompleteName = search.Substring(indexOfSeparator + 1).ToLower();
+
+ if (incompleteName.StartsWith(Constants.AllFilesFolderSearchWildcard))
+ incompleteName = "*" + incompleteName.Substring(1);
+ }
+
+ incompleteName += "*";
+
+ return incompleteName;
+ }
+
+ private List DirectorySearch(SearchOption searchOption, Query query, string search, string searchCriteria)
+ {
+ var results = new List();
+
+ var path = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(search);
+
+ var folderList = new List();
+ var fileList = new List();
+
+ try
+ {
+ var directoryInfo = new System.IO.DirectoryInfo(path);
+ var fileSystemInfos = directoryInfo.GetFileSystemInfos(searchCriteria, searchOption);
+
+ foreach (var fileSystemInfo in fileSystemInfos)
+ {
+ if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue;
+
+ if (fileSystemInfo is System.IO.DirectoryInfo)
+ {
+ folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, Constants.DefaultFolderSubtitleString, fileSystemInfo.FullName, query, true, false));
+ }
+ else
+ {
+ fileList.Add(resultManager.CreateFileResult(fileSystemInfo.FullName, query, true, false));
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ if (e is UnauthorizedAccessException || e is ArgumentException)
+ {
+ results.Add(new Result { Title = e.Message, Score = 501 });
+
+ return results;
+ }
+
+#if DEBUG // Please investigate and handle error from DirectoryInfo search
+ throw e;
+#else
+ Log.Exception($"|Flow.Launcher.Plugin.Explorer.DirectoryInfoSearch|Error from performing DirectoryInfoSearch", e);
+#endif
+ }
+
+ // Intial 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();
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs
new file mode 100644
index 000000000..977b9423e
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Flow.Launcher.Plugin.Explorer.Search
+{
+ public static class EnvironmentVariables
+ {
+ internal static bool IsEnvironmentVariableSearch(string search)
+ {
+ return LoadEnvironmentStringPaths().Count > 0
+ && search.StartsWith("%")
+ && search != "%%"
+ && !search.Contains("\\");
+ }
+
+ internal static Dictionary LoadEnvironmentStringPaths()
+ {
+ var envStringPaths = new Dictionary();
+
+ foreach (DictionaryEntry special in Environment.GetEnvironmentVariables())
+ {
+ if (Directory.Exists(special.Value.ToString()))
+ {
+ envStringPaths.Add(special.Key.ToString().ToLower(), special.Value.ToString());
+ }
+ }
+
+ return envStringPaths;
+ }
+
+ internal static string TranslateEnvironmentVariablePath(string environmentVariablePath)
+ {
+ var envStringPaths = LoadEnvironmentStringPaths();
+ var splitSearch = environmentVariablePath.Substring(1).Split("%");
+ var exactEnvStringPath = splitSearch[0];
+
+ // if there are more than 2 % characters in the query, don't bother
+ if (splitSearch.Length == 2 && envStringPaths.ContainsKey(exactEnvStringPath))
+ {
+ var queryPartToReplace = $"%{exactEnvStringPath}%";
+ var expandedPath = envStringPaths[exactEnvStringPath];
+ // replace the %envstring% part of the query with its expanded equivalent
+ return environmentVariablePath.Replace(queryPartToReplace, expandedPath);
+ }
+
+ return environmentVariablePath;
+ }
+
+ internal static List GetEnvironmentStringPathSuggestions(string querySearch, Query query, PluginInitContext context)
+ {
+ var results = new List();
+
+ var environmentVariables = LoadEnvironmentStringPaths();
+ var search = querySearch;
+
+ if (querySearch.EndsWith("%") && search.Length > 1)
+ {
+ // query starts and ends with a %, find an exact match from env-string paths
+ search = querySearch.Substring(1, search.Length - 2);
+
+ if (environmentVariables.ContainsKey(search))
+ {
+ var expandedPath = environmentVariables[search];
+
+ results.Add(new ResultManager(context).CreateFolderResult($"%{search}%", expandedPath, expandedPath, query));
+
+ return results;
+ }
+ }
+
+ if (querySearch == "%")
+ {
+ search = ""; // Get all paths
+ }
+ else
+ {
+ search = search.Substring(1);
+ }
+
+ foreach (var p in environmentVariables)
+ {
+ if (p.Key.StartsWith(search))
+ {
+ results.Add(new ResultManager(context).CreateFolderResult($"%{p.Key}%", p.Value, p.Value, query));
+ }
+ }
+ return results;
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/FolderLink.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/FolderLink.cs
new file mode 100644
index 000000000..379b5848f
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/FolderLink.cs
@@ -0,0 +1,29 @@
+using Newtonsoft.Json;
+using System;
+using System.Linq;
+
+namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks
+{
+ [JsonObject(MemberSerialization.OptIn)]
+ public class FolderLink
+ {
+ [JsonProperty]
+ public string Path { get; set; }
+
+ public string Nickname
+ {
+ get
+ {
+ var path = Path.EndsWith(Constants.DirectorySeperator) ? Path[0..^1] : Path;
+
+ if (path.EndsWith(':'))
+ return path[0..^1] + " Drive";
+
+ return path.Split(new[] { System.IO.Path.DirectorySeparatorChar }, StringSplitOptions.None)
+ .Last()
+ + " (" + System.IO.Path.GetDirectoryName(Path) + ")";
+ }
+ }
+ }
+
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs
new file mode 100644
index 000000000..689c865c0
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/FolderLinks/QuickFolderAccess.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks
+{
+ public class QuickFolderAccess
+ {
+ internal List FolderList(Query query, List folderLinks, PluginInitContext context)
+ {
+ if (string.IsNullOrEmpty(query.Search))
+ return folderLinks
+ .Select(item =>
+ new ResultManager(context)
+ .CreateFolderResult(item.Nickname, Constants.DefaultFolderSubtitleString, item.Path, query))
+ .ToList();
+
+ string search = query.Search.ToLower();
+
+ var queriedFolderLinks = folderLinks.Where(x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase));
+
+ return queriedFolderLinks.Select(item =>
+ new ResultManager(context)
+ .CreateFolderResult(item.Nickname, Constants.DefaultFolderSubtitleString, item.Path, query))
+ .ToList();
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
new file mode 100644
index 000000000..e2fd1c7da
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
@@ -0,0 +1,135 @@
+using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Plugin.SharedCommands;
+using System;
+using System.IO;
+using System.Linq;
+using System.Windows;
+
+namespace Flow.Launcher.Plugin.Explorer.Search
+{
+ public class ResultManager
+ {
+ private readonly PluginInitContext context;
+
+ public ResultManager(PluginInitContext context)
+ {
+ this.context = context;
+ }
+ internal Result CreateFolderResult(string title, string subtitle, string path, Query query, bool showIndexState = false, bool windowsIndexed = false)
+ {
+ return new Result
+ {
+ Title = title,
+ IcoPath = path,
+ SubTitle = subtitle,
+ TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData,
+ Action = c =>
+ {
+ if (c.SpecialKeyState.CtrlPressed)
+ {
+ try
+ {
+ FilesFolders.OpenPath(path);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Could not start " + path);
+ return false;
+ }
+ }
+
+ string changeTo = path.EndsWith(Constants.DirectorySeperator) ? path : path + Constants.DirectorySeperator;
+ context.API.ChangeQuery(string.IsNullOrEmpty(query.ActionKeyword) ?
+ changeTo :
+ query.ActionKeyword + " " + changeTo);
+ return false;
+ },
+ ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, ShowIndexState = showIndexState, WindowsIndexed = windowsIndexed }
+ };
+ }
+
+ internal Result CreateOpenCurrentFolderResult(string path, bool windowsIndexed = false)
+ {
+ var retrievedDirectoryPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path);
+
+ var folderName = retrievedDirectoryPath.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 subtitleFolderName = folderName;
+
+ // ie. max characters can be displayed without subtitle cutting off: "Program Files (x86)"
+ if (folderName.Length > 19)
+ subtitleFolderName = "the directory";
+
+ return new Result
+ {
+ Title = title,
+ SubTitle = $"Use > to search within {subtitleFolderName}, " +
+ $"* to search for file extensions or >* to combine both searches.",
+ IcoPath = retrievedDirectoryPath,
+ Score = 500,
+ Action = c =>
+ {
+ FilesFolders.OpenPath(retrievedDirectoryPath);
+ return true;
+ },
+ ContextData = new SearchResult { Type = ResultType.Folder, FullPath = retrievedDirectoryPath, ShowIndexState = true, WindowsIndexed = windowsIndexed }
+ };
+ }
+
+ internal Result CreateFileResult(string filePath, Query query, bool showIndexState = false, bool windowsIndexed = false)
+ {
+ var result = new Result
+ {
+ Title = Path.GetFileName(filePath),
+ SubTitle = filePath,
+ IcoPath = filePath,
+ TitleHighlightData = StringMatcher.FuzzySearch(query.Search, Path.GetFileName(filePath)).MatchData,
+ Action = c =>
+ {
+ try
+ {
+ FilesFolders.OpenPath(filePath);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Could not start " + filePath);
+ }
+
+ return true;
+ },
+ ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, ShowIndexState = showIndexState, WindowsIndexed = windowsIndexed }
+ };
+ return result;
+ }
+ }
+
+ internal class SearchResult
+ {
+ public string FullPath { get; set; }
+ public ResultType Type { get; set; }
+
+ public bool WindowsIndexed { get; set; }
+
+ public bool ShowIndexState { get; set; }
+ }
+
+ internal enum ResultType
+ {
+ Volume,
+ Folder,
+ File
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
new file mode 100644
index 000000000..f814962ba
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
@@ -0,0 +1,130 @@
+using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo;
+using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
+using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex;
+using Flow.Launcher.Plugin.SharedCommands;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Flow.Launcher.Plugin.Explorer.Search
+{
+ public class SearchManager
+ {
+ private readonly PluginInitContext context;
+
+ private readonly IndexSearch indexSearch;
+
+ private readonly QuickFolderAccess quickFolderAccess = new QuickFolderAccess();
+
+ private readonly ResultManager resultManager;
+
+ private readonly Settings settings;
+
+ public SearchManager(Settings settings, PluginInitContext context)
+ {
+ this.context = context;
+ indexSearch = new IndexSearch(context);
+ resultManager = new ResultManager(context);
+ this.settings = settings;
+ }
+
+ internal List Search(Query query)
+ {
+ var results = new List();
+
+ var querySearch = query.Search;
+
+ var quickFolderLinks = quickFolderAccess.FolderList(query, settings.QuickFolderAccessLinks, context);
+
+ if (quickFolderLinks.Count > 0)
+ return quickFolderLinks;
+
+ if (string.IsNullOrEmpty(querySearch))
+ return results;
+
+ var isEnvironmentVariable = EnvironmentVariables.IsEnvironmentVariableSearch(querySearch);
+
+ if (isEnvironmentVariable)
+ return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, context);
+
+ // Query is a location path with a full environment variable, eg. %appdata%\somefolder\
+ var isEnvironmentVariablePath = querySearch.Substring(1).Contains("%\\");
+
+ if (!FilesFolders.IsLocationPathString(querySearch) && !isEnvironmentVariablePath)
+ return WindowsIndexFilesAndFoldersSearch(query, querySearch);
+
+ var locationPath = querySearch;
+
+ if (isEnvironmentVariablePath)
+ locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath);
+
+ if (!FilesFolders.LocationExists(FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath)))
+ return results;
+
+ var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath);
+
+ results.Add(resultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch));
+
+ results.AddRange(TopLevelDirectorySearchBehaviour(WindowsIndexTopLevelFolderSearch,
+ DirectoryInfoClassSearch,
+ useIndexSearch,
+ query,
+ locationPath));
+
+ return results;
+ }
+
+ private List DirectoryInfoClassSearch(Query query, string querySearch)
+ {
+ var directoryInfoSearch = new DirectoryInfoSearch(context);
+
+ return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch);
+ }
+
+ public List TopLevelDirectorySearchBehaviour(
+ Func> windowsIndexSearch,
+ Func> directoryInfoClassSearch,
+ bool useIndexSearch,
+ Query query,
+ string querySearchString)
+ {
+ if (!useIndexSearch)
+ return directoryInfoClassSearch(query, querySearchString);
+
+ return windowsIndexSearch(query, querySearchString);
+ }
+
+ private List WindowsIndexFilesAndFoldersSearch(Query query, string querySearchString)
+ {
+ var queryConstructor = new QueryConstructor(settings);
+
+ return indexSearch.WindowsIndexSearch(querySearchString,
+ queryConstructor.CreateQueryHelper().ConnectionString,
+ queryConstructor.QueryForAllFilesAndFolders,
+ query);
+ }
+
+ private List WindowsIndexTopLevelFolderSearch(Query query, string path)
+ {
+ var queryConstructor = new QueryConstructor(settings);
+
+ return indexSearch.WindowsIndexSearch(path,
+ queryConstructor.CreateQueryHelper().ConnectionString,
+ queryConstructor.QueryForTopLevelDirectorySearch,
+ query);
+ }
+
+ private bool UseWindowsIndexForDirectorySearch(string locationPath)
+ {
+ if (!settings.UseWindowsIndexForDirectorySearch)
+ return false;
+
+ if (settings.IndexSearchExcludedSubdirectoryPaths
+ .Any(x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath)
+ .StartsWith(x.Path, StringComparison.OrdinalIgnoreCase)))
+ return false;
+
+ return indexSearch.PathIsIndexed(locationPath);
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs
new file mode 100644
index 000000000..5401c2845
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/IndexSearch.cs
@@ -0,0 +1,117 @@
+using Flow.Launcher.Infrastructure.Logger;
+using Microsoft.Search.Interop;
+using System;
+using System.Collections.Generic;
+using System.Data.OleDb;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
+{
+ internal class IndexSearch
+ {
+ private readonly object _lock = new object();
+
+ private OleDbConnection conn;
+
+ private OleDbCommand command;
+
+ private OleDbDataReader dataReaderResults;
+
+ private readonly ResultManager resultManager;
+
+ // Reserved keywords in oleDB
+ private readonly string reservedStringPattern = @"^[\/\\\$\%]+$";
+
+ internal IndexSearch(PluginInitContext context)
+ {
+ resultManager = new ResultManager(context);
+ }
+
+ internal List ExecuteWindowsIndexSearch(string indexQueryString, string connectionString, Query query)
+ {
+ var folderResults = new List();
+ var fileResults = new List();
+ var results = new List();
+
+ try
+ {
+ using (conn = new OleDbConnection(connectionString))
+ {
+ conn.Open();
+
+ using (command = new OleDbCommand(indexQueryString, conn))
+ {
+ // Results return as an OleDbDataReader.
+ using (dataReaderResults = command.ExecuteReader())
+ {
+ if (dataReaderResults.HasRows)
+ {
+ while (dataReaderResults.Read())
+ {
+ if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value)
+ {
+ if (dataReaderResults.GetString(2) == "Directory")
+ {
+ folderResults.Add(resultManager.CreateFolderResult(
+ dataReaderResults.GetString(0),
+ Constants.DefaultFolderSubtitleString,
+ dataReaderResults.GetString(1),
+ query, true, true));
+ }
+ else
+ {
+ fileResults.Add(resultManager.CreateFileResult(dataReaderResults.GetString(1), query, true, true));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ 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);
+ }
+
+ // Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
+ return results.Concat(folderResults.OrderBy(x => x.Title)).Concat(fileResults.OrderBy(x => x.Title)).ToList(); ;
+ }
+
+ internal List WindowsIndexSearch(string searchString, string connectionString, Func constructQuery, Query query)
+ {
+ var regexMatch = Regex.Match(searchString, reservedStringPattern);
+
+ if (regexMatch.Success)
+ return new List();
+
+ lock (_lock)
+ {
+ var constructedQuery = constructQuery(searchString);
+ return ExecuteWindowsIndexSearch(constructedQuery, connectionString, query);
+ }
+ }
+
+ internal bool PathIsIndexed(string path)
+ {
+ var csm = new CSearchManager();
+ var indexManager = csm.GetCatalog("SystemIndex").GetCrawlScopeManager();
+ return indexManager.IncludedInCrawlScope(path) > 0;
+ }
+
+ private 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
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs
new file mode 100644
index 000000000..f4480eea7
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs
@@ -0,0 +1,121 @@
+using Microsoft.Search.Interop;
+
+namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
+{
+ public class QueryConstructor
+ {
+ private readonly Settings settings;
+
+ private const string SystemIndex = "SystemIndex";
+
+ public QueryConstructor(Settings settings)
+ {
+ this.settings = settings;
+ }
+
+ public 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;
+
+ // Set list of columns we want to display, getting the path presently
+ baseQuery.QuerySelectColumns = "System.FileName, System.ItemPathDisplay, System.ItemType";
+
+ // Filter based on file name
+ baseQuery.QueryContentProperties = "System.FileName";
+
+ // Set sorting order
+ //baseQuery.QuerySorting = "System.ItemType DESC";
+
+ return baseQuery;
+ }
+
+ internal CSearchQueryHelper CreateQueryHelper()
+ {
+ // This uses the Microsoft.Search.Interop assembly
+ var manager = new CSearchManager();
+
+ // SystemIndex catalog is the default catalog in Windows
+ var catalogManager = manager.GetCatalog(SystemIndex);
+
+ // Get the ISearchQueryHelper which will help us to translate AQS --> SQL necessary to query the indexer
+ var queryHelper = catalogManager.GetQueryHelper();
+
+ return queryHelper;
+ }
+
+ ///
+ /// Set the required WHERE clause restriction to search on the first level of a specified directory.
+ ///
+ public string QueryWhereRestrictionsForTopLevelDirectorySearch(string path)
+ {
+ var searchDepth = $"directory='file:";
+
+ return QueryWhereRestrictionsFromLocationPath(path, searchDepth);
+ }
+
+ ///
+ /// Set the required WHERE clause restriction to search all files and subfolders of a specified directory.
+ ///
+ 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}'";
+ }
+
+ ///
+ /// Search will be performed on all folders and files on the first level of a specified directory.
+ ///
+ public string QueryForTopLevelDirectorySearch(string path)
+ {
+ string query = "SELECT TOP " + settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE ";
+
+ if (path.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > path.LastIndexOf(Constants.DirectorySeperator))
+ return query + QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path);
+
+ return query + QueryWhereRestrictionsForTopLevelDirectorySearch(path);
+ }
+
+ ///
+ /// Search will be performed on all folders and files based on user's search keywords.
+ ///
+ public string QueryForAllFilesAndFolders(string userSearchString)
+ {
+ // Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause
+ return CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch();
+ }
+
+ ///
+ /// Set the required WHERE clause restriction to search for all files and folders.
+ ///
+ public string QueryWhereRestrictionsForAllFilesAndFoldersSearch()
+ {
+ return $"scope='file:'";
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs
new file mode 100644
index 000000000..d4bfdbbfc
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs
@@ -0,0 +1,21 @@
+using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+
+namespace Flow.Launcher.Plugin.Explorer
+{
+ public class Settings
+ {
+ [JsonProperty]
+ public int MaxResult { get; set; } = 100;
+
+ [JsonProperty]
+ public List QuickFolderAccessLinks { get; set; } = new List();
+
+ [JsonProperty]
+ public bool UseWindowsIndexForDirectorySearch { get; set; } = true;
+
+ [JsonProperty]
+ public List IndexSearchExcludedSubdirectoryPaths { get; set; } = new List();
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
new file mode 100644
index 000000000..a5e81716a
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
@@ -0,0 +1,44 @@
+using Flow.Launcher.Infrastructure.Storage;
+using Flow.Launcher.Plugin.Explorer.Search;
+using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
+using System.Diagnostics;
+
+namespace Flow.Launcher.Plugin.Explorer.ViewModels
+{
+ public class SettingsViewModel
+ {
+ private readonly PluginJsonStorage storage;
+
+ internal Settings Settings { get; set; }
+
+ internal PluginInitContext Context { get; set; }
+
+ public SettingsViewModel(PluginInitContext context)
+ {
+ Context = context;
+ storage = new PluginJsonStorage();
+ Settings = storage.Load();
+ }
+
+ public void Save()
+ {
+ storage.Save();
+ }
+
+ internal void RemoveFolderLinkFromQuickFolders(FolderLink selectedRow) => Settings.QuickFolderAccessLinks.Remove(selectedRow);
+
+ internal void RemoveFolderLinkFromExcludedIndexPaths(FolderLink selectedRow) => Settings.IndexSearchExcludedSubdirectoryPaths.Remove(selectedRow);
+
+ internal void OpenWindowsIndexingOptions()
+ {
+ var psi = new ProcessStartInfo
+ {
+ FileName = "control.exe",
+ UseShellExecute = true,
+ Arguments = Constants.WindowsIndexingOptions
+ };
+
+ Process.Start(psi);
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml
new file mode 100644
index 000000000..5ba1a063b
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs
new file mode 100644
index 000000000..11c8f6514
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs
@@ -0,0 +1,245 @@
+using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
+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 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
+{
+ ///
+ /// Interaction logic for ExplorerSettings.xaml
+ ///
+ public partial class ExplorerSettings
+ {
+ private readonly SettingsViewModel viewModel;
+ public ExplorerSettings(SettingsViewModel viewModel)
+ {
+ InitializeComponent();
+
+ this.viewModel = viewModel;
+
+ lbxFolderLinks.ItemsSource = this.viewModel.Settings.QuickFolderAccessLinks;
+
+ lbxExcludedPaths.ItemsSource = this.viewModel.Settings.IndexSearchExcludedSubdirectoryPaths;
+
+ RefreshView();
+ }
+
+ public void RefreshView()
+ {
+ lbxFolderLinks.Items.SortDescriptions.Add(new SortDescription("Path", ListSortDirection.Ascending));
+
+ lbxExcludedPaths.Items.SortDescriptions.Add(new SortDescription("Path", ListSortDirection.Ascending));
+
+ btnDelete.Visibility = Visibility.Hidden;
+ btnEdit.Visibility = Visibility.Hidden;
+ btnAdd.Visibility = Visibility.Hidden;
+
+ if (expFolderLinks.IsExpanded || expExcludedPaths.IsExpanded)
+ {
+ btnAdd.Visibility = Visibility.Visible;
+
+ if ((lbxFolderLinks.Items.Count == 0 && lbxExcludedPaths.Items.Count == 0)
+ && btnDelete.Visibility == Visibility.Visible
+ && btnEdit.Visibility == Visibility.Visible)
+ {
+ btnDelete.Visibility = Visibility.Hidden;
+ btnEdit.Visibility = Visibility.Hidden;
+ }
+
+ if (expFolderLinks.IsExpanded
+ && lbxFolderLinks.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;
+ }
+ }
+
+ lbxFolderLinks.Items.Refresh();
+
+ lbxExcludedPaths.Items.Refresh();
+ }
+
+ private void expFolderLinks_Click(object sender, RoutedEventArgs e)
+ {
+ if (expFolderLinks.IsExpanded)
+ expFolderLinks.Height = 235;
+
+ if (!expFolderLinks.IsExpanded)
+ expFolderLinks.Height = Double.NaN;
+
+ if (expExcludedPaths.IsExpanded)
+ expExcludedPaths.IsExpanded = false;
+
+ RefreshView();
+ }
+
+ private void expExcludedPaths_Click(object sender, RoutedEventArgs e)
+ {
+ if (expExcludedPaths.IsExpanded)
+ expFolderLinks.Height = Double.NaN;
+
+ if (expFolderLinks.IsExpanded)
+ expFolderLinks.IsExpanded = false;
+
+ RefreshView();
+ }
+
+ private void btnDelete_Click(object sender, RoutedEventArgs e)
+ {
+ var selectedRow = lbxFolderLinks.SelectedItem as FolderLink?? lbxExcludedPaths.SelectedItem as FolderLink;
+
+ if (selectedRow != null)
+ {
+ 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 (expFolderLinks.IsExpanded)
+ viewModel.RemoveFolderLinkFromQuickFolders(selectedRow);
+
+ if (expExcludedPaths.IsExpanded)
+ viewModel.RemoveFolderLinkFromExcludedIndexPaths(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)
+ {
+ var selectedRow = lbxFolderLinks.SelectedItem as FolderLink ?? lbxExcludedPaths.SelectedItem as FolderLink;
+
+ if (selectedRow != null)
+ {
+ var folderBrowserDialog = new FolderBrowserDialog();
+ folderBrowserDialog.SelectedPath = selectedRow.Path;
+ if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
+ {
+ if (expFolderLinks.IsExpanded)
+ {
+ var link = viewModel.Settings.QuickFolderAccessLinks.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();
+ }
+ else
+ {
+ string warning = viewModel.Context.API.GetTranslation("plugin_explorer_select_folder_link_warning");
+ MessageBox.Show(warning);
+ }
+ }
+
+ private void btnAdd_Click(object sender, RoutedEventArgs e)
+ {
+ var folderBrowserDialog = new FolderBrowserDialog();
+ if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
+ {
+ var newFolderLink = new FolderLink
+ {
+ Path = folderBrowserDialog.SelectedPath
+ };
+
+ AddFolderLink(newFolderLink);
+ }
+
+ RefreshView();
+ }
+
+ private void lbxFolders_Drop(object sender, DragEventArgs e)
+ {
+ string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
+
+ if (files != null && files.Count() > 0)
+ {
+ if (expFolderLinks.IsExpanded && viewModel.Settings.QuickFolderAccessLinks == null)
+ viewModel.Settings.QuickFolderAccessLinks = new List();
+
+ foreach (string s in files)
+ {
+ if (Directory.Exists(s))
+ {
+ var newFolderLink = new FolderLink
+ {
+ Path = s
+ };
+
+ AddFolderLink(newFolderLink);
+ }
+
+ RefreshView();
+ }
+ }
+ }
+
+ private void AddFolderLink(FolderLink newFolderLink)
+ {
+ if (expFolderLinks.IsExpanded
+ && !viewModel.Settings.QuickFolderAccessLinks.Any(x => x.Path == newFolderLink.Path))
+ {
+ if (viewModel.Settings.QuickFolderAccessLinks == null)
+ viewModel.Settings.QuickFolderAccessLinks = new List();
+
+ viewModel.Settings.QuickFolderAccessLinks.Add(newFolderLink);
+ }
+
+ if (expExcludedPaths.IsExpanded
+ && !viewModel.Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x.Path == newFolderLink.Path))
+ {
+ if (viewModel.Settings.IndexSearchExcludedSubdirectoryPaths == null)
+ viewModel.Settings.IndexSearchExcludedSubdirectoryPaths = new List();
+
+ viewModel.Settings.IndexSearchExcludedSubdirectoryPaths.Add(newFolderLink);
+ }
+ }
+
+ private void lbxFolders_DragEnter(object sender, DragEventArgs e)
+ {
+ if (e.Data.GetDataPresent(DataFormats.FileDrop))
+ {
+ e.Effects = DragDropEffects.Link;
+ }
+ else
+ {
+ e.Effects = DragDropEffects.None;
+ }
+ }
+
+ private void btnOpenIndexingOptions_Click(object sender, RoutedEventArgs e)
+ {
+ viewModel.OpenWindowsIndexingOptions();
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json
new file mode 100644
index 000000000..10b3edd96
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json
@@ -0,0 +1,12 @@
+{
+ "ID": "572be03c74c642baae319fc283e561a8",
+ "ActionKeyword": "*",
+ "Name": "Explorer",
+ "Description": "Search and manage files and folders. Explorer utilises Windows Index Search",
+ "Author": "Jeremy Wu",
+ "Version": "1.0.0",
+ "Language": "csharp",
+ "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
+ "ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",
+ "IcoPath": "Images\\explorer.png"
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/ContextMenuLoader.cs b/Plugins/Flow.Launcher.Plugin.Folder/ContextMenuLoader.cs
deleted file mode 100644
index 561047932..000000000
--- a/Plugins/Flow.Launcher.Plugin.Folder/ContextMenuLoader.cs
+++ /dev/null
@@ -1,219 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Threading.Tasks;
-using System.Windows;
-using Flow.Launcher.Infrastructure.Logger;
-using Flow.Launcher.Infrastructure.Image;
-using Flow.Launcher.Plugin.SharedCommands;
-
-namespace Flow.Launcher.Plugin.Folder
-{
- internal class ContextMenuLoader : IContextMenu
- {
- private readonly PluginInitContext _context;
-
- public ContextMenuLoader(PluginInitContext context)
- {
- _context = context;
- }
-
- public List LoadContextMenus(Result selectedResult)
- {
- var contextMenus = new List();
- if (selectedResult.ContextData is SearchResult record)
- {
- if (record.Type == ResultType.File)
- {
- contextMenus.Add(CreateOpenWithEditorResult(record));
- contextMenus.Add(CreateOpenContainingFolderResult(record));
- }
-
- var icoPath = (record.Type == ResultType.File) ? Main.FileImagePath : Main.FolderImagePath;
- var fileOrFolder = (record.Type == ResultType.File) ? "file" : "folder";
- contextMenus.Add(new Result
- {
- Title = "Copy path",
- SubTitle = $"Copy the current {fileOrFolder} path to clipboard",
- Action = (context) =>
- {
- try
- {
- Clipboard.SetText(record.FullPath);
- return true;
- }
- catch (Exception e)
- {
- var message = "Fail to set text in clipboard";
- LogException(message, e);
- _context.API.ShowMsg(message);
- return false;
- }
- },
- IcoPath = Main.CopyImagePath
- });
-
- contextMenus.Add(new Result
- {
- Title = $"Copy {fileOrFolder}",
- SubTitle = $"Copy the {fileOrFolder} to clipboard",
- Action = (context) =>
- {
- try
- {
- Clipboard.SetFileDropList(new System.Collections.Specialized.StringCollection { record.FullPath });
- return true;
- }
- catch (Exception e)
- {
- var message = $"Fail to set {fileOrFolder} in clipboard";
- LogException(message, e);
- _context.API.ShowMsg(message);
- return false;
- }
-
- },
- IcoPath = icoPath
- });
-
- if (record.Type == ResultType.File || record.Type == ResultType.Folder)
- contextMenus.Add(new Result
- {
- Title = $"Delete {fileOrFolder}",
- SubTitle = $"Delete the selected {fileOrFolder}",
- Action = (context) =>
- {
- try
- {
- if (record.Type == ResultType.File)
- File.Delete(record.FullPath);
- else
- Directory.Delete(record.FullPath, true);
- }
- catch(Exception e)
- {
- var message = $"Fail to delete {fileOrFolder} at {record.FullPath}";
- LogException(message, e);
- _context.API.ShowMsg(message);
- return false;
- }
-
- return true;
- },
- IcoPath = Main.DeleteFileFolderImagePath
- });
-
- if (record.Type == ResultType.File && CanRunAsDifferentUser(record.FullPath))
- contextMenus.Add(new Result
- {
- Title = "Run as different user",
- Action = (context) =>
- {
- try
- {
- Task.Run(()=> ShellCommand.RunAsDifferentUser(record.FullPath.SetProcessStartInfo()));
- }
- catch (FileNotFoundException e)
- {
- var name = "Plugin: Folder";
- var message = $"File not found: {e.Message}";
- _context.API.ShowMsg(name, message);
- }
-
- return true;
- },
- IcoPath = "Images/user.png"
- });
- }
-
- return contextMenus;
- }
-
- private Result CreateOpenContainingFolderResult(SearchResult record)
- {
- return new Result
- {
- Title = "Open containing folder",
- Action = _ =>
- {
- try
- {
- Process.Start("explorer.exe", $" /select,\"{record.FullPath}\"");
- }
- catch(Exception e)
- {
- var message = $"Fail to open file at {record.FullPath}";
- LogException(message, e);
- _context.API.ShowMsg(message);
- return false;
- }
-
- return true;
- },
- IcoPath = Main.FolderImagePath
- };
- }
-
-
- private Result CreateOpenWithEditorResult(SearchResult record)
- {
- string editorPath = "notepad.exe"; // TODO add the ability to create a custom editor
-
- var name = "Open With Editor: " + Path.GetFileNameWithoutExtension(editorPath);
- return new Result
- {
- Title = name,
- Action = _ =>
- {
- try
- {
- Process.Start(editorPath, record.FullPath);
- return true;
- }
- catch (Exception e)
- {
- var message = $"Fail to editor for file at {record.FullPath}";
- LogException(message, e);
- _context.API.ShowMsg(message);
- return false;
- }
- },
- IcoPath = editorPath
- };
- }
-
- public void LogException(string message, Exception e)
- {
- Log.Exception($"|Flow.Launcher.Plugin.Folder.ContextMenu|{message}", e);
- }
-
- private bool CanRunAsDifferentUser(string path)
- {
- switch(Path.GetExtension(path))
- {
- case ".exe":
- case ".bat":
- case ".msi":
- return true;
-
- default:
- return false;
-
- }
- }
- }
-
- public class SearchResult
- {
- public string FullPath { get; set; }
- public ResultType Type { get; set; }
- }
-
- public enum ResultType
- {
- Volume,
- Folder,
- File
- }
-}
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/FolderLink.cs b/Plugins/Flow.Launcher.Plugin.Folder/FolderLink.cs
deleted file mode 100644
index b7bcb1d34..000000000
--- a/Plugins/Flow.Launcher.Plugin.Folder/FolderLink.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using System.Linq;
-using Newtonsoft.Json;
-
-namespace Flow.Launcher.Plugin.Folder
-{
- [JsonObject(MemberSerialization.OptIn)]
- public class FolderLink
- {
- [JsonProperty]
- public string Path { get; set; }
-
- public string Nickname =>
- Path.Split(new[] { System.IO.Path.DirectorySeparatorChar }, StringSplitOptions.None)
- .Last()
- + " (" + System.IO.Path.GetDirectoryName(Path) + ")";
- }
-}
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/FolderPluginSettings.xaml b/Plugins/Flow.Launcher.Plugin.Folder/FolderPluginSettings.xaml
deleted file mode 100644
index fb43d6b7c..000000000
--- a/Plugins/Flow.Launcher.Plugin.Folder/FolderPluginSettings.xaml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/FolderPluginSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Folder/FolderPluginSettings.xaml.cs
deleted file mode 100644
index c95f70ef9..000000000
--- a/Plugins/Flow.Launcher.Plugin.Folder/FolderPluginSettings.xaml.cs
+++ /dev/null
@@ -1,156 +0,0 @@
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.IO;
-using System.Linq;
-using System.Windows;
-using System.Windows.Forms;
-using DataFormats = System.Windows.DataFormats;
-using DragDropEffects = System.Windows.DragDropEffects;
-using DragEventArgs = System.Windows.DragEventArgs;
-using MessageBox = System.Windows.MessageBox;
-
-namespace Flow.Launcher.Plugin.Folder
-{
-
- public partial class FolderPluginSettings
- {
- private IPublicAPI flowlauncherAPI;
- private Settings _settings;
-
- public FolderPluginSettings(IPublicAPI flowlauncherAPI, Settings settings)
- {
- this.flowlauncherAPI = flowlauncherAPI;
- InitializeComponent();
- _settings = settings;
- lbxFolders.ItemsSource = _settings.FolderLinks;
-
- RefreshView();
- }
-
- public void RefreshView()
- {
- lbxFolders.Items.SortDescriptions.Add(new SortDescription("Path", ListSortDirection.Ascending));
-
- if (lbxFolders.Items.Count == 0
- && btnDelete.Visibility == Visibility.Visible
- && btnEdit.Visibility == Visibility.Visible)
- {
- btnDelete.Visibility = Visibility.Hidden;
- btnEdit.Visibility = Visibility.Hidden;
- }
-
- if (lbxFolders.Items.Count > 0
- && btnDelete.Visibility == Visibility.Hidden
- && btnEdit.Visibility == Visibility.Hidden)
- {
- btnDelete.Visibility = Visibility.Visible;
- btnEdit.Visibility = Visibility.Visible;
- }
-
- lbxFolders.Items.Refresh();
- }
-
- private void btnDelete_Click(object sender, RoutedEventArgs e)
- {
- var selectedFolder = lbxFolders.SelectedItem as FolderLink;
- if (selectedFolder != null)
- {
- string msg = string.Format(flowlauncherAPI.GetTranslation("flowlauncher_plugin_folder_delete_folder_link"), selectedFolder.Path);
-
- if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
- {
- _settings.FolderLinks.Remove(selectedFolder);
- RefreshView();
- }
- }
- else
- {
- string warning = flowlauncherAPI.GetTranslation("flowlauncher_plugin_folder_select_folder_link_warning");
- MessageBox.Show(warning);
- }
- }
-
- private void btnEdit_Click(object sender, RoutedEventArgs e)
- {
- var selectedFolder = lbxFolders.SelectedItem as FolderLink;
- if (selectedFolder != null)
- {
- var folderBrowserDialog = new FolderBrowserDialog();
- folderBrowserDialog.SelectedPath = selectedFolder.Path;
- if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
- {
- var link = _settings.FolderLinks.First(x => x.Path == selectedFolder.Path);
- link.Path = folderBrowserDialog.SelectedPath;
- }
-
- RefreshView();
- }
- else
- {
- string warning = flowlauncherAPI.GetTranslation("flowlauncher_plugin_folder_select_folder_link_warning");
- MessageBox.Show(warning);
- }
- }
-
- private void btnAdd_Click(object sender, RoutedEventArgs e)
- {
- var folderBrowserDialog = new FolderBrowserDialog();
- if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
- {
- var newFolder = new FolderLink
- {
- Path = folderBrowserDialog.SelectedPath
- };
-
- if (_settings.FolderLinks == null)
- {
- _settings.FolderLinks = new List();
- }
-
- _settings.FolderLinks.Add(newFolder);
- }
-
- RefreshView();
- }
-
- private void lbxFolders_Drop(object sender, DragEventArgs e)
- {
- string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
-
- if (files != null && files.Count() > 0)
- {
- if (_settings.FolderLinks == null)
- {
- _settings.FolderLinks = new List();
- }
-
- foreach (string s in files)
- {
- if (Directory.Exists(s))
- {
- var newFolder = new FolderLink
- {
- Path = s
- };
-
- _settings.FolderLinks.Add(newFolder);
- }
-
- RefreshView();
- }
- }
- }
-
- private void lbxFolders_DragEnter(object sender, DragEventArgs e)
- {
- if (e.Data.GetDataPresent(DataFormats.FileDrop))
- {
- e.Effects = DragDropEffects.Link;
- }
- else
- {
- e.Effects = DragDropEffects.None;
- }
- }
- }
-}
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.Folder/Languages/de.xaml
deleted file mode 100644
index b39e756b6..000000000
--- a/Plugins/Flow.Launcher.Plugin.Folder/Languages/de.xaml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- Löschen
- Bearbeiten
- Hinzufügen
- Ordnerpfad
- Bitte wähle eine Ordnerverknüpfung
- Bist du sicher {0} zu löschen?
-
- Ordner
- Öffne deine Lieblingsordner direkt von Flow Launcher aus
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Folder/Languages/en.xaml
deleted file mode 100644
index 9ee62927d..000000000
--- a/Plugins/Flow.Launcher.Plugin.Folder/Languages/en.xaml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- Delete
- Edit
- Add
- Folder Path
- Please select a folder link
- Are you sure you want to delete {0}?
-
- Folder
- Open favorite folder from Flow Launcher directly
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.Folder/Languages/pl.xaml
deleted file mode 100644
index 440da079e..000000000
--- a/Plugins/Flow.Launcher.Plugin.Folder/Languages/pl.xaml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- Usuń
- Edytuj
- Dodaj
- Ścieżka folderu
- Musisz wybrać któryś folder z listy
- Czy jesteś pewien że chcesz usunąć {0}?
-
- Foldery
- Otwórz ulubione foldery bezpośrednio z poziomu Flow Launchera
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.Folder/Languages/tr.xaml
deleted file mode 100644
index f9d4beb1e..000000000
--- a/Plugins/Flow.Launcher.Plugin.Folder/Languages/tr.xaml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- Sil
- Düzenle
- Ekle
- Klasör Konumu
- Lütfen bir klasör bağlantısı seçin
- {0} bağlantısını silmek istediğinize emin misiniz?
-
- Klasör
- Favori klasörünüzü doğrudan Flow Launcher'tan açın
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Folder/Languages/zh-cn.xaml
deleted file mode 100644
index 434728720..000000000
--- a/Plugins/Flow.Launcher.Plugin.Folder/Languages/zh-cn.xaml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- 删除
- 编辑
- 添加
- 文件夹路径
- 请选择一个文件夹
- 你确定要删除{0}吗?
-
- 文件夹
- 在Flow Launcher中直接打开收藏的文件夹
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.Folder/Languages/zh-tw.xaml
deleted file mode 100644
index 22a08f56a..000000000
--- a/Plugins/Flow.Launcher.Plugin.Folder/Languages/zh-tw.xaml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- 刪除
- 編輯
- 新增
- 資料夾路徑
- 請選擇一個資料夾
- 你確認要刪除{0}嗎?
-
- 資料夾
- 在 Flow Launcher 中直接開啟收藏的資料夾
-
-
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/Main.cs b/Plugins/Flow.Launcher.Plugin.Folder/Main.cs
deleted file mode 100644
index dd85dfd18..000000000
--- a/Plugins/Flow.Launcher.Plugin.Folder/Main.cs
+++ /dev/null
@@ -1,396 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Windows;
-using System.Windows.Controls;
-using Flow.Launcher.Infrastructure;
-using Flow.Launcher.Infrastructure.Storage;
-using Flow.Launcher.Plugin.SharedCommands;
-
-namespace Flow.Launcher.Plugin.Folder
-{
- public class Main : IPlugin, ISettingProvider, IPluginI18n, ISavable, IContextMenu
- {
- public const string FolderImagePath = "Images\\folder.png";
- public const string FileImagePath = "Images\\file.png";
- public const string DeleteFileFolderImagePath = "Images\\deletefilefolder.png";
- public const string CopyImagePath = "Images\\copy.png";
-
- private string DefaultFolderSubtitleString = "Ctrl + Enter to open the directory";
-
- private static List _driverNames;
- private PluginInitContext _context;
-
- private readonly Settings _settings;
- private readonly PluginJsonStorage _storage;
- private IContextMenu _contextMenuLoader;
-
- private static Dictionary _envStringPaths;
-
- public Main()
- {
- _storage = new PluginJsonStorage();
- _settings = _storage.Load();
- }
-
- public void Save()
- {
- _storage.Save();
- }
-
- public Control CreateSettingPanel()
- {
- return new FolderPluginSettings(_context.API, _settings);
- }
-
- public void Init(PluginInitContext context)
- {
- _context = context;
- _contextMenuLoader = new ContextMenuLoader(context);
- InitialDriverList();
- LoadEnvironmentStringPaths();
- }
-
- public List Query(Query query)
- {
- var results = GetUserFolderResults(query);
-
- string search = query.Search.ToLower();
- if (!IsDriveOrSharedFolder(search) && !IsEnvironmentVariableSearch(search))
- {
- return results;
- }
-
- if (IsEnvironmentVariableSearch(search))
- {
- results.AddRange(GetEnvironmentStringPathResults(search, query));
- }
- else
- {
- results.AddRange(QueryInternal_Directory_Exists(query.Search, query));
- }
-
- // todo why was this hack here?
- foreach (var result in results)
- {
- result.Score += 10;
- }
-
- return results;
- }
-
- private static bool IsEnvironmentVariableSearch(string search)
- {
- return _envStringPaths != null && search.StartsWith("%");
- }
-
- private static bool IsDriveOrSharedFolder(string search)
- {
- if (search.StartsWith(@"\\"))
- { // shared folder
- return true;
- }
-
- if (_driverNames != null && _driverNames.Any(search.StartsWith))
- { // normal drive letter
- return true;
- }
-
- if (_driverNames == null && search.Length > 2 && char.IsLetter(search[0]) && search[1] == ':')
- { // when we don't have the drive letters we can try...
- return true; // we don't know so let's give it the possibility
- }
-
- return false;
- }
-
- private Result CreateFolderResult(string title, string subtitle, string path, Query query)
- {
- return new Result
- {
- Title = title,
- IcoPath = path,
- SubTitle = subtitle,
- TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData,
- Action = c =>
- {
- if (c.SpecialKeyState.CtrlPressed)
- {
- try
- {
- FilesFolders.OpenPath(path);
- return true;
- }
- catch (Exception ex)
- {
- MessageBox.Show(ex.Message, "Could not start " + path);
- return false;
- }
- }
-
- string changeTo = path.EndsWith("\\") ? path : path + "\\";
- _context.API.ChangeQuery(string.IsNullOrEmpty(query.ActionKeyword) ?
- changeTo :
- query.ActionKeyword + " " + changeTo);
- return false;
- },
- ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path }
- };
- }
-
- private List GetUserFolderResults(Query query)
- {
- string search = query.Search.ToLower();
- var userFolderLinks = _settings.FolderLinks.Where(
- x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase));
- var results = userFolderLinks.Select(item =>
- CreateFolderResult(item.Nickname, DefaultFolderSubtitleString, item.Path, query)).ToList();
- return results;
- }
-
- private void InitialDriverList()
- {
- if (_driverNames == null)
- {
- _driverNames = new List();
- var allDrives = DriveInfo.GetDrives();
- foreach (DriveInfo driver in allDrives)
- {
- _driverNames.Add(driver.Name.ToLower().TrimEnd('\\'));
- }
- }
- }
-
- private void LoadEnvironmentStringPaths()
- {
- _envStringPaths = new Dictionary();
-
- foreach (DictionaryEntry special in Environment.GetEnvironmentVariables())
- {
- if (Directory.Exists(special.Value.ToString()))
- {
- _envStringPaths.Add(special.Key.ToString().ToLower(), special.Value.ToString());
- }
- }
- }
-
- private static readonly char[] _specialSearchChars = new char[]
- {
- '?', '*', '>'
- };
-
- private List QueryInternal_Directory_Exists(string search, Query query)
- {
- var results = new List();
- var hasSpecial = search.IndexOfAny(_specialSearchChars) >= 0;
- string incompleteName = "";
- if (hasSpecial || !Directory.Exists(search + "\\"))
- {
- // if folder doesn't exist, we want to take the last part and use it afterwards to help the user
- // find the right folder.
- int index = search.LastIndexOf('\\');
- if (index > 0 && index < (search.Length - 1))
- {
- incompleteName = search.Substring(index + 1).ToLower();
- search = search.Substring(0, index + 1);
- if (!Directory.Exists(search))
- {
- return results;
- }
- }
- else
- {
- return results;
- }
- }
- else
- {
- // folder exist, add \ at the end of doesn't exist
- if (!search.EndsWith("\\"))
- {
- search += "\\";
- }
- }
-
- results.Add(CreateOpenCurrentFolderResult(incompleteName, search));
-
- var searchOption = SearchOption.TopDirectoryOnly;
- incompleteName += "*";
-
- // give the ability to search all folder when starting with >
- if (incompleteName.StartsWith(">"))
- {
- searchOption = SearchOption.AllDirectories;
-
- // match everything before and after search term using supported wildcard '*', ie. *searchterm*
- incompleteName = "*" + incompleteName.Substring(1);
- }
-
- var folderList = new List();
- var fileList = new List();
-
- var folderSubtitleString = DefaultFolderSubtitleString;
-
- try
- {
- // search folder and add results
- var directoryInfo = new DirectoryInfo(search);
- var fileSystemInfos = directoryInfo.GetFileSystemInfos(incompleteName, searchOption);
-
- foreach (var fileSystemInfo in fileSystemInfos)
- {
- if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue;
-
- if(fileSystemInfo is DirectoryInfo)
- {
- if (searchOption == SearchOption.AllDirectories)
- folderSubtitleString = fileSystemInfo.FullName;
-
- folderList.Add(CreateFolderResult(fileSystemInfo.Name, folderSubtitleString, fileSystemInfo.FullName, query));
- }
- else
- {
- fileList.Add(CreateFileResult(fileSystemInfo.FullName, query));
- }
- }
- }
- catch (Exception e)
- {
- if (e is UnauthorizedAccessException || e is ArgumentException)
- {
- results.Add(new Result { Title = e.Message, Score = 501 });
-
- return results;
- }
-
- throw;
- }
-
- // Intial 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();
- }
-
- private List GetEnvironmentStringPathSuggestions(string search, Query query)
- {
- var results = new List();
- foreach (var p in _envStringPaths)
- {
- if (p.Key.StartsWith(search))
- {
- results.Add(CreateFolderResult($"%{p.Key}%", p.Value, p.Value, query));
- }
- }
- return results;
- }
-
- private List GetEnvironmentStringPathResults(string envStringSearch, Query query)
- {
- if (envStringSearch == "%")
- { // return all environment string options as path suggestions
- return GetEnvironmentStringPathSuggestions("", query);
- }
-
- var results = new List();
- var search = envStringSearch.Substring(1);
-
- if (search.EndsWith("%") && search.Length > 1)
- { // query starts and ends with a %, find an exact match from env-string paths
- var exactEnvStringPath = search.Substring(0, search.Length-1);
-
- if (_envStringPaths.ContainsKey(exactEnvStringPath))
- {
- var expandedPath = _envStringPaths[exactEnvStringPath];
- results.Add(CreateFolderResult($"%{exactEnvStringPath}%", expandedPath, expandedPath, query));
- }
- }
- else if (search.Contains("%"))
- { // query starts with a % and contains another % somewhere before the end
- var splitSearch = search.Split("%");
- var exactEnvStringPath = splitSearch[0];
-
- // if there are more than 2 % characters in the query, don't bother
- if (splitSearch.Length == 2 && _envStringPaths.ContainsKey(exactEnvStringPath))
- {
- var queryPartToReplace = $"%{exactEnvStringPath}%";
- var expandedPath = _envStringPaths[exactEnvStringPath];
- // replace the %envstring% part of the query with its expanded equivalent
- var updatedSearch = envStringSearch.Replace(queryPartToReplace, expandedPath);
-
- results.AddRange(QueryInternal_Directory_Exists(updatedSearch, query));
- }
- }
- else
- { // query simply starts wtih a %, suggest env-string paths that match the rest of the search
- results.AddRange(GetEnvironmentStringPathSuggestions(search, query));
- }
-
- return results;
- }
-
- private static Result CreateFileResult(string filePath, Query query)
- {
- var result = new Result
- {
- Title = Path.GetFileName(filePath),
- SubTitle = filePath,
- IcoPath = filePath,
- TitleHighlightData = StringMatcher.FuzzySearch(query.Search, Path.GetFileName(filePath)).MatchData,
- Action = c =>
- {
- try
- {
- FilesFolders.OpenPath(filePath);
- }
- catch (Exception ex)
- {
- MessageBox.Show(ex.Message, "Could not start " + filePath);
- }
-
- return true;
- },
- ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath}
- };
- return result;
- }
-
- private static Result CreateOpenCurrentFolderResult(string incompleteName, string search)
- {
- var firstResult = "Open current directory";
- if (incompleteName.Length > 0)
- firstResult = "Open " + search;
-
- var folderName = search.TrimEnd('\\').Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last();
-
- return new Result
- {
- Title = firstResult,
- SubTitle = $"Use > to search files and subfolders within {folderName}, " +
- $"* to search for file extensions in {folderName} or both >* to combine the search",
- IcoPath = search,
- Score = 500,
- Action = c =>
- {
- FilesFolders.OpenPath(search);
- return true;
- }
- };
- }
-
- public string GetTranslatedPluginTitle()
- {
- return _context.API.GetTranslation("flowlauncher_plugin_folder_plugin_name");
- }
-
- public string GetTranslatedPluginDescription()
- {
- return _context.API.GetTranslation("flowlauncher_plugin_folder_plugin_description");
- }
-
- public List LoadContextMenus(Result selectedResult)
- {
- return _contextMenuLoader.LoadContextMenus(selectedResult);
- }
- }
-}
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/Settings.cs b/Plugins/Flow.Launcher.Plugin.Folder/Settings.cs
deleted file mode 100644
index b605dfda4..000000000
--- a/Plugins/Flow.Launcher.Plugin.Folder/Settings.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System.Collections.Generic;
-using Newtonsoft.Json;
-using Flow.Launcher.Infrastructure.Storage;
-
-namespace Flow.Launcher.Plugin.Folder
-{
- public class Settings
- {
- [JsonProperty]
- public List FolderLinks { get; set; } = new List();
- }
-}
diff --git a/Plugins/Flow.Launcher.Plugin.Folder/plugin.json b/Plugins/Flow.Launcher.Plugin.Folder/plugin.json
deleted file mode 100644
index 87a444791..000000000
--- a/Plugins/Flow.Launcher.Plugin.Folder/plugin.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "ID": "B4D3B69656E14D44865C8D818EAE47C4",
- "ActionKeyword": "*",
- "Name": "Folder",
- "Description": "Open favorite folder from Flow Launcher directorily",
- "Author": "qianlifeng",
- "Version": "1.0.0",
- "Language": "csharp",
- "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
- "ExecuteFileName": "Flow.Launcher.Plugin.Folder.dll",
- "IcoPath": "Images\\folder.png"
-}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj
index a29c720f1..48639156e 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj
+++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj
@@ -101,9 +101,5 @@
-
-
-
-
-
+
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.Plugin.PluginManagement.csproj b/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.Plugin.PluginManagement.csproj
index aa180cca4..49451d5ba 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.Plugin.PluginManagement.csproj
+++ b/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.Plugin.PluginManagement.csproj
@@ -103,8 +103,4 @@
-
-
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj
index 6208881d4..331566f90 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj
+++ b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj
@@ -113,8 +113,5 @@
-
-
-
-
+
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj
index b50295ccd..ad1dd079e 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj
@@ -99,8 +99,4 @@
-
-
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj
index 80ed0e56b..b63654b7c 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj
@@ -124,7 +124,6 @@
-
diff --git a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj
index 6bc8154af..75fa52290 100644
--- a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj
+++ b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj
@@ -93,9 +93,5 @@
-
-
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj b/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj
index d159e9bc0..c2449a49e 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj
@@ -151,8 +151,4 @@
-
-
-
-
\ No newline at end of file
diff --git a/Plugins/HelloWorldCSharp/HelloWorldCSharp.csproj b/Plugins/HelloWorldCSharp/HelloWorldCSharp.csproj
index 8f1867db5..2e2ab78b5 100644
--- a/Plugins/HelloWorldCSharp/HelloWorldCSharp.csproj
+++ b/Plugins/HelloWorldCSharp/HelloWorldCSharp.csproj
@@ -51,9 +51,5 @@
PreserveNewest
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index 387e98b6f..53b212284 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,10 @@ Flow Launcher
=============

+[](https://ci.appveyor.com/project/JohnTheGr8/flow-launcher/branch/dev)
[](https://github.com/Flow-Launcher/Flow.Launcher/releases)
[](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest)

-[](https://dev.azure.com/Flow-Launcher/Flow.Launcher/_build/latest?definitionId=1&branchName=master)
Flow Launcher. Dedicated to make your workflow flow more seamlessly. Aimed at being more than an app launcher, it searches, integrates and expands on functionalities. Flow will continue to evolve, designed to be open and built with the community at heart.
@@ -14,7 +14,8 @@ Flow Launcher. Dedicated to make your workflow flow more seamlessly. Aimed at be
## Features

-- Search everything from applications, folders, bookmarks, YouTube, Twitter and more. All from the comfort of your keyboard without ever touching the mouse.
+- Search everything from applications, files, bookmarks, YouTube, Twitter and more. All from the comfort of your keyboard without ever touching the mouse.
+- Support search using environment variable paths
- Run batch and PowerShell commands as Administrator or a different user.
- Support languages from Chinese to Italian and more.
- Support of wide range of plugins.
@@ -51,6 +52,8 @@ We welcome all contributions. If you are unsure of a change you want to make, si
You will find the main goals of flow placed under Projects board, so feel free to contribute on that. If you would like to make small incremental changes, feel free to do so as well.
+Get in touch if you like to join the Flow-Launcher Team and help build this great tool.
+
**Question/Suggestion**
Yes please, submit an issue to let us know.
diff --git a/Scripts/flowlauncher.nuspec b/Scripts/flowlauncher.nuspec
index cfac20b06..6c5deb86b 100644
--- a/Scripts/flowlauncher.nuspec
+++ b/Scripts/flowlauncher.nuspec
@@ -4,13 +4,13 @@
FlowLauncher
Flow Launcher
$version$
- happlebao, Jeremy Wu
+ Flow-Launcher Team
https://github.com/Flow-Launcher/Flow.Launcher
https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher/master/Flow.Launcher/Images/app.png
false
Flow Launcher - a launcher for windows
-
+
diff --git a/Scripts/flowlauncher.plugin.nuspec b/Scripts/flowlauncher.plugin.nuspec
deleted file mode 100644
index 68b60a29f..000000000
--- a/Scripts/flowlauncher.plugin.nuspec
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- Flow.Launcher.Plugin
- $version$
- qianlifeng, Jeremy Wu
- https://github.com/Flow-Launcher/Flow.Launcher/blob/master/LICENSE
- https://github.com/Flow-Launcher/Flow.Launcher
- false
- Reference this library if you want to develop a Flow Launcher plugin
- flowlauncher
-
-
-
-
-
diff --git a/Scripts/post_build.ps1 b/Scripts/post_build.ps1
index 14faf82b1..648b50b15 100644
--- a/Scripts/post_build.ps1
+++ b/Scripts/post_build.ps1
@@ -1,15 +1,15 @@
param(
[string]$config = "Release",
[string]$solution,
- [string]$targetpath
+ [string]$targetpath
)
Write-Host "Config: $config"
function Build-Version {
- if ([string]::IsNullOrEmpty($env:APPVEYOR_BUILD_VERSION)) {
- $v = (Get-Command ${TargetPath}).FileVersionInfo.FileVersion
- } else {
- $v = $env:APPVEYOR_BUILD_VERSION
+ if ([string]::IsNullOrEmpty($env:flowVersion)) {
+ $v = (Get-Command ${TargetPath}).FileVersionInfo.FileVersion
+ } else {
+ $v = $env:flowVersion
}
Write-Host "Build Version: $v"
@@ -35,7 +35,6 @@ function Copy-Resources ($path, $config) {
$project = "$path\Flow.Launcher"
$output = "$path\Output"
$target = "$output\$config"
- Copy-Item -Recurse -Force $project\Themes\* $target\Themes\
Copy-Item -Recurse -Force $project\Images\* $target\Images\
Copy-Item -Recurse -Force $path\Plugins\HelloWorldPython $target\Plugins\HelloWorldPython
Copy-Item -Recurse -Force $path\JsonRPC $target\JsonRPC
@@ -57,28 +56,13 @@ function Validate-Directory ($output) {
New-Item $output -ItemType Directory -Force
}
-function Pack-Nuget ($path, $version, $output) {
- Write-Host "Begin build nuget library"
-
- $spec = "$path\Scripts\flowlauncher.plugin.nuspec"
- Write-Host "nuspec path: $spec"
- Write-Host "Output path: $output"
-
- Nuget pack $spec -Version $version -OutputDirectory $output
-
- Write-Host "End build nuget library"
-}
-
function Zip-Release ($path, $version, $output) {
Write-Host "Begin zip release"
- $input = "$path\Output\Release"
- Write-Host "Input path: $input"
- $file = "$output\Flow.Launcher-$version.zip"
- Write-Host "Filename: $file"
+ $content = "$path\Output\Release\*"
+ $zipFile = "$output\Flow-Launcher-v$version.zip"
- [Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem")
- [System.IO.Compression.ZipFile]::CreateFromDirectory($input, $file)
+ Compress-Archive -Force -Path $content -DestinationPath $zipFile
Write-Host "End zip release"
}
@@ -88,10 +72,12 @@ function Pack-Squirrel-Installer ($path, $version, $output) {
Write-Host "Begin pack squirrel installer"
$spec = "$path\Scripts\flowlauncher.nuspec"
- Write-Host "nuspec path: $spec"
$input = "$path\Output\Release"
+
+ Write-Host "Packing: $spec"
Write-Host "Input path: $input"
- Nuget pack $spec -Version $version -Properties Configuration=Release -BasePath $input -OutputDirectory $output
+ # TODO: can we use dotnet pack here?
+ nuget pack $spec -Version $version -BasePath $input -OutputDirectory $output -Properties Configuration=Release
$nupkg = "$output\FlowLauncher.$version.nupkg"
Write-Host "nupkg path: $nupkg"
@@ -107,7 +93,7 @@ function Pack-Squirrel-Installer ($path, $version, $output) {
Move-Item $temp\* $output -Force
Remove-Item $temp
- $file = "$output\Flow Launcher-$version.exe"
+ $file = "$output\Flow-Launcher-v$version.exe"
Write-Host "Filename: $file"
Move-Item "$output\Setup.exe" $file -Force
@@ -133,7 +119,7 @@ function Main {
if(IsDotNetCoreAppSelfContainedPublishEvent) {
FixPublishLastWriteDateTimeError $p
- }
+ }
Delete-Unused $p $config
$o = "$p\Output\Packages"
@@ -144,7 +130,6 @@ function Main {
$isInCI = $env:APPVEYOR
if ($isInCI) {
- Pack-Nuget $p $v $o
Zip-Release $p $v $o
}
diff --git a/appveyor.yml b/appveyor.yml
index a676731b9..c44372f9b 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,31 +1,34 @@
-version: 1.3.{build}
-image: Visual Studio 2017
-configuration: Release
-platform: Any CPU
+version: '0.9.0.{build}'
+
+init:
+- ps: |
+ $version = new-object System.Version $env:APPVEYOR_BUILD_VERSION
+ $env:flowVersion = "{0}.{1}.{2}" -f $version.Major, $version.Minor, $version.Build
+- sc config WSearch start= auto # Starts Windows Search service- Needed for running ExplorerTest
+- net start WSearch
+
assembly_info:
patch: true
- file: AssemblyInfo.*
- assembly_version: '{version}'
- assembly_file_version: '{version}'
- assembly_informational_version: '{version}'
+ file: SolutionAssemblyInfo.cs
+ assembly_version: $(flowVersion)
+ assembly_file_version: $(flowVersion)
+ assembly_informational_version: $(flowVersion)
+
+skip_commits:
+ files:
+ - '*.md'
+
+image: Visual Studio 2019
+platform: Any CPU
+configuration: Release
before_build:
- ps: nuget restore
build:
project: Flow.Launcher.sln
-after_test:
+ verbosity: minimal
+
artifacts:
-- path: 'Output\Packages\Flow.Launcher-*.zip'
- name: zipped_binary
-- path: 'Output\Packages\Flow.Launcher.Plugin.*.nupkg'
- name: nuget_package
-- path: 'Output\Packages\Flow.Launcher-*.*'
- name: installer
-- path: 'Output\Packages\RELEASES'
- name: installer
-deploy:
- provider: NuGet
- api_key:
- secure: yybUOFgBuGVpbmOVZxsurC8OpkClzt9dR+/54WpMWcq6b6oyMatciaelRPnXsjRn
- artifact: nuget_package
- on:
- branch: api
\ No newline at end of file
+- path: 'Output\Packages\Flow-Launcher-*.zip'
+ name: Zip
+- path: 'Output\Release\Flow.Launcher.Plugin.*.nupkg'
+ name: Plugin nupkg
\ No newline at end of file