forked from microsoft/PowerToys
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSearchController.cs
More file actions
183 lines (166 loc) · 8.33 KB
/
Copy pathSearchController.cs
File metadata and controls
183 lines (166 loc) · 8.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using Microsoft.PowerToys.Run.Plugin.TimeDate.Properties;
using Wox.Infrastructure;
using Wox.Plugin;
namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components
{
/// <summary>
/// SearchController: Class tot hold the search method that filter available date time formats
/// Extra class to simplify code in <see cref="Main"/> class
/// </summary>
internal static class SearchController
{
/// <summary>
/// Var that holds the delimiter between format and date
/// </summary>
private const string InputDelimiter = "::";
/// <summary>
/// A list of conjunctions that we ignore on search
/// </summary>
private static readonly string[] _conjunctionList = Resources.Microsoft_plugin_timedate_Search_ConjunctionList.Split("; ");
/// <summary>
/// Searches for results
/// </summary>
/// <param name="query">Search query object</param>
/// <returns>List of Wox <see cref="Result"/>s.</returns>
internal static List<Result> ExecuteSearch(Query query, string iconTheme)
{
List<AvailableResult> availableFormats = new List<AvailableResult>();
List<Result> results = new List<Result>();
bool isKeywordSearch = !string.IsNullOrEmpty(query.ActionKeyword);
bool isEmptySearchInput = string.IsNullOrWhiteSpace(query.Search);
string searchTerm = query.Search;
// Last input parsing error
string lastInputParsingErrorReason = string.Empty;
// Conjunction search without keyword => return no results
// (This improves the results on global queries.)
if (!isKeywordSearch && _conjunctionList.Any(x => x.Equals(searchTerm, StringComparison.CurrentCultureIgnoreCase)))
{
return results;
}
// Switch search type
if (isEmptySearchInput || (!isKeywordSearch && TimeDateSettings.Instance.OnlyDateTimeNowGlobal))
{
// Return all results for system time/date on empty keyword search
// or only time, date and now results for system time on global queries if the corresponding setting is enabled
availableFormats.AddRange(AvailableResultsList.GetList(isKeywordSearch));
}
else if (Regex.IsMatch(searchTerm, @".+" + Regex.Escape(InputDelimiter) + @".+"))
{
// Search for specified format with specified time/date value
var userInput = searchTerm.Split(InputDelimiter);
if (TimeAndDateHelper.ParseStringAsDateTime(userInput[1], out DateTime timestamp, out lastInputParsingErrorReason))
{
availableFormats.AddRange(AvailableResultsList.GetList(isKeywordSearch, null, null, timestamp));
searchTerm = userInput[0];
}
}
else if (TimeAndDateHelper.ParseStringAsDateTime(searchTerm, out DateTime timestamp, out lastInputParsingErrorReason))
{
// Return all formats for specified time/date value
availableFormats.AddRange(AvailableResultsList.GetList(isKeywordSearch, null, null, timestamp));
searchTerm = string.Empty;
}
else
{
// Search for specified format with system time/date (All other cases)
availableFormats.AddRange(AvailableResultsList.GetList(isKeywordSearch));
}
// Check searchTerm after getting results to select type of result list
if (string.IsNullOrEmpty(searchTerm))
{
// Generate list with all results
foreach (var f in availableFormats)
{
results.Add(new Result
{
Title = f.Value,
SubTitle = $"{f.Label} - {Resources.Microsoft_plugin_timedate_SubTitleNote}",
ToolTipData = ResultHelper.GetSearchTagToolTip(f, out Visibility v),
ToolTipVisibility = v,
IcoPath = f.GetIconPath(iconTheme),
Action = _ => ResultHelper.CopyToClipBoard(f.Value),
ContextData = f,
});
}
}
else
{
// Generate filtered list of results
foreach (var f in availableFormats)
{
var resultMatchScore = GetMatchScore(searchTerm, f.Label, f.AlternativeSearchTag, !isKeywordSearch);
if (resultMatchScore > 0)
{
results.Add(new Result
{
Title = f.Value,
SubTitle = $"{f.Label} - {Resources.Microsoft_plugin_timedate_SubTitleNote}",
ToolTipData = ResultHelper.GetSearchTagToolTip(f, out Visibility v),
ToolTipVisibility = v,
IcoPath = f.GetIconPath(iconTheme),
Action = _ => ResultHelper.CopyToClipBoard(f.Value),
Score = resultMatchScore,
ContextData = f,
});
}
}
}
// If search term is only a number that can't be parsed return an error message
if (!isEmptySearchInput && results.Count == 0 && Regex.IsMatch(searchTerm, @"\w+[+-]?\d+.*$") && !searchTerm.Any(char.IsWhiteSpace) && (TimeAndDateHelper.IsSpecialInputParsing(searchTerm) || !Regex.IsMatch(searchTerm, @"\d+[\.:/]\d+")))
{
string title = !string.IsNullOrEmpty(lastInputParsingErrorReason) ? Resources.Microsoft_plugin_timedate_ErrorResultValue : Resources.Microsoft_plugin_timedate_ErrorResultTitle;
string message = !string.IsNullOrEmpty(lastInputParsingErrorReason) ? lastInputParsingErrorReason : Resources.Microsoft_plugin_timedate_ErrorResultSubTitle;
// Without plugin key word show only if not hidden by setting
if (isKeywordSearch || !TimeDateSettings.Instance.HideNumberMessageOnGlobalQuery)
{
results.Add(ResultHelper.CreateNumberErrorResult(iconTheme, title, message));
}
}
return results;
}
/// <summary>
/// Checks the format for a match with the user query and returns the score.
/// </summary>
/// <param name="query">The user query.</param>
/// <param name="label">The label of the format.</param>
/// <param name="tags">The search tag list as string.</param>
/// <param name="isGlobalSearch">Is this a global search?</param>
/// <returns>The score for the result.</returns>
private static int GetMatchScore(string query, string label, string tags, bool isGlobalSearch)
{
// The query is global and the first word don't match any word in the label or tags => Return score of zero
if (isGlobalSearch)
{
char[] chars = new char[] { ' ', ',', ';', '(', ')' };
string queryFirstWord = query.Split(chars)[0];
string[] words = $"{label} {tags}".Split(chars);
if (!words.Any(x => x.Trim().Equals(queryFirstWord, StringComparison.CurrentCultureIgnoreCase)))
{
return 0;
}
}
// Get match for label (or for tags if label score is <1)
int score = StringMatcher.FuzzySearch(query, label).Score;
if (score < 1)
{
foreach (string t in tags.Split(";"))
{
var tagScore = StringMatcher.FuzzySearch(query, t.Trim()).Score / 2;
if (tagScore > score)
{
score = tagScore / 2;
}
}
}
return score;
}
}
}