To remove the condition that space is needed after the Action keyword (#6003)

* Returning individual queries for each plugin

* Changed cancellation token from Query type to directly using the rawQuery

* Changed the way we get the plugins for which we execute the query

* updated UpdateResultView to take a string instead of query

* Changed the way we set a query for each plugin

* removed todo comment

* global plugins are added as a part of the query builder

* Fix for plugin.json of Folder plugin being copied into the shell plugin

* >,< and : are not allowed in file paths and indexer creates a query which searches compares if a file name is greater than or lesser than the query

* Reformatted the regex

* catching the exception

* fixed existing tests

* modified it so that it works with action keyword as well as action keywords

* Added unit tests for non global plugins

* fixed test

* add back whitespace that was removed by mistake

* fix regex

* modified the cold start query

* remove extra condition

* terms being modified as expected

* used key value pairs to iterate through the dictionary

* renamed variable

* added check for an empty dictionary

* remove : because it may appear in the file path

* fix some whitespace warnings that were being treated as errors
This commit is contained in:
Alekhya 2020-08-21 12:21:42 -07:00 committed by GitHub
parent f6b5840e0e
commit 80f8c0399b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 247 additions and 61 deletions

View File

@ -40,7 +40,7 @@ namespace Microsoft.Plugin.Indexer
private readonly IndexerDriveDetection _driveDetection = new IndexerDriveDetection(new RegistryWrapper()); private readonly IndexerDriveDetection _driveDetection = new IndexerDriveDetection(new RegistryWrapper());
// Reserved keywords in oleDB // Reserved keywords in oleDB
private readonly string reservedStringPattern = @"^[\/\\\$\%]+$"; private readonly string reservedStringPattern = @"^[\/\\\$\%]+$|^.*[<>].*$";
private string WarningIconPath { get; set; } private string WarningIconPath { get; set; }

View File

@ -29,7 +29,9 @@ namespace PowerLauncher.ViewModel
{ {
public class MainViewModel : BaseModel, ISavable, IDisposable public class MainViewModel : BaseModel, ISavable, IDisposable
{ {
private static readonly Query _emptyQuery = new Query(); private string _currentQuery;
private static string _emptyQuery = string.Empty;
private static bool _disposed; private static bool _disposed;
private readonly WoxJsonStorage<QueryHistory> _historyItemsStorage; private readonly WoxJsonStorage<QueryHistory> _historyItemsStorage;
@ -43,7 +45,6 @@ namespace PowerLauncher.ViewModel
private readonly Internationalization _translator = InternationalizationManager.Instance; private readonly Internationalization _translator = InternationalizationManager.Instance;
private readonly System.Diagnostics.Stopwatch _hotkeyTimer = new System.Diagnostics.Stopwatch(); private readonly System.Diagnostics.Stopwatch _hotkeyTimer = new System.Diagnostics.Stopwatch();
private Query _currentQuery;
private string _queryTextBeforeLeaveResults; private string _queryTextBeforeLeaveResults;
private CancellationTokenSource _updateSource; private CancellationTokenSource _updateSource;
@ -113,7 +114,7 @@ namespace PowerLauncher.ViewModel
() => () =>
{ {
PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query);
UpdateResultView(e.Results, e.Query, _updateToken); UpdateResultView(e.Results, e.Query.RawQuery, _updateToken);
}, _updateToken); }, _updateToken);
}; };
} }
@ -461,24 +462,32 @@ namespace PowerLauncher.ViewModel
_updateSource = currentUpdateSource; _updateSource = currentUpdateSource;
var currentCancellationToken = _updateSource.Token; var currentCancellationToken = _updateSource.Token;
_updateToken = currentCancellationToken; _updateToken = currentCancellationToken;
var queryText = QueryText.Trim();
var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins); var pluginQueryPairs = QueryBuilder.Build(ref queryText, PluginManager.NonGlobalPlugins);
if (query != null) if (pluginQueryPairs != null && pluginQueryPairs.Count > 0)
{ {
_currentQuery = query; _currentQuery = queryText;
Task.Run( Task.Run(
() => () =>
{ {
Thread.Sleep(20); Thread.Sleep(20);
var plugins = PluginManager.ValidPluginsForQuery(query);
// Contains all the plugins for which this raw query is valid
var plugins = pluginQueryPairs.Keys.ToList();
try try
{ {
currentCancellationToken.ThrowIfCancellationRequested(); currentCancellationToken.ThrowIfCancellationRequested();
var resultPluginPair = new List<(List<Result>, PluginMetadata)>(); var resultPluginPair = new List<(List<Result>, PluginMetadata)>();
foreach (PluginPair plugin in plugins)
// To execute a query corresponding to each plugin
foreach (KeyValuePair<PluginPair, Query> pluginQueryItem in pluginQueryPairs)
{ {
var plugin = pluginQueryItem.Key;
var query = pluginQueryItem.Value;
if (!plugin.Metadata.Disabled) if (!plugin.Metadata.Disabled)
{ {
var results = PluginManager.QueryForPlugin(plugin, query); var results = PluginManager.QueryForPlugin(plugin, query);
@ -489,12 +498,12 @@ namespace PowerLauncher.ViewModel
lock (_addResultsLock) lock (_addResultsLock)
{ {
if (query.RawQuery == _currentQuery.RawQuery) if (queryText.Equals(_currentQuery, StringComparison.CurrentCultureIgnoreCase))
{ {
Results.Clear(); Results.Clear();
foreach (var p in resultPluginPair) foreach (var p in resultPluginPair)
{ {
UpdateResultView(p.Item1, query, currentCancellationToken); UpdateResultView(p.Item1, queryText, currentCancellationToken);
currentCancellationToken.ThrowIfCancellationRequested(); currentCancellationToken.ThrowIfCancellationRequested();
} }
@ -504,7 +513,8 @@ namespace PowerLauncher.ViewModel
} }
currentCancellationToken.ThrowIfCancellationRequested(); currentCancellationToken.ThrowIfCancellationRequested();
UpdateResultsListViewAfterQuery(query);
UpdateResultsListViewAfterQuery(queryText);
// Run the slower query of the DelayedExecution plugins // Run the slower query of the DelayedExecution plugins
currentCancellationToken.ThrowIfCancellationRequested(); currentCancellationToken.ThrowIfCancellationRequested();
@ -514,13 +524,16 @@ namespace PowerLauncher.ViewModel
{ {
if (!plugin.Metadata.Disabled) if (!plugin.Metadata.Disabled)
{ {
Query query;
pluginQueryPairs.TryGetValue(plugin, out query);
var results = PluginManager.QueryForPlugin(plugin, query, true); var results = PluginManager.QueryForPlugin(plugin, query, true);
currentCancellationToken.ThrowIfCancellationRequested(); currentCancellationToken.ThrowIfCancellationRequested();
if ((results?.Count ?? 0) != 0) if ((results?.Count ?? 0) != 0)
{ {
lock (_addResultsLock) lock (_addResultsLock)
{ {
if (query.RawQuery == _currentQuery.RawQuery) if (queryText.Equals(_currentQuery, StringComparison.CurrentCultureIgnoreCase))
{ {
currentCancellationToken.ThrowIfCancellationRequested(); currentCancellationToken.ThrowIfCancellationRequested();
@ -529,14 +542,15 @@ namespace PowerLauncher.ViewModel
currentCancellationToken.ThrowIfCancellationRequested(); currentCancellationToken.ThrowIfCancellationRequested();
// Add the new results from the plugin // Add the new results from the plugin
UpdateResultView(results, query, currentCancellationToken); UpdateResultView(results, queryText, currentCancellationToken);
currentCancellationToken.ThrowIfCancellationRequested(); currentCancellationToken.ThrowIfCancellationRequested();
Results.Sort(); Results.Sort();
} }
} }
currentCancellationToken.ThrowIfCancellationRequested(); currentCancellationToken.ThrowIfCancellationRequested();
UpdateResultsListViewAfterQuery(query, true); UpdateResultsListViewAfterQuery(queryText, true);
} }
} }
} }
@ -556,7 +570,7 @@ namespace PowerLauncher.ViewModel
{ {
QueryTimeMs = queryTimer.ElapsedMilliseconds, QueryTimeMs = queryTimer.ElapsedMilliseconds,
NumResults = Results.Results.Count, NumResults = Results.Results.Count,
QueryLength = query.RawQuery.Length, QueryLength = queryText.Length,
}; };
PowerToysTelemetry.Log.WriteEvent(queryEvent); PowerToysTelemetry.Log.WriteEvent(queryEvent);
}, currentCancellationToken); }, currentCancellationToken);
@ -572,11 +586,11 @@ namespace PowerLauncher.ViewModel
} }
} }
private void UpdateResultsListViewAfterQuery(Query query, bool isDelayedInvoke = false) private void UpdateResultsListViewAfterQuery(string queryText, bool isDelayedInvoke = false)
{ {
Application.Current.Dispatcher.BeginInvoke(new Action(() => Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{ {
if (query.RawQuery == _currentQuery.RawQuery) if (queryText.Equals(_currentQuery, StringComparison.CurrentCultureIgnoreCase))
{ {
Results.Results.NotifyChanges(); Results.Results.NotifyChanges();
} }
@ -740,7 +754,7 @@ namespace PowerLauncher.ViewModel
/// <summary> /// <summary>
/// To avoid deadlock, this method should not called from main thread /// To avoid deadlock, this method should not called from main thread
/// </summary> /// </summary>
public void UpdateResultView(List<Result> list, Query originQuery, CancellationToken ct) public void UpdateResultView(List<Result> list, string originQuery, CancellationToken ct)
{ {
if (list == null) if (list == null)
{ {
@ -764,7 +778,7 @@ namespace PowerLauncher.ViewModel
} }
} }
if (originQuery.RawQuery == _currentQuery.RawQuery) if (originQuery.Equals(_currentQuery, StringComparison.CurrentCultureIgnoreCase))
{ {
ct.ThrowIfCancellationRequested(); ct.ThrowIfCancellationRequested();
Results.AddResults(list, ct); Results.AddResults(list, ct);
@ -786,10 +800,14 @@ namespace PowerLauncher.ViewModel
// Fix Cold start for plugins // Fix Cold start for plugins
string s = "m"; string s = "m";
var query = QueryBuilder.Build(s.Trim(), PluginManager.NonGlobalPlugins); var pluginQueryPairs = QueryBuilder.Build(ref s, PluginManager.NonGlobalPlugins);
var plugins = PluginManager.ValidPluginsForQuery(query);
foreach (PluginPair plugin in plugins) // To execute a query corresponding to each plugin
foreach (KeyValuePair<PluginPair, Query> pluginQueryItem in pluginQueryPairs)
{ {
var plugin = pluginQueryItem.Key;
var query = pluginQueryItem.Value;
if (!plugin.Metadata.Disabled && plugin.Metadata.Name != "Window Walker") if (!plugin.Metadata.Disabled && plugin.Metadata.Name != "Window Walker")
{ {
_ = PluginManager.QueryForPlugin(plugin, query); _ = PluginManager.QueryForPlugin(plugin, query);

View File

@ -11,7 +11,7 @@ namespace Wox.Core.Plugin
{ {
public static class QueryBuilder public static class QueryBuilder
{ {
public static Query Build(string text, Dictionary<string, PluginPair> nonGlobalPlugins) public static Dictionary<PluginPair, Query> Build(ref string text, Dictionary<string, PluginPair> nonGlobalPlugins)
{ {
// replace multiple white spaces with one white space // replace multiple white spaces with one white space
var terms = text.Split(new[] { Query.TermSeparator }, StringSplitOptions.RemoveEmptyEntries); var terms = text.Split(new[] { Query.TermSeparator }, StringSplitOptions.RemoveEmptyEntries);
@ -20,32 +20,54 @@ namespace Wox.Core.Plugin
return null; return null;
} }
// This Dictionary contains the corresponding query for each plugin
Dictionary<PluginPair, Query> pluginQueryPair = new Dictionary<PluginPair, Query>();
var rawQuery = string.Join(Query.TermSeparator, terms); var rawQuery = string.Join(Query.TermSeparator, terms);
string actionKeyword, search;
// This is the query on removing extra spaces which would be executed by global Plugins
text = rawQuery;
string possibleActionKeyword = terms[0]; string possibleActionKeyword = terms[0];
List<string> actionParameters;
if (nonGlobalPlugins.TryGetValue(possibleActionKeyword, out var pluginPair) && !pluginPair.Metadata.Disabled) foreach (string pluginActionKeyword in nonGlobalPlugins.Keys)
{ // use non global plugin for query {
actionKeyword = possibleActionKeyword; if (possibleActionKeyword.StartsWith(pluginActionKeyword))
actionParameters = terms.Skip(1).ToList(); {
search = actionParameters.Count > 0 ? rawQuery.Substring(actionKeyword.Length + 1) : string.Empty; if (nonGlobalPlugins.TryGetValue(pluginActionKeyword, out var pluginPair) && !pluginPair.Metadata.Disabled)
{
// The search string is the raw query excluding the action keyword
string search = rawQuery.Substring(pluginActionKeyword.Length).Trim();
// To set the terms of the query after removing the action keyword
if (possibleActionKeyword.Length > pluginActionKeyword.Length)
{
// If the first term contains the action keyword, then set the remaining string to be the first term
terms[0] = possibleActionKeyword.Substring(pluginActionKeyword.Length);
} }
else else
{ // non action keyword {
actionKeyword = string.Empty; // If the first term is the action keyword, then skip it.
actionParameters = terms.ToList(); terms = terms.Skip(1).ToArray();
search = rawQuery;
} }
var query = new Query // A new query is constructed for each plugin as they have different action keywords
{ var query = new Query(rawQuery, search, terms, pluginActionKeyword);
Terms = terms,
RawQuery = rawQuery,
ActionKeyword = actionKeyword,
Search = search,
};
return query; pluginQueryPair.TryAdd(pluginPair, query);
}
}
}
var globalplugins = PluginManager.GlobalPlugins;
foreach (PluginPair globalPlugin in PluginManager.GlobalPlugins)
{
var query = new Query(rawQuery, rawQuery, terms, string.Empty);
pluginQueryPair.Add(globalPlugin, query);
}
return pluginQueryPair;
} }
} }
} }

View File

@ -11,45 +11,191 @@ namespace Wox.Test
{ {
public class QueryBuilderTest public class QueryBuilderTest
{ {
[Test] private bool AreEqual(Query firstQuery, Query secondQuery)
public void ExclusivePluginQueryTest()
{ {
return firstQuery.ActionKeyword.Equals(secondQuery.ActionKeyword)
&& firstQuery.Search.Equals(secondQuery.Search)
&& firstQuery.RawQuery.Equals(secondQuery.RawQuery);
}
[Test]
public void QueryBuilder_ShouldRemoveExtraSpaces_ForNonGlobalPlugin()
{
// Arrange
var nonGlobalPlugins = new Dictionary<string, PluginPair> var nonGlobalPlugins = new Dictionary<string, PluginPair>
{ {
{ ">", new PluginPair { Metadata = new PluginMetadata { ActionKeywords = new List<string> { ">" } } } }, { ">", new PluginPair { Metadata = new PluginMetadata { ActionKeywords = new List<string> { ">" } } } },
}; };
string searchQuery = "> file.txt file2 file3";
Query q = QueryBuilder.Build("> file.txt file2 file3", nonGlobalPlugins); // Act
var pluginQueryPairs = QueryBuilder.Build(ref searchQuery, nonGlobalPlugins);
Assert.AreEqual("file.txt file2 file3", q.Search); // Assert
Assert.AreEqual(">", q.ActionKeyword); Assert.AreEqual("> file.txt file2 file3", searchQuery);
} }
[Test] [Test]
public void ExclusivePluginQueryIgnoreDisabledTest() public void QueryBuilder_ShouldRemoveExtraSpaces_ForDisabledNonGlobalPlugin()
{ {
// Arrange
var nonGlobalPlugins = new Dictionary<string, PluginPair> var nonGlobalPlugins = new Dictionary<string, PluginPair>
{ {
{ ">", new PluginPair { Metadata = new PluginMetadata { ActionKeywords = new List<string> { ">" }, Disabled = true } } }, { ">", new PluginPair { Metadata = new PluginMetadata { ActionKeywords = new List<string> { ">" }, Disabled = true } } },
}; };
string searchQuery = "> file.txt file2 file3";
Query q = QueryBuilder.Build("> file.txt file2 file3", nonGlobalPlugins); // Act
var pluginQueryPairs = QueryBuilder.Build(ref searchQuery, nonGlobalPlugins);
Assert.AreEqual("> file.txt file2 file3", q.Search); // Assert
Assert.AreEqual("> file.txt file2 file3", searchQuery);
} }
[Test] [Test]
public void GenericPluginQueryTest() public void QueryBuilder_ShouldRemoveExtraSpaces_ForGlobalPlugin()
{ {
Query q = QueryBuilder.Build("file.txt file2 file3", new Dictionary<string, PluginPair>()); // Arrange
string searchQuery = "file.txt file2 file3";
Assert.AreEqual("file.txt file2 file3", q.Search); // Act
Assert.AreEqual(string.Empty, q.ActionKeyword); var pluginQueryPairs = QueryBuilder.Build(ref searchQuery, new Dictionary<string, PluginPair>());
Assert.AreEqual("file.txt", q.FirstSearch); // Assert
Assert.AreEqual("file2", q.SecondSearch); Assert.AreEqual("file.txt file2 file3", searchQuery);
Assert.AreEqual("file3", q.ThirdSearch); }
Assert.AreEqual("file2 file3", q.SecondToEndSearch);
[Test]
public void QueryBuilder_ShouldGenerateSameQuery_IfEitherActionKeywordOrActionKeywordsListIsSet()
{
// Arrange
string searchQuery = "> query";
var firstPlugin = new PluginPair { Metadata = new PluginMetadata { ActionKeywords = new List<string> { ">" } } };
var secondPlugin = new PluginPair { Metadata = new PluginMetadata { ActionKeyword = ">" } };
var nonGlobalPluginWithActionKeywords = new Dictionary<string, PluginPair>
{
{ ">", firstPlugin },
};
var nonGlobalPluginWithActionKeyword = new Dictionary<string, PluginPair>
{
{ ">", secondPlugin },
};
string[] terms = { ">", "query" };
Query expectedQuery = new Query("> query", "query", terms, ">");
// Act
var queriesForPluginsWithActionKeywords = QueryBuilder.Build(ref searchQuery, nonGlobalPluginWithActionKeywords);
var queriesForPluginsWithActionKeyword = QueryBuilder.Build(ref searchQuery, nonGlobalPluginWithActionKeyword);
var firstQuery = queriesForPluginsWithActionKeyword.GetValueOrDefault(firstPlugin);
var secondQuery = queriesForPluginsWithActionKeywords.GetValueOrDefault(secondPlugin);
// Assert
Assert.IsTrue(AreEqual(firstQuery, expectedQuery));
Assert.IsTrue(AreEqual(firstQuery, secondQuery));
}
[Test]
public void QueryBuilder_ShouldGenerateCorrectQueries_ForPluginsWithMultipleActionKeywords()
{
// Arrange
var plugin = new PluginPair { Metadata = new PluginMetadata { ActionKeywords = new List<string> { "a", "b" } } };
var nonGlobalPlugins = new Dictionary<string, PluginPair>
{
{ "a", plugin },
{ "b", plugin },
};
var firstQueryText = "asearch";
var secondQueryText = "bsearch";
// Act
var firstPluginQueryPair = QueryBuilder.Build(ref firstQueryText, nonGlobalPlugins);
var firstQuery = firstPluginQueryPair.GetValueOrDefault(plugin);
var secondPluginQueryPairs = QueryBuilder.Build(ref secondQueryText, nonGlobalPlugins);
var secondQuery = secondPluginQueryPairs.GetValueOrDefault(plugin);
// Assert
Assert.IsTrue(AreEqual(firstQuery, new Query { ActionKeyword = "a", RawQuery = "asearch", Search = "search" }));
Assert.IsTrue(AreEqual(secondQuery, new Query { ActionKeyword = "b", RawQuery = "bsearch", Search = "search" }));
}
[Test]
public void QueryBuild_ShouldGenerateSameSearchQuery_WithOrWithoutSpaceAfterActionKeyword()
{
// Arrange
var plugin = new PluginPair { Metadata = new PluginMetadata { ActionKeywords = new List<string> { "a" } } };
var nonGlobalPlugins = new Dictionary<string, PluginPair>
{
{ "a", plugin },
};
var firstQueryText = "asearch";
var secondQueryText = "a search";
// Act
var firstPluginQueryPair = QueryBuilder.Build(ref firstQueryText, nonGlobalPlugins);
var firstQuery = firstPluginQueryPair.GetValueOrDefault(plugin);
var secondPluginQueryPairs = QueryBuilder.Build(ref secondQueryText, nonGlobalPlugins);
var secondQuery = secondPluginQueryPairs.GetValueOrDefault(plugin);
// Assert
Assert.IsTrue(firstQuery.Search.Equals(secondQuery.Search));
Assert.IsTrue(firstQuery.ActionKeyword.Equals(secondQuery.ActionKeyword));
}
[Test]
public void QueryBuild_ShouldGenerateCorrectQuery_ForPluginsWhoseActionKeywordsHaveSamePrefix()
{
// Arrange
string searchQuery = "abcdefgh";
var firstPlugin = new PluginPair { Metadata = new PluginMetadata { ActionKeyword = "ab", ID = "plugin1" } };
var secondPlugin = new PluginPair { Metadata = new PluginMetadata { ActionKeyword = "abcd", ID = "plugin2" } };
var nonGlobalPlugins = new Dictionary<string, PluginPair>
{
{ "ab", firstPlugin },
{ "abcd", secondPlugin },
};
// Act
var pluginQueryPairs = QueryBuilder.Build(ref searchQuery, nonGlobalPlugins);
var firstQuery = pluginQueryPairs.GetValueOrDefault(firstPlugin);
var secondQuery = pluginQueryPairs.GetValueOrDefault(secondPlugin);
// Assert
Assert.IsTrue(AreEqual(firstQuery, new Query { RawQuery = searchQuery, Search = searchQuery.Substring(firstPlugin.Metadata.ActionKeyword.Length), ActionKeyword = firstPlugin.Metadata.ActionKeyword } ));
Assert.IsTrue(AreEqual(secondQuery, new Query { RawQuery = searchQuery, Search = searchQuery.Substring(secondPlugin.Metadata.ActionKeyword.Length), ActionKeyword = secondPlugin.Metadata.ActionKeyword }));
}
[Test]
public void QueryBuilder_ShouldSetTermsCorrently_WhenCalled()
{
// Arrange
string searchQuery = "abcd efgh";
var firstPlugin = new PluginPair { Metadata = new PluginMetadata { ActionKeyword = "ab", ID = "plugin1" } };
var secondPlugin = new PluginPair { Metadata = new PluginMetadata { ActionKeyword = "abcd", ID = "plugin2" } };
var nonGlobalPlugins = new Dictionary<string, PluginPair>
{
{ "ab", firstPlugin },
{ "abcd", secondPlugin },
};
// Act
var pluginQueryPairs = QueryBuilder.Build(ref searchQuery, nonGlobalPlugins);
var firstQuery = pluginQueryPairs.GetValueOrDefault(firstPlugin);
var secondQuery = pluginQueryPairs.GetValueOrDefault(secondPlugin);
// Assert
Assert.IsTrue(firstQuery.Terms[0].Equals("cd") && firstQuery.Terms[1].Equals("efgh") && firstQuery.Terms.Length == 2);
Assert.IsTrue(secondQuery.Terms[0].Equals("efgh") && secondQuery.Terms.Length == 1);
} }
} }
} }