mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge remote-tracking branch 'origin/dev' into add_python_installation
This commit is contained in:
commit
39229e591c
23 changed files with 451 additions and 218 deletions
|
|
@ -16,13 +16,7 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
{
|
||||
private const string UserAgent = @"Mozilla/5.0 (Trident/7.0; rv:11.0) like Gecko";
|
||||
|
||||
private static HttpClient client;
|
||||
|
||||
private static SocketsHttpHandler socketsHttpHandler = new SocketsHttpHandler()
|
||||
{
|
||||
UseProxy = true,
|
||||
Proxy = WebProxy
|
||||
};
|
||||
private static HttpClient client = new HttpClient();
|
||||
|
||||
static Http()
|
||||
{
|
||||
|
|
@ -32,8 +26,8 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
| SecurityProtocolType.Tls11
|
||||
| SecurityProtocolType.Tls12;
|
||||
|
||||
client = new HttpClient(socketsHttpHandler, false);
|
||||
client.DefaultRequestHeaders.Add("User-Agent", UserAgent);
|
||||
HttpClient.DefaultProxy = WebProxy;
|
||||
}
|
||||
|
||||
private static HttpProxy proxy;
|
||||
|
|
@ -45,6 +39,7 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
{
|
||||
proxy = value;
|
||||
proxy.PropertyChanged += UpdateProxy;
|
||||
UpdateProxy(ProxyProperty.Enabled);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
|
|
@ -8,14 +9,109 @@ using ToolGood.Words.Pinyin;
|
|||
|
||||
namespace Flow.Launcher.Infrastructure
|
||||
{
|
||||
public class TranslationMapping
|
||||
{
|
||||
private bool constructed;
|
||||
|
||||
private List<int> originalIndexs = new List<int>();
|
||||
private List<int> translatedIndexs = new List<int>();
|
||||
private int translaedLength = 0;
|
||||
|
||||
public string key { get; private set; }
|
||||
|
||||
public void setKey(string key)
|
||||
{
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public void AddNewIndex(int originalIndex, int translatedIndex, int length)
|
||||
{
|
||||
if (constructed)
|
||||
throw new InvalidOperationException("Mapping shouldn't be changed after constructed");
|
||||
|
||||
originalIndexs.Add(originalIndex);
|
||||
translatedIndexs.Add(translatedIndex);
|
||||
translatedIndexs.Add(translatedIndex + length);
|
||||
translaedLength += length - 1;
|
||||
}
|
||||
|
||||
public int MapToOriginalIndex(int translatedIndex)
|
||||
{
|
||||
if (translatedIndex > translatedIndexs.Last())
|
||||
return translatedIndex - translaedLength - 1;
|
||||
|
||||
int lowerBound = 0;
|
||||
int upperBound = originalIndexs.Count - 1;
|
||||
|
||||
int count = 0;
|
||||
|
||||
// Corner case handle
|
||||
if (translatedIndex < translatedIndexs[0])
|
||||
return translatedIndex;
|
||||
if (translatedIndex > translatedIndexs.Last())
|
||||
{
|
||||
int indexDef = 0;
|
||||
for (int k = 0; k < originalIndexs.Count; k++)
|
||||
{
|
||||
indexDef += translatedIndexs[k * 2 + 1] - translatedIndexs[k * 2];
|
||||
}
|
||||
|
||||
return translatedIndex - indexDef - 1;
|
||||
}
|
||||
|
||||
// Binary Search with Range
|
||||
for (int i = originalIndexs.Count / 2;; count++)
|
||||
{
|
||||
if (translatedIndex < translatedIndexs[i * 2])
|
||||
{
|
||||
// move to lower middle
|
||||
upperBound = i;
|
||||
i = (i + lowerBound) / 2;
|
||||
}
|
||||
else if (translatedIndex > translatedIndexs[i * 2 + 1] - 1)
|
||||
{
|
||||
lowerBound = i;
|
||||
// move to upper middle
|
||||
// due to floor of integer division, move one up on corner case
|
||||
i = (i + upperBound + 1) / 2;
|
||||
}
|
||||
else
|
||||
return originalIndexs[i];
|
||||
|
||||
if (upperBound - lowerBound <= 1 &&
|
||||
translatedIndex > translatedIndexs[lowerBound * 2 + 1] &&
|
||||
translatedIndex < translatedIndexs[upperBound * 2])
|
||||
{
|
||||
int indexDef = 0;
|
||||
|
||||
for (int j = 0; j < upperBound; j++)
|
||||
{
|
||||
indexDef += translatedIndexs[j * 2 + 1] - translatedIndexs[j * 2];
|
||||
}
|
||||
|
||||
return translatedIndex - indexDef - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void endConstruct()
|
||||
{
|
||||
if (constructed)
|
||||
throw new InvalidOperationException("Mapping has already been constructed");
|
||||
constructed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IAlphabet
|
||||
{
|
||||
string Translate(string stringToTranslate);
|
||||
public (string translation, TranslationMapping map) Translate(string stringToTranslate);
|
||||
}
|
||||
|
||||
public class PinyinAlphabet : IAlphabet
|
||||
{
|
||||
private ConcurrentDictionary<string, string> _pinyinCache = new ConcurrentDictionary<string, string>();
|
||||
private ConcurrentDictionary<string, (string translation, TranslationMapping map)> _pinyinCache =
|
||||
new ConcurrentDictionary<string, (string translation, TranslationMapping map)>();
|
||||
|
||||
private Settings _settings;
|
||||
|
||||
public void Initialize([NotNull] Settings settings)
|
||||
|
|
@ -23,7 +119,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
public string Translate(string content)
|
||||
public (string translation, TranslationMapping map) Translate(string content)
|
||||
{
|
||||
if (_settings.ShouldUsePinyin)
|
||||
{
|
||||
|
|
@ -34,14 +130,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
var resultList = WordsHelper.GetPinyinList(content);
|
||||
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < resultList.Length; i++)
|
||||
{
|
||||
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
|
||||
resultBuilder.Append(resultList[i].First());
|
||||
}
|
||||
|
||||
resultBuilder.Append(' ');
|
||||
TranslationMapping map = new TranslationMapping();
|
||||
|
||||
bool pre = false;
|
||||
|
||||
|
|
@ -49,6 +138,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
{
|
||||
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
|
||||
{
|
||||
map.AddNewIndex(i, resultBuilder.Length, resultList[i].Length + 1);
|
||||
resultBuilder.Append(' ');
|
||||
resultBuilder.Append(resultList[i]);
|
||||
pre = true;
|
||||
|
|
@ -60,15 +150,21 @@ namespace Flow.Launcher.Infrastructure
|
|||
pre = false;
|
||||
resultBuilder.Append(' ');
|
||||
}
|
||||
|
||||
resultBuilder.Append(resultList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return _pinyinCache[content] = resultBuilder.ToString();
|
||||
map.endConstruct();
|
||||
|
||||
var key = resultBuilder.ToString();
|
||||
map.setKey(key);
|
||||
|
||||
return _pinyinCache[content] = (key, map);
|
||||
}
|
||||
else
|
||||
{
|
||||
return content;
|
||||
return (content, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -78,7 +174,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
}
|
||||
else
|
||||
{
|
||||
return content;
|
||||
return (content, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,20 @@ namespace Flow.Launcher.Infrastructure
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current method:
|
||||
/// Current method has two parts, Acronym Match and Fuzzy Search:
|
||||
///
|
||||
/// Acronym Match:
|
||||
/// Charater listed below will be considered as acronym
|
||||
/// 1. Character on index 0
|
||||
/// 2. Character appears after a space
|
||||
/// 3. Character that is UpperCase
|
||||
/// 4. Character that is number
|
||||
///
|
||||
/// Acronym Match will succeed when all query characters match with acronyms in stringToCompare.
|
||||
/// If any of the characters in the query isn't matched with stringToCompare, Acronym Match will fail.
|
||||
/// Score will be calculated based the percentage of all query characters matched with total acronyms in stringToCompare.
|
||||
///
|
||||
/// Fuzzy Search:
|
||||
/// Character matching + substring matching;
|
||||
/// 1. Query search string is split into substrings, separator is whitespace.
|
||||
/// 2. Check each query substring's characters against full compare string,
|
||||
|
|
@ -43,20 +56,21 @@ namespace Flow.Launcher.Infrastructure
|
|||
/// </summary>
|
||||
public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption opt)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) return new MatchResult (false, UserSettingSearchPrecision);
|
||||
|
||||
query = query.Trim();
|
||||
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query))
|
||||
return new MatchResult(false, UserSettingSearchPrecision);
|
||||
|
||||
if (_alphabet != null)
|
||||
{
|
||||
query = _alphabet.Translate(query);
|
||||
stringToCompare = _alphabet.Translate(stringToCompare);
|
||||
}
|
||||
query = query.Trim();
|
||||
TranslationMapping translationMapping;
|
||||
(stringToCompare, translationMapping) = _alphabet?.Translate(stringToCompare) ?? (stringToCompare, null);
|
||||
|
||||
var currentAcronymQueryIndex = 0;
|
||||
var acronymMatchData = new List<int>();
|
||||
int acronymsTotalCount = 0;
|
||||
int acronymsMatched = 0;
|
||||
|
||||
var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare;
|
||||
|
||||
var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query;
|
||||
|
||||
|
||||
var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
int currentQuerySubstringIndex = 0;
|
||||
var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
|
||||
|
|
@ -74,17 +88,44 @@ namespace Flow.Launcher.Infrastructure
|
|||
|
||||
for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++)
|
||||
{
|
||||
// If acronyms matching successfully finished, this gets the remaining not matched acronyms for score calculation
|
||||
if (currentAcronymQueryIndex >= query.Length && acronymsMatched == query.Length)
|
||||
{
|
||||
if (IsAcronymCount(stringToCompare, compareStringIndex))
|
||||
acronymsTotalCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentAcronymQueryIndex >= query.Length ||
|
||||
currentAcronymQueryIndex >= query.Length && allQuerySubstringsMatched)
|
||||
break;
|
||||
|
||||
// To maintain a list of indices which correspond to spaces in the string to compare
|
||||
// To populate the list only for the first query substring
|
||||
if (fullStringToCompareWithoutCase[compareStringIndex].Equals(' ') && currentQuerySubstringIndex == 0)
|
||||
{
|
||||
if (fullStringToCompareWithoutCase[compareStringIndex] == ' ' && currentQuerySubstringIndex == 0)
|
||||
spaceIndices.Add(compareStringIndex);
|
||||
|
||||
// Acronym Match
|
||||
if (IsAcronym(stringToCompare, compareStringIndex))
|
||||
{
|
||||
if (fullStringToCompareWithoutCase[compareStringIndex] ==
|
||||
queryWithoutCase[currentAcronymQueryIndex])
|
||||
{
|
||||
acronymMatchData.Add(compareStringIndex);
|
||||
acronymsMatched++;
|
||||
|
||||
currentAcronymQueryIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex])
|
||||
if (IsAcronymCount(stringToCompare, compareStringIndex))
|
||||
acronymsTotalCount++;
|
||||
|
||||
if (allQuerySubstringsMatched || fullStringToCompareWithoutCase[compareStringIndex] !=
|
||||
currentQuerySubstring[currentQuerySubstringCharacterIndex])
|
||||
{
|
||||
matchFoundInPreviousLoop = false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -106,14 +147,16 @@ namespace Flow.Launcher.Infrastructure
|
|||
// in order to do so we need to verify all previous chars are part of the pattern
|
||||
var startIndexToVerify = compareStringIndex - currentQuerySubstringCharacterIndex;
|
||||
|
||||
if (AllPreviousCharsMatched(startIndexToVerify, currentQuerySubstringCharacterIndex, fullStringToCompareWithoutCase, currentQuerySubstring))
|
||||
if (AllPreviousCharsMatched(startIndexToVerify, currentQuerySubstringCharacterIndex,
|
||||
fullStringToCompareWithoutCase, currentQuerySubstring))
|
||||
{
|
||||
matchFoundInPreviousLoop = true;
|
||||
|
||||
// if it's the beginning character of the first query substring that is matched then we need to update start index
|
||||
firstMatchIndex = currentQuerySubstringIndex == 0 ? startIndexToVerify : firstMatchIndex;
|
||||
|
||||
indexList = GetUpdatedIndexList(startIndexToVerify, currentQuerySubstringCharacterIndex, firstMatchIndexInWord, indexList);
|
||||
indexList = GetUpdatedIndexList(startIndexToVerify, currentQuerySubstringCharacterIndex,
|
||||
firstMatchIndexInWord, indexList);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -126,49 +169,96 @@ namespace Flow.Launcher.Infrastructure
|
|||
if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length)
|
||||
{
|
||||
// if any of the substrings was not matched then consider as all are not matched
|
||||
allSubstringsContainedInCompareString = matchFoundInPreviousLoop && allSubstringsContainedInCompareString;
|
||||
allSubstringsContainedInCompareString =
|
||||
matchFoundInPreviousLoop && allSubstringsContainedInCompareString;
|
||||
|
||||
currentQuerySubstringIndex++;
|
||||
|
||||
allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length);
|
||||
allQuerySubstringsMatched =
|
||||
AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length);
|
||||
|
||||
if (allQuerySubstringsMatched)
|
||||
break;
|
||||
continue;
|
||||
|
||||
// otherwise move to the next query substring
|
||||
currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
|
||||
currentQuerySubstringCharacterIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// return acronym match if all query char matched
|
||||
if (acronymsMatched > 0 && acronymsMatched == query.Length)
|
||||
{
|
||||
int acronymScore = acronymsMatched * 100 / acronymsTotalCount;
|
||||
|
||||
if (acronymScore >= (int)UserSettingSearchPrecision)
|
||||
{
|
||||
acronymMatchData = acronymMatchData.Select(x => translationMapping?.MapToOriginalIndex(x) ?? x).Distinct().ToList();
|
||||
return new MatchResult(true, UserSettingSearchPrecision, acronymMatchData, acronymScore);
|
||||
}
|
||||
}
|
||||
|
||||
// proceed to calculate score if every char or substring without whitespaces matched
|
||||
if (allQuerySubstringsMatched)
|
||||
{
|
||||
var nearestSpaceIndex = CalculateClosestSpaceIndex(spaceIndices, firstMatchIndex);
|
||||
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
|
||||
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1,
|
||||
lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
|
||||
|
||||
return new MatchResult(true, UserSettingSearchPrecision, indexList, score);
|
||||
var resultList = indexList.Select(x => translationMapping?.MapToOriginalIndex(x) ?? x).Distinct().ToList();
|
||||
return new MatchResult(true, UserSettingSearchPrecision, resultList, score);
|
||||
}
|
||||
|
||||
return new MatchResult(false, UserSettingSearchPrecision);
|
||||
}
|
||||
|
||||
private bool IsAcronym(string stringToCompare, int compareStringIndex)
|
||||
{
|
||||
if (IsAcronymChar(stringToCompare, compareStringIndex) || IsAcronymNumber(stringToCompare, compareStringIndex))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// When counting acronyms, treat a set of numbers as one acronym ie. Visual 2019 as 2 acronyms instead of 5
|
||||
private bool IsAcronymCount(string stringToCompare, int compareStringIndex)
|
||||
{
|
||||
if (IsAcronymChar(stringToCompare, compareStringIndex))
|
||||
return true;
|
||||
|
||||
if (IsAcronymNumber(stringToCompare, compareStringIndex))
|
||||
return compareStringIndex == 0 || char.IsWhiteSpace(stringToCompare[compareStringIndex - 1]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsAcronymChar(string stringToCompare, int compareStringIndex)
|
||||
=> char.IsUpper(stringToCompare[compareStringIndex]) ||
|
||||
compareStringIndex == 0 || // 0 index means char is the start of the compare string, which is an acronym
|
||||
char.IsWhiteSpace(stringToCompare[compareStringIndex - 1]);
|
||||
|
||||
private bool IsAcronymNumber(string stringToCompare, int compareStringIndex)
|
||||
=> stringToCompare[compareStringIndex] >= 0 && stringToCompare[compareStringIndex] <= 9;
|
||||
|
||||
// To get the index of the closest space which preceeds the first matching index
|
||||
private int CalculateClosestSpaceIndex(List<int> spaceIndices, int firstMatchIndex)
|
||||
{
|
||||
if (spaceIndices.Count == 0)
|
||||
var closestSpaceIndex = -1;
|
||||
|
||||
// spaceIndices should be ordered asc
|
||||
foreach (var index in spaceIndices)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
int? ind = spaceIndices.OrderBy(item => (firstMatchIndex - item)).Where(item => firstMatchIndex > item).FirstOrDefault();
|
||||
int closestSpaceIndex = ind ?? -1;
|
||||
return closestSpaceIndex;
|
||||
if (index < firstMatchIndex)
|
||||
closestSpaceIndex = index;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return closestSpaceIndex;
|
||||
}
|
||||
|
||||
private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex,
|
||||
string fullStringToCompareWithoutCase, string currentQuerySubstring)
|
||||
string fullStringToCompareWithoutCase, string currentQuerySubstring)
|
||||
{
|
||||
var allMatch = true;
|
||||
for (int indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++)
|
||||
|
|
@ -182,8 +272,9 @@ namespace Flow.Launcher.Infrastructure
|
|||
|
||||
return allMatch;
|
||||
}
|
||||
|
||||
private static List<int> GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, int firstMatchIndexInWord, List<int> indexList)
|
||||
|
||||
private static List<int> GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex,
|
||||
int firstMatchIndexInWord, List<int> indexList)
|
||||
{
|
||||
var updatedList = new List<int>();
|
||||
|
||||
|
|
@ -201,10 +292,12 @@ namespace Flow.Launcher.Infrastructure
|
|||
|
||||
private static bool AllQuerySubstringsMatched(int currentQuerySubstringIndex, int querySubstringsLength)
|
||||
{
|
||||
// Acronym won't utilize the substring to match
|
||||
return currentQuerySubstringIndex >= querySubstringsLength;
|
||||
}
|
||||
|
||||
private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen, bool allSubstringsContainedInCompareString)
|
||||
private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen,
|
||||
bool allSubstringsContainedInCompareString)
|
||||
{
|
||||
// A match found near the beginning of a string is scored more than a match found near the end
|
||||
// A match is scored more if the characters in the patterns are closer to each other,
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
|
||||
public static void OpenPath(string fileOrFolderPath)
|
||||
{
|
||||
var psi = new ProcessStartInfo { FileName = FileExplorerProgramName, UseShellExecute = true, Arguments = fileOrFolderPath };
|
||||
var psi = new ProcessStartInfo { FileName = FileExplorerProgramName, UseShellExecute = true, Arguments = '"' + fileOrFolderPath + '"' };
|
||||
try
|
||||
{
|
||||
if (LocationExists(fileOrFolderPath) || FileExists(fileOrFolderPath))
|
||||
|
|
|
|||
|
|
@ -93,7 +93,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();
|
||||
|
|
@ -109,9 +110,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("###############################################");
|
||||
|
|
@ -120,6 +121,7 @@ namespace Flow.Launcher.Test
|
|||
{
|
||||
Debug.WriteLine("SCORE: " + item.Score.ToString() + ", FoundString: " + item.Title);
|
||||
}
|
||||
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine("");
|
||||
|
||||
|
|
@ -129,20 +131,21 @@ namespace Flow.Launcher.Test
|
|||
|
||||
[TestCase(Chrome, Chrome, 157)]
|
||||
[TestCase(Chrome, LastIsChrome, 147)]
|
||||
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 25)]
|
||||
[TestCase("chro", HelpCureHopeRaiseOnMindEntityChrome, 50)]
|
||||
[TestCase("chr", HelpCureHopeRaiseOnMindEntityChrome, 30)]
|
||||
[TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 21)]
|
||||
[TestCase(Chrome, CandyCrushSagaFromKing, 0)]
|
||||
[TestCase("sql", MicrosoftSqlServerManagementStudio, 110)]
|
||||
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, 121)]//double spacing intended
|
||||
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, 121)] //double spacing intended
|
||||
public void WhenGivenQueryString_ThenShouldReturn_TheDesiredScoring(
|
||||
string queryString, string compareString, int expectedScore)
|
||||
{
|
||||
// When, Given
|
||||
var matcher = new StringMatcher();
|
||||
var matcher = new StringMatcher {UserSettingSearchPrecision = SearchPrecisionScore.Regular};
|
||||
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}");
|
||||
}
|
||||
|
||||
|
|
@ -154,8 +157,17 @@ namespace Flow.Launcher.Test
|
|||
[TestCase("chr", "Candy Crush Saga from King", SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("chr", "Candy Crush Saga from King", SearchPrecisionScore.None, true)]
|
||||
[TestCase("ccs", "Candy Crush Saga from King", SearchPrecisionScore.Low, true)]
|
||||
[TestCase("cand", "Candy Crush Saga from King",SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("cand", "Candy Crush Saga from King", SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("cand", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("vsc", VisualStudioCode, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("vs", VisualStudioCode, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("vc", VisualStudioCode, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("vts", VisualStudioCode, SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("vcs", VisualStudioCode, SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("wt", "Windows Terminal From Microsoft Store", SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("vsp", "Visual Studio 2019 Preview", SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("vsp", "2019 Visual Studio Preview", SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("2019p", "Visual Studio 2019 Preview", SearchPrecisionScore.Regular, true)]
|
||||
public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual(
|
||||
string queryString,
|
||||
string compareString,
|
||||
|
|
@ -171,14 +183,15 @@ 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("");
|
||||
|
||||
// Should
|
||||
Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
|
||||
$"Query:{queryString}{Environment.NewLine} " +
|
||||
$"Compare:{compareString}{Environment.NewLine}" +
|
||||
$"Query: {queryString}{Environment.NewLine} " +
|
||||
$"Compare: {compareString}{Environment.NewLine}" +
|
||||
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
|
||||
$"Precision Score: {(int)expectedPrecisionScore}");
|
||||
}
|
||||
|
|
@ -196,8 +209,9 @@ namespace Flow.Launcher.Test
|
|||
[TestCase("sql serv man", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("sql studio", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("mic", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("mssms", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("msms", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("chr", "Shutdown", SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("mssms", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).", SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("a test", "This is a test", SearchPrecisionScore.Regular, true)]
|
||||
|
|
@ -212,7 +226,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);
|
||||
|
|
@ -220,7 +234,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("");
|
||||
|
||||
|
|
@ -239,7 +254,7 @@ namespace Flow.Launcher.Test
|
|||
string queryString, string compareString1, string compareString2)
|
||||
{
|
||||
// When
|
||||
var matcher = new StringMatcher { UserSettingSearchPrecision = SearchPrecisionScore.Regular };
|
||||
var matcher = new StringMatcher {UserSettingSearchPrecision = SearchPrecisionScore.Regular};
|
||||
|
||||
// Given
|
||||
var compareString1Result = matcher.FuzzyMatch(queryString, compareString1);
|
||||
|
|
@ -248,8 +263,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("");
|
||||
|
||||
|
|
@ -257,13 +274,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
|
||||
|
|
@ -276,15 +293,39 @@ 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", 66)]
|
||||
[TestCase("vs", "Visual Studio", 100)]
|
||||
[TestCase("vs", "Visual Studio Preview", 66)]
|
||||
[TestCase("vsp", "Visual Studio Preview", 100)]
|
||||
[TestCase("pc", "postman canary", 100)]
|
||||
[TestCase("psc", "Postman super canary", 100)]
|
||||
[TestCase("psc", "Postman super Canary", 100)]
|
||||
[TestCase("vsp", "Visual Studio", 0)]
|
||||
[TestCase("vps", "Visual Studio", 0)]
|
||||
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 75)]
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -317,11 +317,9 @@ namespace Flow.Launcher.Test.Plugins
|
|||
[TestCase("c:\\somefolder\\", "*")]
|
||||
public void GivenDirectoryInfoSearch_WhenSearchPatternHotKeyIsSearchAll_ThenSearchCriteriaShouldUseCriteriaString(string path, string expectedString)
|
||||
{
|
||||
// Given
|
||||
var criteriaConstructor = new DirectoryInfoSearch(new PluginInitContext());
|
||||
|
||||
//When
|
||||
var resultString = criteriaConstructor.ConstructSearchCriteria(path);
|
||||
var resultString = DirectoryInfoSearch.ConstructSearchCriteria(path);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(resultString == expectedString,
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ namespace Flow.Launcher
|
|||
_settingsVM = new SettingWindowViewModel(_updater, _portable);
|
||||
_settings = _settingsVM.Settings;
|
||||
|
||||
Http.Proxy = _settings.Proxy;
|
||||
|
||||
_alphabet.Initialize(_settings);
|
||||
_stringMatcher = new StringMatcher(_alphabet);
|
||||
StringMatcher.Instance = _stringMatcher;
|
||||
|
|
@ -85,8 +87,6 @@ namespace Flow.Launcher
|
|||
ThemeManager.Instance.Settings = _settings;
|
||||
ThemeManager.Instance.ChangeTheme(_settings.Theme);
|
||||
|
||||
Http.Proxy = _settings.Proxy;
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
RegisterExitEvents();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using NHotkey;
|
||||
|
|
@ -18,7 +19,6 @@ using Flow.Launcher.Plugin;
|
|||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.Storage;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
|
|
@ -45,6 +45,7 @@ namespace Flow.Launcher.ViewModel
|
|||
private bool _saved;
|
||||
|
||||
private readonly Internationalization _translator = InternationalizationManager.Instance;
|
||||
|
||||
private BufferBlock<ResultsForUpdate> _resultsUpdateQueue;
|
||||
private Task _resultsViewUpdateTask;
|
||||
|
||||
|
|
@ -74,30 +75,14 @@ namespace Flow.Launcher.ViewModel
|
|||
_selectedResults = Results;
|
||||
|
||||
InitializeKeyCommands();
|
||||
|
||||
RegisterViewUpdate();
|
||||
RegisterResultsUpdatedEvent();
|
||||
|
||||
|
||||
SetHotkey(_settings.Hotkey, OnHotkey);
|
||||
SetCustomPluginHotkey();
|
||||
SetOpenResultModifiers();
|
||||
}
|
||||
|
||||
private void RegisterResultsUpdatedEvent()
|
||||
{
|
||||
foreach (var pair in PluginManager.GetPluginsForInterface<IResultUpdated>())
|
||||
{
|
||||
var plugin = (IResultUpdated) pair.Plugin;
|
||||
plugin.ResultsUpdated += (s, e) =>
|
||||
{
|
||||
PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query);
|
||||
if (e.Query.Search == _lastQuery.Search)
|
||||
_resultsUpdateQueue.Post(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterViewUpdate()
|
||||
{
|
||||
_resultsUpdateQueue = new BufferBlock<ResultsForUpdate>();
|
||||
|
|
@ -136,6 +121,22 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private void RegisterResultsUpdatedEvent()
|
||||
{
|
||||
foreach (var pair in PluginManager.GetPluginsForInterface<IResultUpdated>())
|
||||
{
|
||||
var plugin = (IResultUpdated)pair.Plugin;
|
||||
plugin.ResultsUpdated += (s, e) =>
|
||||
{
|
||||
if (e.Query.RawQuery == QueryText) // TODO: allow cancellation
|
||||
{
|
||||
PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query);
|
||||
_resultsUpdateQueue.Post(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, _updateToken));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void InitializeKeyCommands()
|
||||
{
|
||||
|
|
@ -233,7 +234,6 @@ namespace Flow.Launcher.ViewModel
|
|||
public ResultsViewModel Results { get; private set; }
|
||||
public ResultsViewModel ContextMenu { get; private set; }
|
||||
public ResultsViewModel History { get; private set; }
|
||||
private string _lastQueryText;
|
||||
|
||||
private string _queryText;
|
||||
|
||||
|
|
@ -393,7 +393,7 @@ namespace Flow.Launcher.ViewModel
|
|||
Title = string.Format(title, h.Query),
|
||||
SubTitle = string.Format(time, h.ExecutedDateTime),
|
||||
IcoPath = "Images\\history.png",
|
||||
OriginQuery = new Query {RawQuery = h.Query},
|
||||
OriginQuery = new Query { RawQuery = h.Query },
|
||||
Action = _ =>
|
||||
{
|
||||
SelectedResults = Results;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
|
||||
contextMenu = new ContextMenu(Context, Settings, viewModel);
|
||||
searchManager = new SearchManager(Settings, Context);
|
||||
ResultManager.Init(Context);
|
||||
}
|
||||
|
||||
public List<Result> LoadContextMenus(Result selectedResult)
|
||||
|
|
|
|||
|
|
@ -8,16 +8,9 @@ using System.Threading;
|
|||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
||||
{
|
||||
public class DirectoryInfoSearch
|
||||
public static class DirectoryInfoSearch
|
||||
{
|
||||
private readonly ResultManager resultManager;
|
||||
|
||||
public DirectoryInfoSearch(PluginInitContext context)
|
||||
{
|
||||
resultManager = new ResultManager(context);
|
||||
}
|
||||
|
||||
internal List<Result> TopLevelDirectorySearch(Query query, string search, CancellationToken token)
|
||||
internal static List<Result> TopLevelDirectorySearch(Query query, string search, CancellationToken token)
|
||||
{
|
||||
var criteria = ConstructSearchCriteria(search);
|
||||
|
||||
|
|
@ -31,7 +24,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
return DirectorySearch(new EnumerationOptions(), query, search, criteria, token); // null will be passed as default
|
||||
}
|
||||
|
||||
public string ConstructSearchCriteria(string search)
|
||||
public static string ConstructSearchCriteria(string search)
|
||||
{
|
||||
string incompleteName = "";
|
||||
|
||||
|
|
@ -50,7 +43,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
return incompleteName;
|
||||
}
|
||||
|
||||
private List<Result> DirectorySearch(EnumerationOptions enumerationOption, Query query, string search,
|
||||
private static List<Result> DirectorySearch(EnumerationOptions enumerationOption, Query query, string search,
|
||||
string searchCriteria, CancellationToken token)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
|
|
@ -68,12 +61,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
{
|
||||
if (fileSystemInfo is System.IO.DirectoryInfo)
|
||||
{
|
||||
folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName,
|
||||
folderList.Add(ResultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName,
|
||||
fileSystemInfo.FullName, query, true, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
fileList.Add(resultManager.CreateFileResult(fileSystemInfo.FullName, query, true, false));
|
||||
fileList.Add(ResultManager.CreateFileResult(fileSystemInfo.FullName, query, true, false));
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
{
|
||||
var expandedPath = environmentVariables[search];
|
||||
|
||||
results.Add(new ResultManager(context).CreateFolderResult($"%{search}%", expandedPath, expandedPath, query));
|
||||
results.Add(ResultManager.CreateFolderResult($"%{search}%", expandedPath, expandedPath, query));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
|
@ -95,7 +95,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
{
|
||||
if (p.Key.StartsWith(search, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
results.Add(new ResultManager(context).CreateFolderResult($"%{p.Key}%", p.Value, p.Value, query));
|
||||
results.Add(ResultManager.CreateFolderResult($"%{p.Key}%", p.Value, p.Value, query));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,16 +4,9 @@ using System.Linq;
|
|||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks
|
||||
{
|
||||
public class QuickAccess
|
||||
internal static class QuickAccess
|
||||
{
|
||||
private readonly ResultManager resultManager;
|
||||
|
||||
public QuickAccess(PluginInitContext context)
|
||||
{
|
||||
resultManager = new ResultManager(context);
|
||||
}
|
||||
|
||||
internal List<Result> AccessLinkListMatched(Query query, List<AccessLink> accessLinks)
|
||||
internal static List<Result> AccessLinkListMatched(Query query, List<AccessLink> accessLinks)
|
||||
{
|
||||
if (string.IsNullOrEmpty(query.Search))
|
||||
return new List<Result>();
|
||||
|
|
@ -28,21 +21,21 @@ namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks
|
|||
|
||||
return queriedAccessLinks.Select(l => l.Type switch
|
||||
{
|
||||
ResultType.Folder => resultManager.CreateFolderResult(l.Nickname, l.Path, l.Path, query),
|
||||
ResultType.File => resultManager.CreateFileResult(l.Path, query),
|
||||
ResultType.Folder => ResultManager.CreateFolderResult(l.Nickname, l.Path, l.Path, query),
|
||||
ResultType.File => ResultManager.CreateFileResult(l.Path, query),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
internal List<Result> AccessLinkListAll(Query query, List<AccessLink> accessLinks)
|
||||
internal static List<Result> AccessLinkListAll(Query query, List<AccessLink> accessLinks)
|
||||
=> accessLinks
|
||||
.OrderBy(x => x.Type)
|
||||
.ThenBy(x => x.Nickname)
|
||||
.Select(l => l.Type switch
|
||||
{
|
||||
ResultType.Folder => resultManager.CreateFolderResult(l.Nickname, l.Path, l.Path, query),
|
||||
ResultType.File => resultManager.CreateFileResult(l.Path, query),
|
||||
ResultType.Folder => ResultManager.CreateFolderResult(l.Nickname, l.Path, l.Path, query),
|
||||
ResultType.File => ResultManager.CreateFileResult(l.Path, query),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
|
||||
}).ToList();
|
||||
|
|
|
|||
|
|
@ -4,19 +4,21 @@ using System;
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search
|
||||
{
|
||||
public class ResultManager
|
||||
public static class ResultManager
|
||||
{
|
||||
private readonly PluginInitContext context;
|
||||
private static PluginInitContext Context;
|
||||
|
||||
public ResultManager(PluginInitContext context)
|
||||
public static void Init(PluginInitContext context)
|
||||
{
|
||||
this.context = context;
|
||||
Context = context;
|
||||
}
|
||||
internal Result CreateFolderResult(string title, string subtitle, string path, Query query, bool showIndexState = false, bool windowsIndexed = false)
|
||||
|
||||
internal static Result CreateFolderResult(string title, string subtitle, string path, Query query, bool showIndexState = false, bool windowsIndexed = false)
|
||||
{
|
||||
return new Result
|
||||
{
|
||||
|
|
@ -41,7 +43,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
}
|
||||
|
||||
string changeTo = path.EndsWith(Constants.DirectorySeperator) ? path : path + Constants.DirectorySeperator;
|
||||
context.API.ChangeQuery(string.IsNullOrEmpty(query.ActionKeyword) ?
|
||||
Context.API.ChangeQuery(string.IsNullOrEmpty(query.ActionKeyword) ?
|
||||
changeTo :
|
||||
query.ActionKeyword + " " + changeTo);
|
||||
return false;
|
||||
|
|
@ -52,7 +54,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
};
|
||||
}
|
||||
|
||||
internal Result CreateOpenCurrentFolderResult(string path, bool windowsIndexed = false)
|
||||
internal static Result CreateOpenCurrentFolderResult(string path, bool windowsIndexed = false)
|
||||
{
|
||||
var retrievedDirectoryPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path);
|
||||
|
||||
|
|
@ -94,7 +96,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
};
|
||||
}
|
||||
|
||||
internal Result CreateFileResult(string filePath, Query query, bool showIndexState = false, bool windowsIndexed = false)
|
||||
internal static Result CreateFileResult(string filePath, Query query, bool showIndexState = false, bool windowsIndexed = false)
|
||||
{
|
||||
var result = new Result
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,21 +14,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
{
|
||||
private readonly PluginInitContext context;
|
||||
|
||||
private readonly IndexSearch indexSearch;
|
||||
|
||||
private readonly QuickAccess quickAccess;
|
||||
|
||||
private readonly ResultManager resultManager;
|
||||
|
||||
private readonly Settings settings;
|
||||
|
||||
public SearchManager(Settings settings, PluginInitContext context)
|
||||
{
|
||||
this.context = context;
|
||||
indexSearch = new IndexSearch(context);
|
||||
resultManager = new ResultManager(context);
|
||||
this.settings = settings;
|
||||
quickAccess = new QuickAccess(context);
|
||||
}
|
||||
|
||||
internal async Task<List<Result>> SearchAsync(Query query, CancellationToken token)
|
||||
|
|
@ -42,9 +33,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
// This allows the user to type the assigned action keyword and only see the list of quick folder links
|
||||
if (string.IsNullOrEmpty(query.Search))
|
||||
return quickAccess.AccessLinkListAll(query, settings.QuickAccessLinks);
|
||||
return QuickAccess.AccessLinkListAll(query, settings.QuickAccessLinks);
|
||||
|
||||
var quickaccessLinks = quickAccess.AccessLinkListMatched(query, settings.QuickAccessLinks);
|
||||
var quickaccessLinks = QuickAccess.AccessLinkListMatched(query, settings.QuickAccessLinks);
|
||||
|
||||
if (quickaccessLinks.Count > 0)
|
||||
results.AddRange(quickaccessLinks);
|
||||
|
|
@ -75,7 +66,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath);
|
||||
|
||||
results.Add(resultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch));
|
||||
results.Add(ResultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch));
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
|
|
@ -100,7 +91,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
if (string.IsNullOrEmpty(querySearchString))
|
||||
return new List<Result>();
|
||||
|
||||
return await indexSearch.WindowsIndexSearchAsync(querySearchString,
|
||||
return await IndexSearch.WindowsIndexSearchAsync(querySearchString,
|
||||
queryConstructor.CreateQueryHelper().ConnectionString,
|
||||
queryConstructor.QueryForFileContentSearch,
|
||||
query,
|
||||
|
|
@ -114,9 +105,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
private List<Result> DirectoryInfoClassSearch(Query query, string querySearch, CancellationToken token)
|
||||
{
|
||||
var directoryInfoSearch = new DirectoryInfoSearch(context);
|
||||
|
||||
return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch, token);
|
||||
return DirectoryInfoSearch.TopLevelDirectorySearch(query, querySearch, token);
|
||||
}
|
||||
|
||||
public async Task<List<Result>> TopLevelDirectorySearchBehaviourAsync(
|
||||
|
|
@ -137,7 +126,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
{
|
||||
var queryConstructor = new QueryConstructor(settings);
|
||||
|
||||
return await indexSearch.WindowsIndexSearchAsync(querySearchString,
|
||||
return await IndexSearch.WindowsIndexSearchAsync(querySearchString,
|
||||
queryConstructor.CreateQueryHelper().ConnectionString,
|
||||
queryConstructor.QueryForAllFilesAndFolders,
|
||||
query,
|
||||
|
|
@ -148,7 +137,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
{
|
||||
var queryConstructor = new QueryConstructor(settings);
|
||||
|
||||
return await indexSearch.WindowsIndexSearchAsync(path,
|
||||
return await IndexSearch.WindowsIndexSearchAsync(path,
|
||||
queryConstructor.CreateQueryHelper().ConnectionString,
|
||||
queryConstructor.QueryForTopLevelDirectorySearch,
|
||||
query,
|
||||
|
|
@ -167,7 +156,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
.StartsWith(x.Path, StringComparison.OrdinalIgnoreCase)))
|
||||
return false;
|
||||
|
||||
return indexSearch.PathIsIndexed(pathToDirectory);
|
||||
return IndexSearch.PathIsIndexed(pathToDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,19 +10,13 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
||||
{
|
||||
internal class IndexSearch
|
||||
internal static class IndexSearch
|
||||
{
|
||||
private readonly ResultManager resultManager;
|
||||
|
||||
// Reserved keywords in oleDB
|
||||
private readonly string reservedStringPattern = @"^[`\@\#\^,\&\/\\\$\%_]+$";
|
||||
private const string reservedStringPattern = @"^[`\@\#\^,\&\/\\\$\%_]+$";
|
||||
|
||||
internal IndexSearch(PluginInitContext context)
|
||||
{
|
||||
resultManager = new ResultManager(context);
|
||||
}
|
||||
|
||||
internal async Task<List<Result>> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token)
|
||||
internal async static Task<List<Result>> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
var fileResults = new List<Result>();
|
||||
|
|
@ -54,7 +48,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
|
||||
if (dataReaderResults.GetString(2) == "Directory")
|
||||
{
|
||||
results.Add(resultManager.CreateFolderResult(
|
||||
results.Add(ResultManager.CreateFolderResult(
|
||||
dataReaderResults.GetString(0),
|
||||
path,
|
||||
path,
|
||||
|
|
@ -62,7 +56,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
}
|
||||
else
|
||||
{
|
||||
fileResults.Add(resultManager.CreateFileResult(path, query, true, true));
|
||||
fileResults.Add(ResultManager.CreateFileResult(path, query, true, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -88,7 +82,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
return results;
|
||||
}
|
||||
|
||||
internal async Task<List<Result>> WindowsIndexSearchAsync(string searchString, string connectionString,
|
||||
internal async static Task<List<Result>> WindowsIndexSearchAsync(string searchString, string connectionString,
|
||||
Func<string, string> constructQuery, Query query,
|
||||
CancellationToken token)
|
||||
{
|
||||
|
|
@ -102,14 +96,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
|
||||
}
|
||||
|
||||
internal bool PathIsIndexed(string path)
|
||||
internal static bool PathIsIndexed(string path)
|
||||
{
|
||||
var csm = new CSearchManager();
|
||||
var indexManager = csm.GetCatalog("SystemIndex").GetCrawlScopeManager();
|
||||
return indexManager.IncludedInCrawlScope(path) > 0;
|
||||
}
|
||||
|
||||
private void LogException(string message, Exception e)
|
||||
private static void LogException(string message, Exception e)
|
||||
{
|
||||
#if DEBUG // Please investigate and handle error from index search
|
||||
throw e;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"Name": "Explorer",
|
||||
"Description": "Search and manage files and folders. Explorer utilises Windows Index Search",
|
||||
"Author": "Jeremy Wu",
|
||||
"Version": "1.6.0",
|
||||
"Version": "1.7.0",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ using Flow.Launcher.Infrastructure;
|
|||
using Flow.Launcher.Plugin.Program.Logger;
|
||||
using IStream = AppxPackaing.IStream;
|
||||
using Rect = System.Windows.Rect;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Programs
|
||||
{
|
||||
|
|
@ -206,12 +207,11 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.LogException("UWP" ,"CurrentUserPackages", $"id","An unexpected error occured and "
|
||||
ProgramLogger.LogException("UWP", "CurrentUserPackages", $"id", "An unexpected error occured and "
|
||||
+ $"unable to verify if package is valid", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return valid;
|
||||
});
|
||||
return ps;
|
||||
|
|
@ -263,21 +263,40 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
public string LogoPath { get; set; }
|
||||
public UWP Package { get; set; }
|
||||
|
||||
public Application(){}
|
||||
public Application() { }
|
||||
|
||||
|
||||
public Result Result(string query, IPublicAPI api)
|
||||
{
|
||||
var title = (Name, Description) switch
|
||||
{
|
||||
(var n, null) => n,
|
||||
(var n, var d) when d.StartsWith(n) => d,
|
||||
(var n, var d) when n.StartsWith(d) => n,
|
||||
(var n, var d) when !string.IsNullOrEmpty(d) => $"{n}: {d}",
|
||||
_ => Name
|
||||
};
|
||||
string title;
|
||||
MatchResult matchResult;
|
||||
|
||||
var matchResult = StringMatcher.FuzzySearch(query, title);
|
||||
// We suppose Name won't be null
|
||||
if (Description == null || Name.StartsWith(Description))
|
||||
{
|
||||
title = Name;
|
||||
matchResult = StringMatcher.FuzzySearch(query, title);
|
||||
}
|
||||
else if (Description.StartsWith(Name))
|
||||
{
|
||||
title = Description;
|
||||
matchResult = StringMatcher.FuzzySearch(query, Description);
|
||||
}
|
||||
else
|
||||
{
|
||||
title = $"{Name}: {Description}";
|
||||
var nameMatch = StringMatcher.FuzzySearch(query, Name);
|
||||
var desciptionMatch = StringMatcher.FuzzySearch(query, Description);
|
||||
if (desciptionMatch.Score > nameMatch.Score)
|
||||
{
|
||||
for (int i = 0; i < desciptionMatch.MatchData.Count; i++)
|
||||
{
|
||||
desciptionMatch.MatchData[i] += Name.Length + 2; // 2 is ": "
|
||||
}
|
||||
matchResult = desciptionMatch;
|
||||
}
|
||||
else matchResult = nameMatch;
|
||||
}
|
||||
|
||||
if (!matchResult.Success)
|
||||
return null;
|
||||
|
|
@ -311,7 +330,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
|
||||
Action = _ =>
|
||||
{
|
||||
Main.StartProcess(Process.Start,
|
||||
Main.StartProcess(Process.Start,
|
||||
new ProcessStartInfo(
|
||||
!string.IsNullOrEmpty(Main._settings.CustomizedExplorer)
|
||||
? Main._settings.CustomizedExplorer
|
||||
|
|
@ -403,14 +422,14 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
public string FormattedPriReferenceValue(string packageName, string rawPriReferenceValue)
|
||||
{
|
||||
const string prefix = "ms-resource:";
|
||||
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rawPriReferenceValue) || !rawPriReferenceValue.StartsWith(prefix))
|
||||
return rawPriReferenceValue;
|
||||
|
||||
string key = rawPriReferenceValue.Substring(prefix.Length);
|
||||
if (key.StartsWith("//"))
|
||||
return $"{prefix}{key}";
|
||||
|
||||
|
||||
if (!key.StartsWith("/"))
|
||||
{
|
||||
key = $"/{key}";
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using Shell;
|
|||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Plugin.Program.Logger;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Programs
|
||||
{
|
||||
|
|
@ -36,19 +37,38 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
|
||||
public Result Result(string query, IPublicAPI api)
|
||||
{
|
||||
var title = (Name, Description) switch
|
||||
string title;
|
||||
MatchResult matchResult;
|
||||
|
||||
// We suppose Name won't be null
|
||||
if (Description == null || Name.StartsWith(Description))
|
||||
{
|
||||
(var n, null) => n,
|
||||
(var n, var d) when d.StartsWith(n) => d,
|
||||
(var n, var d) when n.StartsWith(d) => n,
|
||||
(var n, var d) when !string.IsNullOrEmpty(d) => $"{n}: {d}",
|
||||
_ => Name
|
||||
};
|
||||
title = Name;
|
||||
matchResult = StringMatcher.FuzzySearch(query, title);
|
||||
}
|
||||
else if (Description.StartsWith(Name))
|
||||
{
|
||||
title = Description;
|
||||
matchResult = StringMatcher.FuzzySearch(query, Description);
|
||||
}
|
||||
else
|
||||
{
|
||||
title = $"{Name}: {Description}";
|
||||
var nameMatch = StringMatcher.FuzzySearch(query, Name);
|
||||
var desciptionMatch = StringMatcher.FuzzySearch(query, Description);
|
||||
if (desciptionMatch.Score > nameMatch.Score)
|
||||
{
|
||||
for (int i = 0; i < desciptionMatch.MatchData.Count; i++)
|
||||
{
|
||||
desciptionMatch.MatchData[i] += Name.Length + 2; // 2 is ": "
|
||||
}
|
||||
matchResult = desciptionMatch;
|
||||
}
|
||||
else matchResult = nameMatch;
|
||||
}
|
||||
|
||||
var matchResult = StringMatcher.FuzzySearch(query, title);
|
||||
if (!matchResult.Success) return null;
|
||||
|
||||
if (!matchResult.Success)
|
||||
return null;
|
||||
|
||||
var result = new Result
|
||||
{
|
||||
|
|
@ -58,7 +78,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
Score = matchResult.Score,
|
||||
TitleHighlightData = matchResult.MatchData,
|
||||
ContextData = this,
|
||||
Action = e =>
|
||||
Action = _ =>
|
||||
{
|
||||
var info = new ProcessStartInfo
|
||||
{
|
||||
|
|
@ -268,10 +288,10 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
try
|
||||
{
|
||||
var paths = Directory.EnumerateFiles(directory, "*", new EnumerationOptions
|
||||
{
|
||||
IgnoreInaccessible = true,
|
||||
RecurseSubdirectories = true
|
||||
})
|
||||
{
|
||||
IgnoreInaccessible = true,
|
||||
RecurseSubdirectories = true
|
||||
})
|
||||
.Where(x => suffixes.Contains(Extension(x)));
|
||||
|
||||
return paths;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Name": "Program",
|
||||
"Description": "Search programs in Flow.Launcher",
|
||||
"Author": "qianlifeng",
|
||||
"Version": "1.3.0",
|
||||
"Version": "1.4.0",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Program.dll",
|
||||
|
|
|
|||
|
|
@ -54,10 +54,10 @@
|
|||
<Content Include="Images\*.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Page Include="ShellSetting.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="ShellSetting.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -48,10 +48,10 @@
|
|||
<Content Include="Images\*.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Page Include="SysSettings.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="SysSettings.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
Loading…
Reference in a new issue