mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge Dev
This commit is contained in:
commit
a918e7d9c9
55 changed files with 846 additions and 481 deletions
|
|
@ -54,4 +54,4 @@ namespace Flow.Launcher.Core.Plugin
|
|||
return referencedPluginPackageDependencyResolver.ResolveAssemblyToPath(assemblyName) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -320,4 +320,4 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,4 +183,4 @@ namespace Flow.Launcher.Core.Plugin
|
|||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ namespace Flow.Launcher.Core
|
|||
UpdateInfo newUpdateInfo;
|
||||
|
||||
if (!silentUpdate)
|
||||
api.ShowMsg("Please wait...", "Checking for new update");
|
||||
api.ShowMsg(api.GetTranslation("pleaseWait"),
|
||||
api.GetTranslation("update_flowlauncher_update_check"));
|
||||
|
||||
using var updateManager = await GitHubUpdateManager(GitHubRepository).ConfigureAwait(false);
|
||||
|
||||
|
|
@ -51,12 +52,13 @@ namespace Flow.Launcher.Core
|
|||
if (newReleaseVersion <= currentVersion)
|
||||
{
|
||||
if (!silentUpdate)
|
||||
MessageBox.Show("You already have the latest Flow Launcher version");
|
||||
MessageBox.Show(api.GetTranslation("update_flowlauncher_already_on_latest"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!silentUpdate)
|
||||
api.ShowMsg("Update found", "Updating...");
|
||||
api.ShowMsg(api.GetTranslation("update_flowlauncher_update_found"),
|
||||
api.GetTranslation("update_flowlauncher_updating"));
|
||||
|
||||
await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false);
|
||||
|
||||
|
|
@ -67,8 +69,9 @@ namespace Flow.Launcher.Core
|
|||
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
|
||||
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination);
|
||||
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination))
|
||||
MessageBox.Show("Flow Launcher was not able to move your user profile data to the new update version. Please manually " +
|
||||
$"move your profile data folder from {DataLocation.PortableDataPath} to {targetDestination}");
|
||||
MessageBox.Show(string.Format(api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
|
||||
DataLocation.PortableDataPath,
|
||||
targetDestination));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -79,7 +82,7 @@ namespace Flow.Launcher.Core
|
|||
|
||||
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
|
||||
|
||||
if (MessageBox.Show(newVersionTips, "New Update", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
if (MessageBox.Show(newVersionTips, api.GetTranslation("update_flowlauncher_new_update"), MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
||||
}
|
||||
|
|
@ -87,7 +90,8 @@ namespace Flow.Launcher.Core
|
|||
catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException)
|
||||
{
|
||||
Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
|
||||
api.ShowMsg("Update Failed", "Check your connection and try updating proxy settings to github-cloud.s3.amazonaws.com.");
|
||||
api.ShowMsg(api.GetTranslation("update_flowlauncher_fail"),
|
||||
api.GetTranslation("update_flowlauncher_check_connection"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
|
||||
var usage = LoadStorageToConcurrentDictionary();
|
||||
|
||||
|
||||
foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon })
|
||||
{
|
||||
ImageSource img = new BitmapImage(new Uri(icon));
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
/// <summary>
|
||||
/// Serialize object using json format.
|
||||
/// </summary>
|
||||
public class JsonStrorage<T>
|
||||
public class JsonStrorage<T> where T : new()
|
||||
{
|
||||
private readonly JsonSerializerOptions _serializerSettings;
|
||||
private T _data;
|
||||
|
|
@ -76,7 +76,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
BackupOriginFile();
|
||||
}
|
||||
|
||||
_data = JsonSerializer.Deserialize<T>("{}", _serializerSettings);
|
||||
_data = new T();
|
||||
Save();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.3.1</Version>
|
||||
<PackageVersion>1.3.1</PackageVersion>
|
||||
<AssemblyVersion>1.3.1</AssemblyVersion>
|
||||
<FileVersion>1.3.1</FileVersion>
|
||||
<Version>1.4.0</Version>
|
||||
<PackageVersion>1.4.0</PackageVersion>
|
||||
<AssemblyVersion>1.4.0</AssemblyVersion>
|
||||
<FileVersion>1.4.0</FileVersion>
|
||||
<PackageId>Flow.Launcher.Plugin</PackageId>
|
||||
<Authors>Flow-Launcher</Authors>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
|
@ -64,4 +64,4 @@
|
|||
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,28 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Asynchronous Plugin Model for Flow Launcher
|
||||
/// </summary>
|
||||
public interface IAsyncPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Asynchronous Querying
|
||||
/// </summary>
|
||||
/// <para>
|
||||
/// If the Querying or Init method requires high IO transmission
|
||||
/// or performing CPU intense jobs (performing better with cancellation), please use this IAsyncPlugin interface
|
||||
/// </para>
|
||||
/// <param name="query">Query to search</param>
|
||||
/// <param name="token">Cancel when querying job is obsolete</param>
|
||||
/// <returns></returns>
|
||||
Task<List<Result>> QueryAsync(Query query, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize plugin asynchrously (will still wait finish to continue)
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
Task InitAsync(PluginInitContext context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,30 @@
|
|||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Synchronous Plugin Model for Flow Launcher
|
||||
/// <para>
|
||||
/// If the Querying or Init method requires high IO transmission
|
||||
/// or performaing CPU intense jobs (performing better with cancellation), please try the IAsyncPlugin interface
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Querying when user's search changes
|
||||
/// <para>
|
||||
/// This method will be called within a Task.Run,
|
||||
/// so please avoid synchrously wait for long.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="query">Query to search</param>
|
||||
/// <returns></returns>
|
||||
List<Result> Query(Query query);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialize plugin
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
void Init(PluginInitContext context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,19 @@
|
|||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface is to indicate and allow plugins to asyncronously reload their
|
||||
/// in memory data cache or other mediums when user makes a new change
|
||||
/// that is not immediately captured. For example, for BrowserBookmark and Program
|
||||
/// plugin does not automatically detect when a user added a new bookmark or program,
|
||||
/// so this interface's function is exposed to allow user manually do the reloading after
|
||||
/// those new additions.
|
||||
///
|
||||
/// The command that allows user to manual reload is exposed via Plugin.Sys, and
|
||||
/// it will call the plugins that have implemented this interface.
|
||||
/// </summary>
|
||||
public interface IAsyncReloadable
|
||||
{
|
||||
Task ReloadDataAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface is to indicate and allow plugins to reload their
|
||||
/// This interface is to indicate and allow plugins to synchronously reload their
|
||||
/// in memory data cache or other mediums when user makes a new change
|
||||
/// that is not immediately captured. For example, for BrowserBookmark and Program
|
||||
/// plugin does not automatically detect when a user added a new bookmark or program,
|
||||
|
|
@ -10,6 +10,10 @@
|
|||
///
|
||||
/// The command that allows user to manual reload is exposed via Plugin.Sys, and
|
||||
/// it will call the plugins that have implemented this interface.
|
||||
///
|
||||
/// <para>
|
||||
/// If requiring reloading data asynchronously, please use the IAsyncReloadable interface
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface IReloadable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -146,31 +146,23 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
/// This checks whether a given string is a directory path or network location string.
|
||||
/// It does not check if location actually exists.
|
||||
///</summary>
|
||||
public static bool IsLocationPathString(string querySearchString)
|
||||
public static bool IsLocationPathString(this string querySearchString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(querySearchString))
|
||||
if (string.IsNullOrEmpty(querySearchString) || querySearchString.Length < 3)
|
||||
return false;
|
||||
|
||||
// // shared folder location, and not \\\location\
|
||||
if (querySearchString.Length >= 3
|
||||
&& querySearchString.StartsWith(@"\\")
|
||||
&& char.IsLetter(querySearchString[2]))
|
||||
if (querySearchString.StartsWith(@"\\")
|
||||
&& querySearchString[2] != '\\')
|
||||
return true;
|
||||
|
||||
// c:\
|
||||
if (querySearchString.Length == 3
|
||||
&& char.IsLetter(querySearchString[0])
|
||||
if (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 querySearchString.Length == 3 || querySearchString[3] != '\\';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ namespace Flow.Launcher.Test
|
|||
Enum.GetValues(typeof(StringMatcher.SearchPrecisionScore))
|
||||
.Cast<StringMatcher.SearchPrecisionScore>()
|
||||
.ToList()
|
||||
.ForEach(x => listToReturn.Add((int)x));
|
||||
.ForEach(x => listToReturn.Add((int) x));
|
||||
|
||||
return listToReturn;
|
||||
}
|
||||
|
|
@ -92,7 +92,8 @@ namespace Flow.Launcher.Test
|
|||
[TestCase("cand")]
|
||||
[TestCase("cpywa")]
|
||||
[TestCase("ccs")]
|
||||
public void GivenQueryString_WhenAppliedPrecisionFiltering_ThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm)
|
||||
public void GivenQueryString_WhenAppliedPrecisionFiltering_ThenShouldReturnGreaterThanPrecisionScoreResults(
|
||||
string searchTerm)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
var matcher = new StringMatcher();
|
||||
|
|
@ -108,9 +109,9 @@ namespace Flow.Launcher.Test
|
|||
foreach (var precisionScore in GetPrecisionScores())
|
||||
{
|
||||
var filteredResult = results.Where(result => result.Score >= precisionScore)
|
||||
.Select(result => result)
|
||||
.OrderByDescending(x => x.Score)
|
||||
.ToList();
|
||||
.Select(result => result)
|
||||
.OrderByDescending(x => x.Score)
|
||||
.ToList();
|
||||
|
||||
Debug.WriteLine("");
|
||||
Debug.WriteLine("###############################################");
|
||||
|
|
@ -119,6 +120,7 @@ namespace Flow.Launcher.Test
|
|||
{
|
||||
Debug.WriteLine("SCORE: " + item.Score.ToString() + ", FoundString: " + item.Title);
|
||||
}
|
||||
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine("");
|
||||
|
||||
|
|
@ -128,11 +130,11 @@ namespace Flow.Launcher.Test
|
|||
|
||||
[TestCase(Chrome, Chrome, 157)]
|
||||
[TestCase(Chrome, LastIsChrome, 147)]
|
||||
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 25)]
|
||||
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 90)]
|
||||
[TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 21)]
|
||||
[TestCase(Chrome, CandyCrushSagaFromKing, 0)]
|
||||
[TestCase("sql", MicrosoftSqlServerManagementStudio, 110)]
|
||||
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, 121)]//double spacing intended
|
||||
[TestCase("sql", MicrosoftSqlServerManagementStudio, 90)]
|
||||
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, 121)] //double spacing intended
|
||||
public void WhenGivenQueryString_ThenShouldReturn_TheDesiredScoring(
|
||||
string queryString, string compareString, int expectedScore)
|
||||
{
|
||||
|
|
@ -141,20 +143,20 @@ namespace Flow.Launcher.Test
|
|||
var rawScore = matcher.FuzzyMatch(queryString, compareString).RawScore;
|
||||
|
||||
// Should
|
||||
Assert.AreEqual(expectedScore, rawScore,
|
||||
Assert.AreEqual(expectedScore, rawScore,
|
||||
$"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}");
|
||||
}
|
||||
|
||||
[TestCase("goo", "Google Chrome", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("chr", "Google Chrome", StringMatcher.SearchPrecisionScore.Low, true)]
|
||||
[TestCase("chr", "Chrome", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("chr", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("chr", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Low, true)]
|
||||
[TestCase("chr", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("chr", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.None, true)]
|
||||
[TestCase("ccs", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Low, true)]
|
||||
[TestCase("cand", "Candy Crush Saga from King",StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("cand", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("ccs", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("cand", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("cand", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular,
|
||||
false)]
|
||||
public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual(
|
||||
string queryString,
|
||||
string compareString,
|
||||
|
|
@ -170,7 +172,8 @@ namespace Flow.Launcher.Test
|
|||
Debug.WriteLine("");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
|
||||
Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})");
|
||||
Debug.WriteLine(
|
||||
$"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int) expectedPrecisionScore})");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine("");
|
||||
|
||||
|
|
@ -179,13 +182,15 @@ namespace Flow.Launcher.Test
|
|||
$"Query:{queryString}{Environment.NewLine} " +
|
||||
$"Compare:{compareString}{Environment.NewLine}" +
|
||||
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
|
||||
$"Precision Score: {(int)expectedPrecisionScore}");
|
||||
$"Precision Score: {(int) expectedPrecisionScore}");
|
||||
}
|
||||
|
||||
[TestCase("exce", "OverLeaf-Latex: An online LaTeX editor", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("term", "Windows Terminal (Preview)", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql s managa", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("sql' s manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("sql s managa", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular,
|
||||
false)]
|
||||
[TestCase("sql' s manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular,
|
||||
false)]
|
||||
[TestCase("sql s manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
|
|
@ -195,15 +200,21 @@ namespace Flow.Launcher.Test
|
|||
[TestCase("sql serv man", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql studio", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("mic", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("mssms", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("msms", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("chr", "Shutdown", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("mssms", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).",
|
||||
StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).",
|
||||
StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("a test", "This is a test", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("test", "This is a test", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("cod", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("code", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("codes", "Visual Studio Codes", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("vsc", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("vs", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("vc", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
public void WhenGivenQuery_ShouldReturnResults_ContainingAllQuerySubstrings(
|
||||
string queryString,
|
||||
string compareString,
|
||||
|
|
@ -211,7 +222,7 @@ namespace Flow.Launcher.Test
|
|||
bool expectedPrecisionResult)
|
||||
{
|
||||
// When
|
||||
var matcher = new StringMatcher { UserSettingSearchPrecision = expectedPrecisionScore };
|
||||
var matcher = new StringMatcher {UserSettingSearchPrecision = expectedPrecisionScore};
|
||||
|
||||
// Given
|
||||
var matchResult = matcher.FuzzyMatch(queryString, compareString);
|
||||
|
|
@ -219,7 +230,8 @@ namespace Flow.Launcher.Test
|
|||
Debug.WriteLine("");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
|
||||
Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})");
|
||||
Debug.WriteLine(
|
||||
$"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int) expectedPrecisionScore})");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine("");
|
||||
|
||||
|
|
@ -228,7 +240,7 @@ namespace Flow.Launcher.Test
|
|||
$"Query:{queryString}{Environment.NewLine} " +
|
||||
$"Compare:{compareString}{Environment.NewLine}" +
|
||||
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
|
||||
$"Precision Score: {(int)expectedPrecisionScore}");
|
||||
$"Precision Score: {(int) expectedPrecisionScore}");
|
||||
}
|
||||
|
||||
[TestCase("man", "Task Manager", "eManual")]
|
||||
|
|
@ -238,7 +250,7 @@ namespace Flow.Launcher.Test
|
|||
string queryString, string compareString1, string compareString2)
|
||||
{
|
||||
// When
|
||||
var matcher = new StringMatcher { UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular };
|
||||
var matcher = new StringMatcher {UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular};
|
||||
|
||||
// Given
|
||||
var compareString1Result = matcher.FuzzyMatch(queryString, compareString1);
|
||||
|
|
@ -247,8 +259,10 @@ namespace Flow.Launcher.Test
|
|||
Debug.WriteLine("");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine($"QueryString: \"{queryString}\"{Environment.NewLine}");
|
||||
Debug.WriteLine($"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}");
|
||||
Debug.WriteLine($"CompareString2: \"{compareString2}\", Score: {compareString2Result.Score}{Environment.NewLine}");
|
||||
Debug.WriteLine(
|
||||
$"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}");
|
||||
Debug.WriteLine(
|
||||
$"CompareString2: \"{compareString2}\", Score: {compareString2Result.Score}{Environment.NewLine}");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine("");
|
||||
|
||||
|
|
@ -256,13 +270,13 @@ namespace Flow.Launcher.Test
|
|||
Assert.True(compareString1Result.Score > compareString2Result.Score,
|
||||
$"Query: \"{queryString}\"{Environment.NewLine} " +
|
||||
$"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" +
|
||||
$"Should be greater than{ Environment.NewLine}" +
|
||||
$"Should be greater than{Environment.NewLine}" +
|
||||
$"CompareString2: \"{compareString2}\", Score: {compareString1Result.Score}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
[TestCase("vim", "Vim", "ignoreDescription", "ignore.exe", "Vim Diff", "ignoreDescription", "ignore.exe")]
|
||||
public void WhenMultipleResults_ExactMatchingResult_ShouldHaveGreatestScore(
|
||||
string queryString, string firstName, string firstDescription, string firstExecutableName,
|
||||
string queryString, string firstName, string firstDescription, string firstExecutableName,
|
||||
string secondName, string secondDescription, string secondExecutableName)
|
||||
{
|
||||
// Act
|
||||
|
|
@ -275,15 +289,36 @@ namespace Flow.Launcher.Test
|
|||
var secondDescriptionMatch = matcher.FuzzyMatch(queryString, secondDescription).RawScore;
|
||||
var secondExecutableNameMatch = matcher.FuzzyMatch(queryString, secondExecutableName).RawScore;
|
||||
|
||||
var firstScore = new[] { firstNameMatch, firstDescriptionMatch, firstExecutableNameMatch }.Max();
|
||||
var secondScore = new[] { secondNameMatch, secondDescriptionMatch, secondExecutableNameMatch }.Max();
|
||||
var firstScore = new[] {firstNameMatch, firstDescriptionMatch, firstExecutableNameMatch}.Max();
|
||||
var secondScore = new[] {secondNameMatch, secondDescriptionMatch, secondExecutableNameMatch}.Max();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(firstScore > secondScore,
|
||||
$"Query: \"{queryString}\"{Environment.NewLine} " +
|
||||
$"Name of first: \"{firstName}\", Final Score: {firstScore}{Environment.NewLine}" +
|
||||
$"Should be greater than{ Environment.NewLine}" +
|
||||
$"Should be greater than{Environment.NewLine}" +
|
||||
$"Name of second: \"{secondName}\", Final Score: {secondScore}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
[TestCase("vsc","Visual Studio Code", 100)]
|
||||
[TestCase("jbr","JetBrain Rider",100)]
|
||||
[TestCase("jr","JetBrain Rider",90)]
|
||||
[TestCase("vs","Visual Studio",100)]
|
||||
[TestCase("vs","Visual Studio Preview",100)]
|
||||
[TestCase("vsp","Visual Studio Preview",100)]
|
||||
[TestCase("vsp","Visual Studio",0)]
|
||||
[TestCase("pc","Postman Canary",100)]
|
||||
|
||||
public void WhenGivenAnAcronymQuery_ShouldReturnAcronymScore(string queryString, string compareString,
|
||||
int desiredScore)
|
||||
{
|
||||
var matcher = new StringMatcher();
|
||||
var score = matcher.FuzzyMatch(queryString, compareString).Score;
|
||||
Assert.IsTrue(score == desiredScore,
|
||||
$@"Query: ""{queryString}""
|
||||
CompareString: ""{compareString}""
|
||||
Score: {score}
|
||||
Desired Score: {desiredScore}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
return new List<Result>();
|
||||
}
|
||||
|
||||
private List<Result> MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString)
|
||||
private List<Result> MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString, CancellationToken token)
|
||||
{
|
||||
return new List<Result>
|
||||
{
|
||||
|
|
@ -60,8 +60,8 @@ namespace Flow.Launcher.Test.Plugins
|
|||
$"Actual: {result}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
[TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\'")]
|
||||
[TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\'")]
|
||||
[TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\' ORDER BY System.FileName")]
|
||||
[TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\' ORDER BY System.FileName")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString)
|
||||
{
|
||||
// Given
|
||||
|
|
@ -79,7 +79,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
[TestCase("C:\\SomeFolder\\flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " +
|
||||
"FROM SystemIndex WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
|
||||
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033))" +
|
||||
" AND directory='file:C:\\SomeFolder'")]
|
||||
" AND directory='file:C:\\SomeFolder' ORDER BY System.FileName")]
|
||||
public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString(
|
||||
string userSearchString, string expectedString)
|
||||
{
|
||||
|
|
@ -116,11 +116,8 @@ namespace Flow.Launcher.Test.Plugins
|
|||
[TestCase("scope='file:'")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString)
|
||||
{
|
||||
// Given
|
||||
var queryConstructor = new QueryConstructor(new Settings());
|
||||
|
||||
//When
|
||||
var resultString = queryConstructor.QueryWhereRestrictionsForAllFilesAndFoldersSearch();
|
||||
var resultString = QueryConstructor.QueryWhereRestrictionsForAllFilesAndFoldersSearch;
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(resultString == expectedString,
|
||||
|
|
@ -130,7 +127,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
|
||||
[TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" " +
|
||||
"FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
|
||||
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:'")]
|
||||
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY System.FileName")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString(
|
||||
string userSearchString, string expectedString)
|
||||
{
|
||||
|
|
@ -205,7 +202,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
}
|
||||
|
||||
[TestCase("some words", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " +
|
||||
"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:'")]
|
||||
"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY System.FileName")]
|
||||
public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString(
|
||||
string userSearchString, string expectedString)
|
||||
{
|
||||
|
|
@ -243,6 +240,9 @@ namespace Flow.Launcher.Test.Plugins
|
|||
[TestCase(@"cc:\", false)]
|
||||
[TestCase(@"\\\SomeNetworkLocation\", false)]
|
||||
[TestCase("RandomFile", false)]
|
||||
[TestCase(@"c:\>*", true)]
|
||||
[TestCase(@"c:\>", true)]
|
||||
[TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)]
|
||||
public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult)
|
||||
{
|
||||
// When, Given
|
||||
|
|
@ -295,9 +295,9 @@ namespace Flow.Launcher.Test.Plugins
|
|||
}
|
||||
|
||||
[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'")]
|
||||
[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
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
Icon="Images\app.png"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Title="Custom Plugin Hotkey" Height="200" Width="674.766">
|
||||
Title="{DynamicResource customeQueryHotkeyTitle}" Height="200" Width="674.766">
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Key="Escape" Command="Close"/>
|
||||
</Window.InputBindings>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
<!--Setting General-->
|
||||
<system:String x:Key="flowlauncher_settings">Flow Launcher Settings</system:String>
|
||||
<system:String x:Key="general">General</system:String>
|
||||
<system:String x:Key="portableMode">Portable Mode</system:String>
|
||||
<system:String x:Key="startFlowLauncherOnSystemStartup">Start Flow Launcher on system startup</system:String>
|
||||
<system:String x:Key="hideFlowLauncherWhenLoseFocus">Hide Flow Launcher when focus is lost</system:String>
|
||||
<system:String x:Key="dontPromptUpdateMsg">Do not show new version notifications</system:String>
|
||||
|
|
@ -39,12 +40,13 @@
|
|||
<!--Setting Plugin-->
|
||||
<system:String x:Key="plugin">Plugin</system:String>
|
||||
<system:String x:Key="browserMorePlugins">Find more plugins</system:String>
|
||||
<system:String x:Key="enable">Enable</system:String>
|
||||
<system:String x:Key="disable">Disable</system:String>
|
||||
<system:String x:Key="actionKeywords">Action keyword:</system:String>
|
||||
<system:String x:Key="currentActionKeywords">Current action keyword:</system:String>
|
||||
<system:String x:Key="newActionKeyword">New action keyword:</system:String>
|
||||
<system:String x:Key="currentPriority">Current Priority: </system:String>
|
||||
<system:String x:Key="newPriority">New Priority: </system:String>
|
||||
<system:String x:Key="currentPriority">Current Priority:</system:String>
|
||||
<system:String x:Key="newPriority">New Priority:</system:String>
|
||||
<system:String x:Key="pluginDirectory">Plugin Directory</system:String>
|
||||
<system:String x:Key="author">Author</system:String>
|
||||
<system:String x:Key="plugin_init_time">Init time:</system:String>
|
||||
|
|
@ -109,7 +111,7 @@
|
|||
<!--Priority Setting Dialog-->
|
||||
<system:String x:Key="priority_tips">Greater the number, the higher the result will be ranked. Try setting it as 5. If you want the results to be lower than any other plugin's, provide a negative number</system:String>
|
||||
<system:String x:Key="invalidPriority">Please provide an valid integer for Priority!</system:String>
|
||||
|
||||
|
||||
<!--Action Keyword Setting Dialog-->
|
||||
<system:String x:Key="oldActionKeywords">Old Action Keyword</system:String>
|
||||
<system:String x:Key="newActionKeywords">New Action Keyword</system:String>
|
||||
|
|
@ -122,6 +124,7 @@
|
|||
<system:String x:Key="actionkeyword_tips">Use * if you don't want to specify an action keyword</system:String>
|
||||
|
||||
<!--Custom Query Hotkey Dialog-->
|
||||
<system:String x:Key="customeQueryHotkeyTitle">Custom Plugin Hotkey</system:String>
|
||||
<system:String x:Key="preview">Preview</system:String>
|
||||
<system:String x:Key="hotkeyIsNotUnavailable">Hotkey is unavailable, please select a new hotkey</system:String>
|
||||
<system:String x:Key="invalidPluginHotkey">Invalid plugin hotkey</system:String>
|
||||
|
|
@ -146,11 +149,23 @@
|
|||
<system:String x:Key="reportWindow_report_failed">Failed to send report</system:String>
|
||||
<system:String x:Key="reportWindow_flowlauncher_got_an_error">Flow Launcher got an error</system:String>
|
||||
|
||||
<!--General Notice-->
|
||||
<system:String x:Key="pleaseWait">Please wait...</system:String>
|
||||
|
||||
<!--update-->
|
||||
<system:String x:Key="update_flowlauncher_update_check">Checking for new update</system:String>
|
||||
<system:String x:Key="update_flowlauncher_already_on_latest">You already have the latest Flow Launcher version</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_found">Update found</system:String>
|
||||
<system:String x:Key="update_flowlauncher_updating">Updating...</system:String>
|
||||
<system:String x:Key="update_flowlauncher_fail_moving_portable_user_profile_data">Flow Launcher was not able to move your user profile data to the new update version.
|
||||
Please manually move your profile data folder from {0} to {1}</system:String>
|
||||
<system:String x:Key="update_flowlauncher_new_update">New Update</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_new_version_available">New Flow Launcher release {0} is now available</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_error">An error occurred while trying to install software updates</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update">Update</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_cancel">Cancel</system:String>
|
||||
<system:String x:Key="update_flowlauncher_fail">Update Failed</system:String>
|
||||
<system:String x:Key="update_flowlauncher_check_connection">Check your connection and try updating proxy settings to github-cloud.s3.amazonaws.com.</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_restart_flowlauncher_tip">This upgrade will restart Flow Launcher</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_upadte_files">Following files will be updated</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_files">Update files</system:String>
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@
|
|||
Background="Transparent"/>
|
||||
</Grid>
|
||||
<Line x:Name="ProgressBar" HorizontalAlignment="Right"
|
||||
Style="{DynamicResource PendingLineStyle}" Visibility="{Binding ProgressBarVisibility, Mode=TwoWay}"
|
||||
Style="{DynamicResource PendingLineStyle}" Visibility="{Binding ProgressBarVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Y1="0" Y2="0" X2="100" Height="2" Width="752" StrokeThickness="1">
|
||||
</Line>
|
||||
<ContentControl>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace Flow.Launcher
|
|||
#region Private Fields
|
||||
|
||||
private readonly Storyboard _progressBarStoryboard = new Storyboard();
|
||||
private bool isProgressBarStoryboardPaused;
|
||||
private Settings _settings;
|
||||
private NotifyIcon _notifyIcon;
|
||||
private MainViewModel _viewModel;
|
||||
|
|
@ -52,7 +53,7 @@ namespace Flow.Launcher
|
|||
|
||||
private void OnInitialized(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs _)
|
||||
|
|
@ -73,7 +74,7 @@ namespace Flow.Launcher
|
|||
{
|
||||
if (e.PropertyName == nameof(MainViewModel.MainWindowVisibility))
|
||||
{
|
||||
if (Visibility == Visibility.Visible)
|
||||
if (_viewModel.MainWindowVisibility == Visibility.Visible)
|
||||
{
|
||||
Activate();
|
||||
QueryTextBox.Focus();
|
||||
|
|
@ -84,7 +85,34 @@ namespace Flow.Launcher
|
|||
QueryTextBox.SelectAll();
|
||||
_viewModel.LastQuerySelected = true;
|
||||
}
|
||||
|
||||
if (_viewModel.ProgressBarVisibility == Visibility.Visible && isProgressBarStoryboardPaused)
|
||||
{
|
||||
_progressBarStoryboard.Resume();
|
||||
isProgressBarStoryboardPaused = false;
|
||||
}
|
||||
}
|
||||
else if (!isProgressBarStoryboardPaused)
|
||||
{
|
||||
_progressBarStoryboard.Pause();
|
||||
isProgressBarStoryboardPaused = true;
|
||||
}
|
||||
}
|
||||
else if (e.PropertyName == nameof(MainViewModel.ProgressBarVisibility))
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (_viewModel.ProgressBarVisibility == Visibility.Hidden && !isProgressBarStoryboardPaused)
|
||||
{
|
||||
_progressBarStoryboard.Pause();
|
||||
isProgressBarStoryboardPaused = true;
|
||||
}
|
||||
else if (_viewModel.MainWindowVisibility == Visibility.Visible && isProgressBarStoryboardPaused)
|
||||
{
|
||||
_progressBarStoryboard.Resume();
|
||||
isProgressBarStoryboardPaused = false;
|
||||
}
|
||||
}, System.Windows.Threading.DispatcherPriority.Render);
|
||||
}
|
||||
};
|
||||
_settings.PropertyChanged += (o, e) =>
|
||||
|
|
@ -170,6 +198,7 @@ namespace Flow.Launcher
|
|||
_progressBarStoryboard.RepeatBehavior = RepeatBehavior.Forever;
|
||||
ProgressBar.BeginStoryboard(_progressBarStoryboard);
|
||||
_viewModel.ProgressBarVisibility = Visibility.Hidden;
|
||||
isProgressBarStoryboardPaused = true;
|
||||
}
|
||||
|
||||
private void OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
<ScrollViewer ui:ScrollViewerHelper.AutoHideScrollBars="True" Margin="60,30,0,30">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<ui:ToggleSwitch Margin="10" IsOn="{Binding PortableMode}">
|
||||
<TextBlock Text="Portable Mode" />
|
||||
<TextBlock Text="{DynamicResource portableMode}" />
|
||||
</ui:ToggleSwitch>
|
||||
<CheckBox Margin="10" IsChecked="{Binding Settings.StartFlowLauncherOnSystemStartup}"
|
||||
Checked="OnAutoStartupChecked" Unchecked="OnAutoStartupUncheck">
|
||||
|
|
@ -166,7 +166,7 @@
|
|||
</TextBlock>
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
<ui:ToggleSwitch Grid.Column="1" OffContent="Disabled" OnContent="Enabled"
|
||||
<ui:ToggleSwitch Grid.Column="1" OffContent="{DynamicResource disable}" OnContent="{DynamicResource enable}"
|
||||
MaxWidth="110" HorizontalAlignment="Right"
|
||||
IsOn="{Binding PluginState}"/>
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Storage
|
||||
|
|
@ -8,20 +9,24 @@ namespace Flow.Launcher.Storage
|
|||
// todo this class is not thread safe.... but used from multiple threads.
|
||||
public class TopMostRecord
|
||||
{
|
||||
private Dictionary<string, Record> records = new Dictionary<string, Record>();
|
||||
/// <summary>
|
||||
/// You should not directly access this field
|
||||
/// <para>
|
||||
/// It is public due to System.Text.Json limitation in version 3.1
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// TODO: Set it to private
|
||||
public Dictionary<string, Record> records { get; set; } = new Dictionary<string, Record>();
|
||||
|
||||
internal bool IsTopMost(Result result)
|
||||
{
|
||||
if (records.Count == 0)
|
||||
if (records.Count == 0 || !records.ContainsKey(result.OriginQuery.RawQuery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// since this dictionary should be very small (or empty) going over it should be pretty fast.
|
||||
return records.Any(o => o.Value.Title == result.Title
|
||||
&& o.Value.SubTitle == result.SubTitle
|
||||
&& o.Value.PluginID == result.PluginID
|
||||
&& o.Key == result.OriginQuery.RawQuery);
|
||||
// since this dictionary should be very small (or empty) going over it should be pretty fast.
|
||||
return records[result.OriginQuery.RawQuery].Equals(result);
|
||||
}
|
||||
|
||||
internal void Remove(Result result)
|
||||
|
|
@ -53,5 +58,12 @@ namespace Flow.Launcher.Storage
|
|||
public string Title { get; set; }
|
||||
public string SubTitle { get; set; }
|
||||
public string PluginID { get; set; }
|
||||
|
||||
public bool Equals(Result r)
|
||||
{
|
||||
return Title == r.Title
|
||||
&& SubTitle == r.SubTitle
|
||||
&& PluginID == r.PluginID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
|
|
@ -6,14 +7,27 @@ namespace Flow.Launcher.Storage
|
|||
{
|
||||
public class UserSelectedRecord
|
||||
{
|
||||
private Dictionary<string, int> records = new Dictionary<string, int>();
|
||||
/// <summary>
|
||||
/// You should not directly access this field
|
||||
/// <para>
|
||||
/// It is public due to System.Text.Json limitation in version 3.1
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// TODO: Set it to private
|
||||
[JsonPropertyName("records")]
|
||||
public Dictionary<string, int> records { get; set; }
|
||||
|
||||
public UserSelectedRecord()
|
||||
{
|
||||
records = new Dictionary<string, int>();
|
||||
}
|
||||
|
||||
public void Add(Result result)
|
||||
{
|
||||
var key = result.ToString();
|
||||
if (records.TryGetValue(key, out int value))
|
||||
if (records.ContainsKey(key))
|
||||
{
|
||||
records[key] = value + 1;
|
||||
records[key]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
63
Flow.Launcher/Themes/BlurBlack Darker.xaml
Normal file
63
Flow.Launcher/Themes/BlurBlack Darker.xaml
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<system:Boolean x:Key="ThemeBlurEnabled">True</system:Boolean>
|
||||
|
||||
<Style x:Key="QueryBoxStyle" BasedOn="{StaticResource BaseQueryBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Foreground" Value="#FFFFFFFF" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Foreground" Value="LightGray" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="WindowBorderStyle" BasedOn="{StaticResource BaseWindowBorderStyle}" TargetType="{x:Type Border}">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="Black" Opacity="0.6"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="WindowStyle" BasedOn="{StaticResource BaseWindowStyle}" TargetType="{x:Type Window}">
|
||||
<Setter Property="Width" Value="750" /> <!-- This is used to set the blur width only -->
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="Black" Opacity="0.6"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PendingLineStyle" BasedOn="{StaticResource BasePendingLineStyle}" TargetType="{x:Type Line}">
|
||||
</Style>
|
||||
|
||||
<!-- Item Style -->
|
||||
<Style x:Key="ItemTitleStyle" BasedOn="{StaticResource BaseItemTitleStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Margin" Value="0, -10"/>
|
||||
<Setter Property="Foreground" Value="#FFFFFFFF"/>
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleStyle" BasedOn="{StaticResource BaseItemSubTitleStyle}" TargetType="{x:Type TextBlock}" >
|
||||
<Setter Property="Foreground" Value="#FFFFFFFF"/>
|
||||
</Style>
|
||||
<Style x:Key="ItemTitleSelectedStyle" BasedOn="{StaticResource BaseItemTitleSelectedStyle}" TargetType="{x:Type TextBlock}" >
|
||||
<Setter Property="Margin" Value="0, -10"/>
|
||||
<Setter Property="Foreground" Value="#FFFFFFFF"/>
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleSelectedStyle" BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}" TargetType="{x:Type TextBlock}" >
|
||||
<Setter Property="Foreground" Value="#FFFFFFFF"/>
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#356ef3</SolidColorBrush>
|
||||
|
||||
<!-- button style in the middle of the scrollbar -->
|
||||
<Style x:Key="ThumbStyle" BasedOn="{StaticResource BaseThumbStyle}" TargetType="{x:Type Thumb}">
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ScrollBarStyle" BasedOn="{StaticResource BaseScrollBarStyle}" TargetType="{x:Type ScrollBar}">
|
||||
<Setter Property="Background" Value="#a0a0a0"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
<Style x:Key="WindowBorderStyle" BasedOn="{StaticResource BaseWindowBorderStyle}" TargetType="{x:Type Border}">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="White" Opacity="0.1"/>
|
||||
<SolidColorBrush Color="White" Opacity="0.5"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
<Setter Property="Width" Value="750" /> <!-- This is used to set the blur width only -->
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="White" Opacity="0.1"/>
|
||||
<SolidColorBrush Color="White" Opacity="0.5"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
|
|
|||
|
|
@ -4,21 +4,19 @@
|
|||
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style x:Key="QueryBoxStyle" BasedOn="{StaticResource BaseQueryBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#EDEDED" />
|
||||
<Setter Property="Background" Value="LightGray" />
|
||||
<Setter Property="Foreground" Value="#222222" />
|
||||
<Setter Property="FontSize" Value="38" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#EDEDED" />
|
||||
<Setter Property="Foreground" Value="#222222" />
|
||||
<Setter Property="FontSize" Value="38" />
|
||||
<Setter Property="Background" Value="LightGray" />
|
||||
<Setter Property="Foreground" Value="#6E6E6E" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="WindowBorderStyle" BasedOn="{StaticResource BaseWindowBorderStyle}" TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderBrush" Value="#B0B0B0" />
|
||||
<Setter Property="BorderBrush" Value="LightGray" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Background" Value="#EDEDED"></Setter>
|
||||
<Setter Property="Background" Value="LightGray"></Setter>
|
||||
</Style>
|
||||
<Style x:Key="WindowStyle" TargetType="{x:Type Window}" BasedOn="{StaticResource BaseWindowStyle}" >
|
||||
</Style>
|
||||
|
|
@ -31,15 +29,15 @@
|
|||
<Setter Property="Foreground" Value="#A6A6A6" />
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleStyle" BasedOn="{StaticResource BaseItemSubTitleStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#A6A6A6" />
|
||||
<Setter Property="Foreground" Value="#6B6B6B" />
|
||||
</Style>
|
||||
<Style x:Key="ItemTitleSelectedStyle" BasedOn="{StaticResource BaseItemTitleSelectedStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#FFFFF8" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleSelectedStyle" BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#ffffff" />
|
||||
<Setter Property="Foreground" Value="#EDEDED" />
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#00AAF6</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#787878</SolidColorBrush>
|
||||
<Style x:Key="ThumbStyle" BasedOn="{StaticResource BaseThumbStyle}" TargetType="{x:Type Thumb}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
|
|
|
|||
|
|
@ -6,19 +6,19 @@
|
|||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Style x:Key="QueryBoxStyle" BasedOn="{StaticResource BaseQueryBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#EBEBEB"/>
|
||||
<Setter Property="Background" Value="White "/>
|
||||
<Setter Property="Foreground" Value="#000000" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#EBEBEB"/>
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="Foreground" Value="#000000" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="WindowBorderStyle" BasedOn="{StaticResource BaseWindowBorderStyle}" TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderBrush" Value="#AAAAAA" />
|
||||
<Setter Property="BorderThickness" Value="5" />
|
||||
<Setter Property="Background" Value="#ffffff"></Setter>
|
||||
<Setter Property="BorderBrush" Value="LightGray" />
|
||||
<Setter Property="BorderThickness" Value="1"></Setter>
|
||||
<Setter Property="Background" Value="White"></Setter>
|
||||
</Style>
|
||||
<Style x:Key="WindowStyle" TargetType="{x:Type Window}" BasedOn="{StaticResource BaseWindowStyle}" >
|
||||
</Style>
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
<Style x:Key="ItemSubTitleSelectedStyle" BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#F6F6FF" />
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#3875D7</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#909090</SolidColorBrush>
|
||||
|
||||
<!-- button style in the middle of the scrollbar -->
|
||||
<Style x:Key="ThumbStyle" BasedOn="{StaticResource BaseThumbStyle}" TargetType="{x:Type Thumb}">
|
||||
|
|
|
|||
56
Flow.Launcher/Themes/Nord Darker.xaml
Normal file
56
Flow.Launcher/Themes/Nord Darker.xaml
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style x:Key="QueryBoxStyle" BasedOn="{StaticResource BaseQueryBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#2e3440" />
|
||||
<Setter Property="Foreground" Value="#eceff4" />
|
||||
<Setter Property="FontSize" Value="30" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#2e3440" />
|
||||
<Setter Property="Foreground" Value="#eceff4" />
|
||||
<Setter Property="FontSize" Value="30" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="WindowBorderStyle" BasedOn="{StaticResource BaseWindowBorderStyle}" TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderBrush" Value="#4c566a" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Background" Value="#2e3440"></Setter>
|
||||
</Style>
|
||||
<Style x:Key="WindowStyle" TargetType="{x:Type Window}" BasedOn="{StaticResource BaseWindowStyle}" >
|
||||
</Style>
|
||||
<Style x:Key="PendingLineStyle" BasedOn="{StaticResource BasePendingLineStyle}" TargetType="{x:Type Line}" />
|
||||
<Style x:Key="ItemTitleStyle" BasedOn="{StaticResource BaseItemTitleStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#e5e9f0" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
</Style>
|
||||
<Style x:Key="ItemNumberStyle" BasedOn="{StaticResource BaseItemNumberStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#A6A6A6" />
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleStyle" BasedOn="{StaticResource BaseItemSubTitleStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#d8dee9" />
|
||||
</Style>
|
||||
<Style x:Key="ItemTitleSelectedStyle" BasedOn="{StaticResource BaseItemTitleSelectedStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#2e3440" />
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleSelectedStyle" BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#4c566a" />
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#5e81ac</SolidColorBrush>
|
||||
<Style x:Key="ThumbStyle" BasedOn="{StaticResource BaseThumbStyle}" TargetType="{x:Type Thumb}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Thumb}">
|
||||
<Border CornerRadius="2" DockPanel.Dock="Right" Background="#4c566a" BorderBrush="Transparent" BorderThickness="0" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ScrollBarStyle" BasedOn="{StaticResource BaseScrollBarStyle}" TargetType="{x:Type ScrollBar}" >
|
||||
<Setter Property="Width" Value="3"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
56
Flow.Launcher/Themes/Nord.xaml
Normal file
56
Flow.Launcher/Themes/Nord.xaml
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style x:Key="QueryBoxStyle" BasedOn="{StaticResource BaseQueryBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#4c566a" />
|
||||
<Setter Property="Foreground" Value="#eceff4" />
|
||||
<Setter Property="FontSize" Value="30" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Background" Value="#4c566a" />
|
||||
<Setter Property="Foreground" Value="#eceff4" />
|
||||
<Setter Property="FontSize" Value="30" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="WindowBorderStyle" BasedOn="{StaticResource BaseWindowBorderStyle}" TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderBrush" Value="#2e3440" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Background" Value="#4c566a"></Setter>
|
||||
</Style>
|
||||
<Style x:Key="WindowStyle" TargetType="{x:Type Window}" BasedOn="{StaticResource BaseWindowStyle}" >
|
||||
</Style>
|
||||
<Style x:Key="PendingLineStyle" BasedOn="{StaticResource BasePendingLineStyle}" TargetType="{x:Type Line}" />
|
||||
<Style x:Key="ItemTitleStyle" BasedOn="{StaticResource BaseItemTitleStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#e5e9f0" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
</Style>
|
||||
<Style x:Key="ItemNumberStyle" BasedOn="{StaticResource BaseItemNumberStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#A6A6A6" />
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleStyle" BasedOn="{StaticResource BaseItemSubTitleStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#d8dee9" />
|
||||
</Style>
|
||||
<Style x:Key="ItemTitleSelectedStyle" BasedOn="{StaticResource BaseItemTitleSelectedStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#2e3440" />
|
||||
</Style>
|
||||
<Style x:Key="ItemSubTitleSelectedStyle" BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#4c566a" />
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#5e81ac</SolidColorBrush>
|
||||
<Style x:Key="ThumbStyle" BasedOn="{StaticResource BaseThumbStyle}" TargetType="{x:Type Thumb}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Thumb}">
|
||||
<Border CornerRadius="2" DockPanel.Dock="Right" Background="#2e3440" BorderBrush="Transparent" BorderThickness="0" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ScrollBarStyle" BasedOn="{StaticResource BaseScrollBarStyle}" TargetType="{x:Type ScrollBar}" >
|
||||
<Setter Property="Width" Value="3"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -20,9 +18,8 @@ using Flow.Launcher.Infrastructure.UserSettings;
|
|||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.Storage;
|
||||
using System.Windows.Media;
|
||||
using Flow.Launcher.Infrastructure.Image;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
|
|
@ -96,11 +93,18 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
async Task updateAction()
|
||||
{
|
||||
var queue = new Dictionary<string, ResultsForUpdate>();
|
||||
while (await _resultsUpdateQueue.OutputAvailableAsync())
|
||||
{
|
||||
queue.Clear();
|
||||
await Task.Delay(20);
|
||||
_resultsUpdateQueue.TryReceiveAll(out var queue);
|
||||
UpdateResultView(queue.Where(r => !r.Token.IsCancellationRequested));
|
||||
while (_resultsUpdateQueue.TryReceive(out var item))
|
||||
{
|
||||
if (!item.Token.IsCancellationRequested)
|
||||
queue[item.ID] = item;
|
||||
}
|
||||
|
||||
UpdateResultView(queue.Values);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -236,7 +240,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
public string QueryText
|
||||
{
|
||||
get { return _queryText; }
|
||||
get => _queryText;
|
||||
set
|
||||
{
|
||||
_queryText = value;
|
||||
|
|
@ -351,9 +355,20 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
var filtered = results.Where
|
||||
(
|
||||
r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet()
|
||||
|| StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet()
|
||||
).ToList();
|
||||
r =>
|
||||
{
|
||||
var match = StringMatcher.FuzzySearch(query, r.Title);
|
||||
if (!match.IsSearchPrecisionScoreMet())
|
||||
{
|
||||
match = StringMatcher.FuzzySearch(query, r.SubTitle);
|
||||
}
|
||||
|
||||
if (!match.IsSearchPrecisionScoreMet()) return false;
|
||||
|
||||
r.Score = match.Score;
|
||||
return true;
|
||||
|
||||
}).ToList();
|
||||
ContextMenu.AddResults(filtered, id);
|
||||
}
|
||||
else
|
||||
|
|
@ -407,105 +422,111 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
private void QueryResults()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(QueryText))
|
||||
_updateSource?.Cancel();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(QueryText))
|
||||
{
|
||||
_updateSource?.Cancel();
|
||||
var currentUpdateSource = new CancellationTokenSource();
|
||||
_updateSource = currentUpdateSource;
|
||||
var currentCancellationToken = _updateSource.Token;
|
||||
_updateToken = currentCancellationToken;
|
||||
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
_isQueryRunning = true;
|
||||
var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins);
|
||||
if (query != null)
|
||||
{
|
||||
// handle the exclusiveness of plugin using action keyword
|
||||
RemoveOldQueryResults(query);
|
||||
|
||||
_lastQuery = query;
|
||||
|
||||
var plugins = PluginManager.ValidPluginsForQuery(query);
|
||||
Task.Run(async () =>
|
||||
{
|
||||
if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign)
|
||||
{
|
||||
// Wait 45 millisecond for query change in global query
|
||||
// if query changes, return so that it won't be calculated
|
||||
await Task.Delay(45, currentCancellationToken);
|
||||
if (currentCancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
}
|
||||
|
||||
_ = Task.Delay(200, currentCancellationToken).ContinueWith(_ =>
|
||||
{
|
||||
// start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet
|
||||
if (!currentCancellationToken.IsCancellationRequested && _isQueryRunning)
|
||||
{
|
||||
ProgressBarVisibility = Visibility.Visible;
|
||||
}
|
||||
}, currentCancellationToken);
|
||||
|
||||
// so looping will stop once it was cancelled
|
||||
|
||||
Task[] tasks = new Task[plugins.Count];
|
||||
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < plugins.Count; i++)
|
||||
{
|
||||
if (!plugins[i].Metadata.Disabled)
|
||||
tasks[i] = QueryTask(plugins[i], query, currentCancellationToken);
|
||||
else tasks[i] = Task.CompletedTask; // Avoid Null
|
||||
}
|
||||
|
||||
// Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
if (currentCancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
// this should happen once after all queries are done so progress bar should continue
|
||||
// until the end of all querying
|
||||
_isQueryRunning = false;
|
||||
if (!currentCancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// update to hidden if this is still the current query
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
// Local Function
|
||||
async Task QueryTask(PluginPair plugin, Query query, CancellationToken token)
|
||||
{
|
||||
// Since it is wrapped within a Task.Run, the synchronous context is null
|
||||
// Task.Yield will force it to run in ThreadPool
|
||||
await Task.Yield();
|
||||
|
||||
var results = await PluginManager.QueryForPlugin(plugin, query, token);
|
||||
if (!currentCancellationToken.IsCancellationRequested)
|
||||
_resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, query, token));
|
||||
}
|
||||
}, currentCancellationToken).ContinueWith(
|
||||
t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception),
|
||||
TaskContinuationOptions.OnlyOnFaulted);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateSource?.Cancel();
|
||||
Results.Clear();
|
||||
Results.Visbility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
_updateSource?.Dispose();
|
||||
|
||||
var currentUpdateSource = new CancellationTokenSource();
|
||||
_updateSource = currentUpdateSource;
|
||||
var currentCancellationToken = _updateSource.Token;
|
||||
_updateToken = currentCancellationToken;
|
||||
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
_isQueryRunning = true;
|
||||
|
||||
var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins);
|
||||
|
||||
// handle the exclusiveness of plugin using action keyword
|
||||
RemoveOldQueryResults(query);
|
||||
|
||||
_lastQuery = query;
|
||||
|
||||
var plugins = PluginManager.ValidPluginsForQuery(query);
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign)
|
||||
{
|
||||
// Wait 45 millisecond for query change in global query
|
||||
// if query changes, return so that it won't be calculated
|
||||
await Task.Delay(45, currentCancellationToken);
|
||||
if (currentCancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
}
|
||||
|
||||
_ = Task.Delay(200, currentCancellationToken).ContinueWith(_ =>
|
||||
{
|
||||
// start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet
|
||||
if (!currentCancellationToken.IsCancellationRequested && _isQueryRunning)
|
||||
{
|
||||
ProgressBarVisibility = Visibility.Visible;
|
||||
}
|
||||
}, currentCancellationToken);
|
||||
|
||||
Task[] tasks = new Task[plugins.Count];
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < plugins.Count; i++)
|
||||
{
|
||||
if (!plugins[i].Metadata.Disabled)
|
||||
{
|
||||
tasks[i] = QueryTask(plugins[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
tasks[i] = Task.CompletedTask; // Avoid Null
|
||||
}
|
||||
}
|
||||
|
||||
// Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
if (currentCancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
// this should happen once after all queries are done so progress bar should continue
|
||||
// until the end of all querying
|
||||
_isQueryRunning = false;
|
||||
if (!currentCancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// update to hidden if this is still the current query
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
// Local function
|
||||
async Task QueryTask(PluginPair plugin)
|
||||
{
|
||||
// Since it is wrapped within a Task.Run, the synchronous context is null
|
||||
// Task.Yield will force it to run in ThreadPool
|
||||
await Task.Yield();
|
||||
|
||||
var results = await PluginManager.QueryForPlugin(plugin, query, currentCancellationToken);
|
||||
if (!currentCancellationToken.IsCancellationRequested)
|
||||
_resultsUpdateQueue.Post(new ResultsForUpdate(results, plugin.Metadata, query,
|
||||
currentCancellationToken));
|
||||
}
|
||||
}, currentCancellationToken)
|
||||
.ContinueWith(t => Log.Exception("|MainViewModel|Plugins Query Exceptions", t.Exception),
|
||||
TaskContinuationOptions.OnlyOnFaulted);
|
||||
}
|
||||
|
||||
|
||||
private void RemoveOldQueryResults(Query query)
|
||||
{
|
||||
string lastKeyword = _lastQuery.ActionKeyword;
|
||||
|
||||
string keyword = query.ActionKeyword;
|
||||
if (string.IsNullOrEmpty(lastKeyword))
|
||||
{
|
||||
|
|
@ -600,7 +621,6 @@ namespace Flow.Launcher.ViewModel
|
|||
return selected;
|
||||
}
|
||||
|
||||
|
||||
private bool HistorySelected()
|
||||
{
|
||||
var selected = SelectedResults == History;
|
||||
|
|
@ -753,45 +773,25 @@ namespace Flow.Launcher.ViewModel
|
|||
#endif
|
||||
|
||||
|
||||
foreach (var result in resultsForUpdates.SelectMany(u => u.Results))
|
||||
foreach (var metaResults in resultsForUpdates)
|
||||
{
|
||||
if (_topMostRecord.IsTopMost(result))
|
||||
foreach (var result in metaResults.Results)
|
||||
{
|
||||
result.Score = int.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Score += _userSelectedRecord.GetSelectedCount(result) * 5;
|
||||
if (_topMostRecord.IsTopMost(result))
|
||||
{
|
||||
result.Score = int.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
var priorityScore = metaResults.Metadata.Priority * 150;
|
||||
result.Score += _userSelectedRecord.GetSelectedCount(result) * 5 + priorityScore;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Results.AddResults(resultsForUpdates, token);
|
||||
}
|
||||
|
||||
/// <summary>U
|
||||
/// To avoid deadlock, this method should not called from main thread
|
||||
/// </summary>
|
||||
public void UpdateResultView(List<Result> list, PluginMetadata metadata, Query originQuery)
|
||||
{
|
||||
foreach (var result in list)
|
||||
{
|
||||
if (_topMostRecord.IsTopMost(result))
|
||||
{
|
||||
result.Score = int.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
var priorityScore = metadata.Priority * 150;
|
||||
result.Score += _userSelectedRecord.GetSelectedCount(result) * 5 + priorityScore;
|
||||
}
|
||||
}
|
||||
|
||||
if (originQuery.RawQuery == _lastQuery.RawQuery)
|
||||
{
|
||||
Results.AddResults(list, metadata.ID);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,11 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Image;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Microsoft.FSharp.Core;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
|
|
@ -128,7 +125,6 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Result.GetHashCode();
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ namespace Flow.Launcher.ViewModel
|
|||
Token = token;
|
||||
}
|
||||
|
||||
|
||||
public ResultsForUpdate(List<Result> results, PluginMetadata metadata, Query query, CancellationToken token)
|
||||
{
|
||||
Results = results;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Configuration;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -11,7 +9,6 @@ using System.Windows;
|
|||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Forms;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
|
|
@ -137,46 +134,14 @@ namespace Flow.Launcher.ViewModel
|
|||
Results.Update(Results.Where(r => r.Result.PluginID != metadata.ID).ToList());
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// To avoid deadlock, this method should not called from main thread
|
||||
/// </summary>
|
||||
public void AddResults(List<Result> newRawResults, string resultId)
|
||||
{
|
||||
var newResults = NewResults(newRawResults, resultId);
|
||||
|
||||
lock (_collectionLock)
|
||||
{
|
||||
var newResults = NewResults(newRawResults, resultId);
|
||||
|
||||
// https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf
|
||||
// fix selected index flow
|
||||
var updateTask = Task.Run(() =>
|
||||
{
|
||||
// update UI in one run, so it can avoid UI flickering
|
||||
|
||||
Results.Update(newResults);
|
||||
if (Results.Any())
|
||||
SelectedItem = Results[0];
|
||||
});
|
||||
if (!updateTask.Wait(300))
|
||||
{
|
||||
updateTask.Dispose();
|
||||
throw new TimeoutException("Update result use too much time.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (Visbility != Visibility.Visible && Results.Count > 0)
|
||||
{
|
||||
Margin = new Thickness { Top = 8 };
|
||||
SelectedIndex = 0;
|
||||
Visbility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
Margin = new Thickness { Top = 0 };
|
||||
Visbility = Visibility.Collapsed;
|
||||
}
|
||||
UpdateResults(newResults);
|
||||
}
|
||||
/// <summary>
|
||||
/// To avoid deadlock, this method should not called from main thread
|
||||
|
|
@ -184,17 +149,21 @@ namespace Flow.Launcher.ViewModel
|
|||
public void AddResults(IEnumerable<ResultsForUpdate> resultsForUpdates, CancellationToken token)
|
||||
{
|
||||
var newResults = NewResults(resultsForUpdates);
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
UpdateResults(newResults, token);
|
||||
}
|
||||
|
||||
private void UpdateResults(List<ResultViewModel> newResults, CancellationToken token = default)
|
||||
{
|
||||
lock (_collectionLock)
|
||||
{
|
||||
// update UI in one run, so it can avoid UI flickering
|
||||
|
||||
Results.Update(newResults, token);
|
||||
if (Results.Any())
|
||||
SelectedItem = Results[0];
|
||||
|
||||
|
||||
}
|
||||
|
||||
switch (Visbility)
|
||||
|
|
@ -209,10 +178,8 @@ namespace Flow.Launcher.ViewModel
|
|||
Visbility = Visibility.Collapsed;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private List<ResultViewModel> NewResults(List<Result> newRawResults, string resultId)
|
||||
{
|
||||
if (newRawResults.Count == 0)
|
||||
|
|
@ -220,12 +187,10 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
var results = Results as IEnumerable<ResultViewModel>;
|
||||
|
||||
var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).ToList();
|
||||
|
||||
|
||||
var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings));
|
||||
|
||||
return results.Where(r => r.Result.PluginID != resultId)
|
||||
.Concat(results.Intersect(newResults).Union(newResults))
|
||||
.Concat(newResults)
|
||||
.OrderByDescending(r => r.Result.Score)
|
||||
.ToList();
|
||||
}
|
||||
|
|
@ -238,14 +203,12 @@ namespace Flow.Launcher.ViewModel
|
|||
var results = Results as IEnumerable<ResultViewModel>;
|
||||
|
||||
return results.Where(r => r != null && !resultsForUpdates.Any(u => u.Metadata.ID == r.Result.PluginID))
|
||||
.Concat(
|
||||
resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings)))
|
||||
.Concat(resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings)))
|
||||
.OrderByDescending(rv => rv.Result.Score)
|
||||
.ToList();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region FormattedText Dependency Property
|
||||
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
|
||||
"FormattedText",
|
||||
|
|
@ -277,55 +240,52 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
#endregion
|
||||
|
||||
public class ResultCollection : ObservableCollection<ResultViewModel>
|
||||
public class ResultCollection : List<ResultViewModel>, INotifyCollectionChanged
|
||||
{
|
||||
|
||||
private long editTime = 0;
|
||||
|
||||
private bool _suppressNotifying = false;
|
||||
|
||||
private CancellationToken _token;
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
|
||||
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (!_suppressNotifying)
|
||||
{
|
||||
base.OnCollectionChanged(e);
|
||||
}
|
||||
CollectionChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public void BulkAddRange(IEnumerable<ResultViewModel> resultViews)
|
||||
public void BulkAddAll(List<ResultViewModel> resultViews)
|
||||
{
|
||||
// suppress notifying before adding all element
|
||||
_suppressNotifying = true;
|
||||
foreach (var item in resultViews)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
_suppressNotifying = false;
|
||||
// manually update event
|
||||
// wpf use directx / double buffered already, so just reset all won't cause ui flickering
|
||||
AddRange(resultViews);
|
||||
|
||||
// can return because the list will be cleared next time updated, which include a reset event
|
||||
if (_token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
// manually update event
|
||||
// wpf use directx / double buffered already, so just reset all won't cause ui flickering
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
public void AddRange(IEnumerable<ResultViewModel> Items)
|
||||
private void AddAll(List<ResultViewModel> Items)
|
||||
{
|
||||
foreach (var item in Items)
|
||||
for (int i = 0; i < Items.Count; i++)
|
||||
{
|
||||
var item = Items[i];
|
||||
if (_token.IsCancellationRequested)
|
||||
return;
|
||||
Add(item);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, i));
|
||||
}
|
||||
}
|
||||
public void RemoveAll()
|
||||
public void RemoveAll(int Capacity = 512)
|
||||
{
|
||||
ClearItems();
|
||||
Clear();
|
||||
if (this.Capacity > 8000 && Capacity < this.Capacity)
|
||||
this.Capacity = Capacity;
|
||||
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update the results collection with new results, try to keep identical results
|
||||
/// </summary>
|
||||
|
|
@ -336,17 +296,21 @@ namespace Flow.Launcher.ViewModel
|
|||
if (Count == 0 && newItems.Count == 0 || _token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
if (editTime < 5 || newItems.Count < 30)
|
||||
if (editTime < 10 || newItems.Count < 30)
|
||||
{
|
||||
if (Count != 0) ClearItems();
|
||||
AddRange(newItems);
|
||||
if (Count != 0) RemoveAll(newItems.Count);
|
||||
AddAll(newItems);
|
||||
editTime++;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Clear();
|
||||
BulkAddRange(newItems);
|
||||
BulkAddAll(newItems);
|
||||
if (Capacity > 8000 && newItems.Count < 3000)
|
||||
{
|
||||
Capacity = newItems.Count;
|
||||
}
|
||||
editTime++;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Threading;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
||||
{
|
||||
|
|
@ -17,14 +17,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
resultManager = new ResultManager(context);
|
||||
}
|
||||
|
||||
internal List<Result> TopLevelDirectorySearch(Query query, string search)
|
||||
internal List<Result> TopLevelDirectorySearch(Query query, string search, CancellationToken token)
|
||||
{
|
||||
var criteria = ConstructSearchCriteria(search);
|
||||
|
||||
if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > search.LastIndexOf(Constants.DirectorySeperator))
|
||||
return DirectorySearch(SearchOption.AllDirectories, query, search, criteria);
|
||||
if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) >
|
||||
search.LastIndexOf(Constants.DirectorySeperator))
|
||||
return DirectorySearch(new EnumerationOptions
|
||||
{
|
||||
RecurseSubdirectories = true
|
||||
}, query, search, criteria, token);
|
||||
|
||||
return DirectorySearch(SearchOption.TopDirectoryOnly, query, search, criteria);
|
||||
return DirectorySearch(new EnumerationOptions(), query, search, criteria, token); // null will be passed as default
|
||||
}
|
||||
|
||||
public string ConstructSearchCriteria(string search)
|
||||
|
|
@ -46,7 +50,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
return incompleteName;
|
||||
}
|
||||
|
||||
private List<Result> DirectorySearch(SearchOption searchOption, Query query, string search, string searchCriteria)
|
||||
private List<Result> DirectorySearch(EnumerationOptions enumerationOption, Query query, string search,
|
||||
string searchCriteria, CancellationToken token)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
|
||||
|
|
@ -59,38 +64,38 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
{
|
||||
var directoryInfo = new System.IO.DirectoryInfo(path);
|
||||
|
||||
foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, searchOption))
|
||||
foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, enumerationOption))
|
||||
{
|
||||
if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue;
|
||||
|
||||
if (fileSystemInfo is System.IO.DirectoryInfo)
|
||||
{
|
||||
folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, fileSystemInfo.FullName, query, true, false));
|
||||
folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName,
|
||||
fileSystemInfo.FullName, query, true, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
fileList.Add(resultManager.CreateFileResult(fileSystemInfo.FullName, query, true, false));
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is UnauthorizedAccessException || e is ArgumentException)
|
||||
{
|
||||
results.Add(new Result { Title = e.Message, Score = 501 });
|
||||
if (!(e is ArgumentException))
|
||||
throw e;
|
||||
|
||||
results.Add(new Result {Title = e.Message, Score = 501});
|
||||
|
||||
return results;
|
||||
}
|
||||
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
|
||||
#endif
|
||||
}
|
||||
|
||||
// Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
|
||||
// Initial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
|
||||
return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,25 +6,31 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks
|
|||
{
|
||||
public class QuickFolderAccess
|
||||
{
|
||||
internal List<Result> FolderListMatched(Query query, List<FolderLink> folderLinks, PluginInitContext context)
|
||||
private readonly ResultManager resultManager;
|
||||
|
||||
public QuickFolderAccess(PluginInitContext context)
|
||||
{
|
||||
resultManager = new ResultManager(context);
|
||||
}
|
||||
|
||||
internal List<Result> FolderListMatched(Query query, List<FolderLink> folderLinks)
|
||||
{
|
||||
if (string.IsNullOrEmpty(query.Search))
|
||||
return new List<Result>();
|
||||
|
||||
string search = query.Search.ToLower();
|
||||
|
||||
var queriedFolderLinks = folderLinks.Where(x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var queriedFolderLinks =
|
||||
folderLinks.Where(x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return queriedFolderLinks.Select(item =>
|
||||
new ResultManager(context)
|
||||
.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
|
||||
.ToList();
|
||||
resultManager.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
internal List<Result> FolderListAll(Query query, List<FolderLink> folderLinks, PluginInitContext context)
|
||||
internal List<Result> FolderListAll(Query query, List<FolderLink> folderLinks)
|
||||
=> folderLinks
|
||||
.Select(item =>
|
||||
new ResultManager(context).CreateFolderResult(item.Nickname, item.Path, item.Path, query))
|
||||
.Select(item => resultManager.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
private readonly IndexSearch indexSearch;
|
||||
|
||||
private readonly QuickFolderAccess quickFolderAccess = new QuickFolderAccess();
|
||||
private readonly QuickFolderAccess quickFolderAccess;
|
||||
|
||||
private readonly ResultManager resultManager;
|
||||
|
||||
|
|
@ -28,6 +28,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
indexSearch = new IndexSearch(context);
|
||||
resultManager = new ResultManager(context);
|
||||
this.settings = settings;
|
||||
quickFolderAccess = new QuickFolderAccess(context);
|
||||
}
|
||||
|
||||
internal async Task<List<Result>> SearchAsync(Query query, CancellationToken token)
|
||||
|
|
@ -40,12 +41,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
return await WindowsIndexFileContentSearchAsync(query, querySearch, token).ConfigureAwait(false);
|
||||
|
||||
// This allows the user to type the assigned action keyword and only see the list of quick folder links
|
||||
if (settings.QuickFolderAccessLinks.Count > 0
|
||||
&& query.ActionKeyword == settings.SearchActionKeyword
|
||||
&& string.IsNullOrEmpty(query.Search))
|
||||
return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks, context);
|
||||
if (string.IsNullOrEmpty(query.Search))
|
||||
return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks);
|
||||
|
||||
var quickFolderLinks = quickFolderAccess.FolderListMatched(query, settings.QuickFolderAccessLinks, context);
|
||||
var quickFolderLinks = quickFolderAccess.FolderListMatched(query, settings.QuickFolderAccessLinks);
|
||||
|
||||
if (quickFolderLinks.Count > 0)
|
||||
results.AddRange(quickFolderLinks);
|
||||
|
|
@ -58,7 +57,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
// Query is a location path with a full environment variable, eg. %appdata%\somefolder\
|
||||
var isEnvironmentVariablePath = querySearch[1..].Contains("%\\");
|
||||
|
||||
if (!FilesFolders.IsLocationPathString(querySearch) && !isEnvironmentVariablePath)
|
||||
if (!querySearch.IsLocationPathString() && !isEnvironmentVariablePath)
|
||||
{
|
||||
results.AddRange(await WindowsIndexFilesAndFoldersSearchAsync(query, querySearch, token).ConfigureAwait(false));
|
||||
|
||||
|
|
@ -70,6 +69,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
if (isEnvironmentVariablePath)
|
||||
locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath);
|
||||
|
||||
// Check that actual location exists, otherwise directory search will throw directory not found exception
|
||||
if (!FilesFolders.LocationExists(FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath)))
|
||||
return results;
|
||||
|
||||
|
|
@ -77,15 +77,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
results.Add(resultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch));
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return null;
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
results.AddRange(await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync,
|
||||
DirectoryInfoClassSearch,
|
||||
useIndexSearch,
|
||||
query,
|
||||
locationPath,
|
||||
token).ConfigureAwait(false));
|
||||
var directoryResult = await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync,
|
||||
DirectoryInfoClassSearch,
|
||||
useIndexSearch,
|
||||
query,
|
||||
locationPath,
|
||||
token).ConfigureAwait(false);
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
results.AddRange(directoryResult);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
|
@ -109,23 +112,23 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
return actionKeyword == settings.FileContentSearchActionKeyword;
|
||||
}
|
||||
|
||||
private List<Result> DirectoryInfoClassSearch(Query query, string querySearch)
|
||||
private List<Result> DirectoryInfoClassSearch(Query query, string querySearch, CancellationToken token)
|
||||
{
|
||||
var directoryInfoSearch = new DirectoryInfoSearch(context);
|
||||
|
||||
return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch);
|
||||
return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch, token);
|
||||
}
|
||||
|
||||
public async Task<List<Result>> TopLevelDirectorySearchBehaviourAsync(
|
||||
Func<Query, string, CancellationToken, Task<List<Result>>> windowsIndexSearch,
|
||||
Func<Query, string, List<Result>> directoryInfoClassSearch,
|
||||
Func<Query, string, CancellationToken, List<Result>> directoryInfoClassSearch,
|
||||
bool useIndexSearch,
|
||||
Query query,
|
||||
string querySearchString,
|
||||
CancellationToken token)
|
||||
{
|
||||
if (!useIndexSearch)
|
||||
return directoryInfoClassSearch(query, querySearchString);
|
||||
return directoryInfoClassSearch(query, querySearchString, token);
|
||||
|
||||
return await windowsIndexSearch(query, querySearchString, token);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Microsoft.Search.Interop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
@ -15,7 +15,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
private readonly ResultManager resultManager;
|
||||
|
||||
// Reserved keywords in oleDB
|
||||
private readonly string reservedStringPattern = @"^[\/\\\$\%_]+$";
|
||||
private readonly string reservedStringPattern = @"^[`\@\#\^,\&\/\\\$\%_]+$";
|
||||
|
||||
internal IndexSearch(PluginInitContext context)
|
||||
{
|
||||
|
|
@ -24,9 +24,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
|
||||
internal async Task<List<Result>> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token)
|
||||
{
|
||||
var folderResults = new List<Result>();
|
||||
var fileResults = new List<Result>();
|
||||
var results = new List<Result>();
|
||||
var fileResults = new List<Result>();
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -55,7 +54,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
|
||||
if (dataReaderResults.GetString(2) == "Directory")
|
||||
{
|
||||
folderResults.Add(resultManager.CreateFolderResult(
|
||||
results.Add(resultManager.CreateFolderResult(
|
||||
dataReaderResults.GetString(0),
|
||||
path,
|
||||
path,
|
||||
|
|
@ -83,8 +82,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
LogException("General error from performing index search", e);
|
||||
}
|
||||
|
||||
results.AddRange(fileResults);
|
||||
|
||||
// Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
|
||||
return results.Concat(folderResults.OrderBy(x => x.Title)).Concat(fileResults.OrderBy(x => x.Title)).ToList(); ;
|
||||
return results;
|
||||
}
|
||||
|
||||
internal async Task<List<Result>> WindowsIndexSearchAsync(string searchString, string connectionString,
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
|
||||
// Get the ISearchQueryHelper which will help us to translate AQS --> SQL necessary to query the indexer
|
||||
var queryHelper = catalogManager.GetQueryHelper();
|
||||
|
||||
|
||||
return queryHelper;
|
||||
}
|
||||
|
||||
|
|
@ -81,11 +81,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
var previousLevelDirectory = path.Substring(0, indexOfSeparator);
|
||||
|
||||
if (string.IsNullOrEmpty(itemName))
|
||||
return searchDepth + $"{previousLevelDirectory}'";
|
||||
return $"{searchDepth}{previousLevelDirectory}'";
|
||||
|
||||
return $"(System.FileName LIKE '{itemName}%' " +
|
||||
$"OR CONTAINS(System.FileName,'\"{itemName}*\"',1033)) AND " +
|
||||
searchDepth + $"{previousLevelDirectory}'";
|
||||
return $"(System.FileName LIKE '{itemName}%' OR CONTAINS(System.FileName,'\"{itemName}*\"',1033)) AND {searchDepth}{previousLevelDirectory}'";
|
||||
}
|
||||
|
||||
///<summary>
|
||||
|
|
@ -96,9 +94,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
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 + QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path) + QueryOrderByFileNameRestriction;
|
||||
|
||||
return query + QueryWhereRestrictionsForTopLevelDirectorySearch(path);
|
||||
return query + QueryWhereRestrictionsForTopLevelDirectorySearch(path) + QueryOrderByFileNameRestriction;
|
||||
}
|
||||
|
||||
///<summary>
|
||||
|
|
@ -107,16 +105,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
public string QueryForAllFilesAndFolders(string userSearchString)
|
||||
{
|
||||
// Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause
|
||||
return CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch();
|
||||
return CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch
|
||||
+ QueryOrderByFileNameRestriction;
|
||||
}
|
||||
|
||||
///<summary>
|
||||
/// Set the required WHERE clause restriction to search for all files and folders.
|
||||
///</summary>
|
||||
public string QueryWhereRestrictionsForAllFilesAndFoldersSearch()
|
||||
{
|
||||
return $"scope='file:'";
|
||||
}
|
||||
public const string QueryWhereRestrictionsForAllFilesAndFoldersSearch = "scope='file:'";
|
||||
|
||||
public const string QueryOrderByFileNameRestriction = " ORDER BY System.FileName";
|
||||
|
||||
|
||||
///<summary>
|
||||
/// Search will be performed on all indexed file contents for the specified search keywords.
|
||||
|
|
@ -125,7 +124,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
{
|
||||
string query = "SELECT TOP " + settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE ";
|
||||
|
||||
return query + QueryWhereRestrictionsForFileContentSearch(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch();
|
||||
return query + QueryWhereRestrictionsForFileContentSearch(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch
|
||||
+ QueryOrderByFileNameRestriction;
|
||||
}
|
||||
|
||||
///<summary>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using Flow.Launcher.Plugin.Explorer.Search;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"Name": "Explorer",
|
||||
"Description": "Search and manage files and folders. Explorer utilises Windows Index Search",
|
||||
"Author": "Jeremy Wu",
|
||||
"Version": "1.2.6",
|
||||
"Version": "1.5.0",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",
|
||||
|
|
|
|||
|
|
@ -30,15 +30,29 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
return new PluginsManagerSettings(viewModel);
|
||||
}
|
||||
|
||||
public async Task InitAsync(PluginInitContext context)
|
||||
public Task InitAsync(PluginInitContext context)
|
||||
{
|
||||
Context = context;
|
||||
viewModel = new SettingsViewModel(context);
|
||||
Settings = viewModel.Settings;
|
||||
contextMenu = new ContextMenu(Context);
|
||||
pluginManager = new PluginsManager(Context, Settings);
|
||||
await pluginManager.UpdateManifest();
|
||||
lastUpdateTime = DateTime.Now;
|
||||
var updateManifestTask = pluginManager.UpdateManifest();
|
||||
_ = updateManifestTask.ContinueWith(t =>
|
||||
{
|
||||
if (t.IsCompletedSuccessfully)
|
||||
{
|
||||
lastUpdateTime = DateTime.Now;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.API.ShowMsg("Plugin Manifest Download Fail.",
|
||||
"Please check if you can connect to github.com. " +
|
||||
"This error means you may not be able to Install and Update Plugin.", pluginManager.icoPath, false);
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public List<Result> LoadContextMenus(Result selectedResult)
|
||||
|
|
@ -61,7 +75,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
|
||||
return search switch
|
||||
{
|
||||
var s when s.StartsWith(Settings.HotKeyInstall) => pluginManager.RequestInstallOrUpdate(s),
|
||||
var s when s.StartsWith(Settings.HotKeyInstall) => await pluginManager.RequestInstallOrUpdate(s, token),
|
||||
var s when s.StartsWith(Settings.HotkeyUninstall) => pluginManager.RequestUninstall(s),
|
||||
var s when s.StartsWith(Settings.HotkeyUpdate) => pluginManager.RequestUpdate(s),
|
||||
_ => pluginManager.GetDefaultHotKeys().Where(hotkey =>
|
||||
|
|
@ -93,4 +107,4 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
lastUpdateTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
}
|
||||
}
|
||||
|
||||
private readonly string icoPath = "Images\\pluginsmanager.png";
|
||||
internal readonly string icoPath = "Images\\pluginsmanager.png";
|
||||
|
||||
internal PluginsManager(PluginInitContext context, Settings settings)
|
||||
{
|
||||
|
|
@ -64,27 +65,27 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
return false;
|
||||
}
|
||||
},
|
||||
new Result()
|
||||
new Result()
|
||||
{
|
||||
Title = Settings.HotkeyUninstall,
|
||||
IcoPath = icoPath,
|
||||
Action = _ =>
|
||||
{
|
||||
Title = Settings.HotkeyUninstall,
|
||||
IcoPath = icoPath,
|
||||
Action = _ =>
|
||||
{
|
||||
Context.API.ChangeQuery("pm uninstall ");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
new Result()
|
||||
{
|
||||
Title = Settings.HotkeyUpdate,
|
||||
IcoPath = icoPath,
|
||||
Action = _ =>
|
||||
{
|
||||
Context.API.ChangeQuery("pm update ");
|
||||
return false;
|
||||
}
|
||||
Context.API.ChangeQuery("pm uninstall ");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
},
|
||||
new Result()
|
||||
{
|
||||
Title = Settings.HotkeyUpdate,
|
||||
IcoPath = icoPath,
|
||||
Action = _ =>
|
||||
{
|
||||
Context.API.ChangeQuery("pm update ");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
internal async Task InstallOrUpdate(UserPlugin plugin)
|
||||
|
|
@ -137,7 +138,8 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
catch (Exception e)
|
||||
{
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), plugin.Name));
|
||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
|
||||
plugin.Name));
|
||||
|
||||
Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "InstallOrUpdate");
|
||||
|
||||
|
|
@ -164,7 +166,8 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
from existingPlugin in Context.API.GetAllPlugins()
|
||||
join pluginFromManifest in pluginsManifest.UserPlugins
|
||||
on existingPlugin.Metadata.ID equals pluginFromManifest.ID
|
||||
where existingPlugin.Metadata.Version.CompareTo(pluginFromManifest.Version) < 0 // if current version precedes manifest version
|
||||
where existingPlugin.Metadata.Version.CompareTo(pluginFromManifest.Version) <
|
||||
0 // if current version precedes manifest version
|
||||
select
|
||||
new
|
||||
{
|
||||
|
|
@ -214,22 +217,29 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
|
||||
Task.Run(async delegate
|
||||
{
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_please_wait"));
|
||||
Context.API.ShowMsg(
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_please_wait"));
|
||||
|
||||
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath).ConfigureAwait(false);
|
||||
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_download_success"));
|
||||
Context.API.ShowMsg(
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_download_success"));
|
||||
|
||||
Install(x.PluginNewUserPlugin, downloadToFilePath);
|
||||
|
||||
Context.API.RestartApp();
|
||||
}).ContinueWith(t =>
|
||||
{
|
||||
Log.Exception("PluginsManager", $"Update failed for {x.Name}", t.Exception.InnerException, "RequestUpdate");
|
||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), x.Name));
|
||||
Log.Exception("PluginsManager", $"Update failed for {x.Name}",
|
||||
t.Exception.InnerException, "RequestUpdate");
|
||||
Context.API.ShowMsg(
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
|
||||
string.Format(
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
|
||||
x.Name));
|
||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||
|
||||
return true;
|
||||
|
|
@ -264,8 +274,21 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
.ToList();
|
||||
}
|
||||
|
||||
internal List<Result> RequestInstallOrUpdate(string searchName)
|
||||
private Task _downloadManifestTask = Task.CompletedTask;
|
||||
|
||||
internal async ValueTask<List<Result>> RequestInstallOrUpdate(string searchName, CancellationToken token)
|
||||
{
|
||||
if (!pluginsManifest.UserPlugins.Any() &&
|
||||
_downloadManifestTask.Status != TaskStatus.Running)
|
||||
{
|
||||
_downloadManifestTask = pluginsManifest.DownloadManifest();
|
||||
}
|
||||
|
||||
await _downloadManifestTask;
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return null;
|
||||
|
||||
var searchNameWithoutKeyword = searchName.Replace(Settings.HotKeyInstall, string.Empty).Trim();
|
||||
|
||||
var results =
|
||||
|
|
@ -406,4 +429,4 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
return new List<Result>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
"Name": "Plugins Manager",
|
||||
"Description": "Management of installing, uninstalling or updating Flow Launcher plugins",
|
||||
"Author": "Jeremy Wu",
|
||||
"Version": "1.4.1",
|
||||
"Version": "1.6.1",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll",
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ namespace Flow.Launcher.Plugin.Program
|
|||
|
||||
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
||||
{
|
||||
if (IsStartupIndexProgramsRequired)
|
||||
_ = IndexPrograms();
|
||||
|
||||
Win32[] win32;
|
||||
UWP.Application[] uwps;
|
||||
|
||||
|
|
@ -97,22 +100,31 @@ namespace Flow.Launcher.Plugin.Program
|
|||
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>");
|
||||
});
|
||||
|
||||
bool indexedWinApps = false;
|
||||
bool indexedUWPApps = false;
|
||||
|
||||
var a = Task.Run(() =>
|
||||
{
|
||||
if (IsStartupIndexProgramsRequired || !_win32s.Any())
|
||||
{
|
||||
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs);
|
||||
indexedWinApps = true;
|
||||
}
|
||||
});
|
||||
|
||||
var b = Task.Run(() =>
|
||||
{
|
||||
if (IsStartupIndexProgramsRequired || !_uwps.Any())
|
||||
{
|
||||
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexUwpPrograms);
|
||||
indexedUWPApps = true;
|
||||
}
|
||||
});
|
||||
|
||||
await Task.WhenAll(a, b);
|
||||
|
||||
_settings.LastIndexTime = DateTime.Today;
|
||||
if (indexedWinApps && indexedUWPApps)
|
||||
_settings.LastIndexTime = DateTime.Today;
|
||||
}
|
||||
|
||||
public static void IndexWin32Programs()
|
||||
|
|
@ -138,7 +150,7 @@ namespace Flow.Launcher.Plugin.Program
|
|||
|
||||
var t2 = Task.Run(IndexUwpPrograms);
|
||||
|
||||
await Task.WhenAll(t1, t2);
|
||||
await Task.WhenAll(t1, t2).ConfigureAwait(false);
|
||||
|
||||
_settings.LastIndexTime = DateTime.Today;
|
||||
}
|
||||
|
|
@ -193,12 +205,14 @@ namespace Flow.Launcher.Plugin.Program
|
|||
return;
|
||||
|
||||
if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
|
||||
_uwps.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier).FirstOrDefault().Enabled =
|
||||
false;
|
||||
_uwps.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)
|
||||
.FirstOrDefault()
|
||||
.Enabled = false;
|
||||
|
||||
if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
|
||||
_win32s.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier).FirstOrDefault().Enabled =
|
||||
false;
|
||||
_win32s.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)
|
||||
.FirstOrDefault()
|
||||
.Enabled = false;
|
||||
|
||||
_settings.DisabledProgramSources
|
||||
.Add(
|
||||
|
|
@ -232,4 +246,4 @@ namespace Flow.Launcher.Plugin.Program
|
|||
await IndexPrograms();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
|
||||
private void ViewRefresh()
|
||||
{
|
||||
if(programSourceView.Items.Count == 0
|
||||
if (programSourceView.Items.Count == 0
|
||||
&& btnProgramSourceStatus.Visibility == Visibility.Visible
|
||||
&& btnEditProgramSource.Visibility == Visibility.Visible)
|
||||
{
|
||||
|
|
@ -70,21 +70,19 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
programSourceView.Items.Refresh();
|
||||
}
|
||||
|
||||
private void ReIndexing()
|
||||
private async void ReIndexing()
|
||||
{
|
||||
ViewRefresh();
|
||||
Task.Run(() =>
|
||||
{
|
||||
Dispatcher.Invoke(() => { indexingPanel.Visibility = Visibility.Visible; });
|
||||
Main.IndexPrograms();
|
||||
Dispatcher.Invoke(() => { indexingPanel.Visibility = Visibility.Hidden; });
|
||||
});
|
||||
|
||||
indexingPanel.Visibility = Visibility.Visible;
|
||||
await Main.IndexPrograms();
|
||||
indexingPanel.Visibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
private void btnAddProgramSource_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var add = new AddProgramSource(context, _settings);
|
||||
if(add.ShowDialog() ?? false)
|
||||
if (add.ShowDialog() ?? false)
|
||||
{
|
||||
ReIndexing();
|
||||
}
|
||||
|
|
@ -165,14 +163,14 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
UniqueIdentifier = directory
|
||||
};
|
||||
|
||||
directoriesToAdd.Add(source);
|
||||
directoriesToAdd.Add(source);
|
||||
}
|
||||
}
|
||||
|
||||
if (directoriesToAdd.Count() > 0)
|
||||
{
|
||||
directoriesToAdd.ForEach(x => _settings.ProgramSources.Add(x));
|
||||
directoriesToAdd.ForEach(x => ProgramSettingDisplayList.Add(x));
|
||||
directoriesToAdd.ForEach(x => ProgramSettingDisplayList.Add(x));
|
||||
|
||||
ViewRefresh();
|
||||
ReIndexing();
|
||||
|
|
@ -238,8 +236,8 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
ProgramSettingDisplayList.SetProgramSourcesStatus(selectedItems, true);
|
||||
|
||||
ProgramSettingDisplayList.RemoveDisabledFromSettings();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (selectedItems.IsReindexRequired())
|
||||
ReIndexing();
|
||||
|
||||
|
|
@ -282,7 +280,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
var sortBy = columnBinding?.Path.Path ?? headerClicked.Column.Header as string;
|
||||
|
||||
Sort(sortBy, direction);
|
||||
|
||||
|
||||
_lastHeaderClicked = headerClicked;
|
||||
_lastDirection = direction;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Name": "Program",
|
||||
"Description": "Search programs in Flow.Launcher",
|
||||
"Author": "qianlifeng",
|
||||
"Version": "1.2.3",
|
||||
"Version": "1.3.0",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Program.dll",
|
||||
|
|
|
|||
|
|
@ -170,8 +170,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
// http://www.pinvoke.net/default.aspx/shell32/SHEmptyRecycleBin.html
|
||||
// FYI, couldn't find documentation for this but if the recycle bin is already empty, it will return -2147418113 (0x8000FFFF (E_UNEXPECTED))
|
||||
// 0 for nothing
|
||||
var result = SHEmptyRecycleBin(new WindowInteropHelper(Application.Current.MainWindow).Handle,
|
||||
0);
|
||||
var result = SHEmptyRecycleBin(new WindowInteropHelper(Application.Current.MainWindow).Handle, 0);
|
||||
if (result != (uint) HRESULT.S_OK && result != (uint) 0x8000FFFF)
|
||||
{
|
||||
MessageBox.Show($"Error emptying recycle bin, error code: {result}\n" +
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Name": "System Commands",
|
||||
"Description": "Provide System related commands. e.g. shutdown,lock,setting etc.",
|
||||
"Author": "qianlifeng",
|
||||
"Version": "1.1.2",
|
||||
"Version": "1.2.0",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Sys.dll",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_url_open_search_in">Open search in:</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_new_window">New Window</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_new_tab">New Tab</system:String>
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_url_open_url">Open url:{0}</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_url_canot_open_url">Can't open url:{0}</system:String>
|
||||
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@
|
|||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="0">
|
||||
<Label Content="Open search in:" Margin="0 10 15 0" />
|
||||
<Label Content="{DynamicResource flowlauncher_plugin_url_open_search_in}" Margin="0 10 15 0" />
|
||||
<RadioButton x:Name="NewWindowBrowser" GroupName="OpenSearchBehaviour"
|
||||
Content="New window" Click="OnNewBrowserWindowClick" />
|
||||
Content="{DynamicResource flowlauncher_plugin_new_window}" Click="OnNewBrowserWindowClick" />
|
||||
<RadioButton x:Name="NewTabInBrowser" GroupName="OpenSearchBehaviour"
|
||||
Content="New tab" Click="OnNewTabClick" />
|
||||
Content="{DynamicResource flowlauncher_plugin_new_tab}" Click="OnNewTabClick" />
|
||||
</StackPanel>
|
||||
<StackPanel VerticalAlignment="Top" Grid.Row="1" Height="106" Margin="0 20 0 0">
|
||||
<Label Content="{DynamicResource flowlauncher_plugin_url_plugin_set_tip}" Height="28" Margin="0,0,155,0"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Name": "URL",
|
||||
"Description": "Open the typed URL from Flow Launcher",
|
||||
"Author": "qianlifeng",
|
||||
"Version": "1.1.2",
|
||||
"Version": "1.1.3",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Url.dll",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_websearch_open_search_in">Open search in:</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_websearch_new_window">New Window</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_websearch_new_tab">New Tab</system:String>
|
||||
<system:String x:Key="flowlaucnher_plugin_websearch_set_browser_path">Set browser from path:</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_websearch_choose">Choose</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_websearch_delete">Delete</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_websearch_edit">Edit</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_websearch_add">Add</system:String>
|
||||
|
|
|
|||
|
|
@ -33,17 +33,17 @@
|
|||
<!-- Not sure why binding IsEnabled directly to Settings.EnableWebSaerchSuggestion is not working -->
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
<Label Content="Open search in:" Margin="0 15 20 0"/>
|
||||
<RadioButton Name="NewWindowBrowser" GroupName="OpenSearchBehaviour" Content="New window" Click="OnNewBrowserWindowClick"
|
||||
<Label Content="{DynamicResource flowlauncher_plugin_websearch_open_search_in}" Margin="0 15 20 0"/>
|
||||
<RadioButton Name="NewWindowBrowser" GroupName="OpenSearchBehaviour" Content="{DynamicResource flowlauncher_plugin_websearch_new_window}" Click="OnNewBrowserWindowClick"
|
||||
Margin="0 0 20 0"/>
|
||||
<RadioButton Name="NewTabInBrowser" GroupName="OpenSearchBehaviour" Content="New tab" Click="OnNewTabClick" />
|
||||
<RadioButton Name="NewTabInBrowser" GroupName="OpenSearchBehaviour" Content="{DynamicResource flowlauncher_plugin_websearch_new_tab}" Click="OnNewTabClick" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="2" HorizontalAlignment="Left" Margin="0 3 0 0">
|
||||
<Label Content="Set browser from path:" Margin="0 0 350 0" HorizontalAlignment="Left" Width="140"/>
|
||||
<Label Content="{DynamicResource flowlaucnher_plugin_websearch_set_browser_path}" Margin="0 0 350 0" HorizontalAlignment="Left" Width="140"/>
|
||||
<TextBox x:Name="browserPathBox" HorizontalAlignment="Left" Margin="160,-22,0,0" TextChanged="OnBrowserPathTextChanged"
|
||||
Width="214" Style="{StaticResource BrowserPathBoxStyle}"/>
|
||||
<Button x:Name="viewButton" HorizontalAlignment="Left" Margin="400,-30,0,0"
|
||||
Click="OnChooseClick" FontSize="13" Content="Choose" Width="80"/>
|
||||
Click="OnChooseClick" FontSize="13" Content="{DynamicResource flowlauncher_plugin_websearch_choose}" Width="80"/>
|
||||
</StackPanel>
|
||||
<ListView ItemsSource="{Binding Settings.SearchSources}"
|
||||
SelectedItem="{Binding Settings.SelectedSearchSource}"
|
||||
|
|
|
|||
|
|
@ -22,8 +22,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
try
|
||||
{
|
||||
const string api = "https://api.bing.com/qsonhs.aspx?q=";
|
||||
|
||||
using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query), token).ConfigureAwait(false);
|
||||
if (resultStream.Length == 0) return new List<string>(); // this handles the cancellation
|
||||
|
||||
if (resultStream.Length == 0)
|
||||
return new List<string>(); // this handles the cancellation
|
||||
|
||||
json = (await JsonDocument.ParseAsync(resultStream, cancellationToken: token)).RootElement.GetProperty("AS");
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
try
|
||||
{
|
||||
const string api = "https://www.google.com/complete/search?output=chrome&q=";
|
||||
|
||||
using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeUriString(query)).ConfigureAwait(false);
|
||||
if (resultStream.Length == 0) return new List<string>();
|
||||
|
||||
if (resultStream.Length == 0)
|
||||
return new List<string>();
|
||||
|
||||
json = await JsonDocument.ParseAsync(resultStream);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,4 +8,4 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
|
|||
{
|
||||
public abstract Task<List<string>> Suggestions(string query, CancellationToken token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
"Name": "Web Searches",
|
||||
"Description": "Provide the web search ability",
|
||||
"Author": "qianlifeng",
|
||||
"Version": "1.2.1",
|
||||
"Version": "1.3.1",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.WebSearch.dll",
|
||||
|
|
|
|||
Loading…
Reference in a new issue