Merge remote-tracking branch 'origin/dev' into add_python_installation

This commit is contained in:
Jeremy Wu 2021-02-03 12:55:18 +11:00
commit 39229e591c
23 changed files with 451 additions and 218 deletions

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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,

View file

@ -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))

View file

@ -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}");
}
}
}
}

View file

@ -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,

View file

@ -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();

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>

View file

@ -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;

View file

@ -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)

View file

@ -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();

View file

@ -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));
}
}

View file

@ -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();

View file

@ -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
{

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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",

View file

@ -1,5 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>

View file

@ -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}";

View file

@ -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;

View file

@ -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",

View file

@ -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>

View file

@ -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>