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

This commit is contained in:
Jeremy Wu 2021-02-03 12:56:43 +11:00
commit f17b7cc355
59 changed files with 1040 additions and 585 deletions

View file

@ -35,7 +35,8 @@ namespace Flow.Launcher.Core
UpdateInfo newUpdateInfo;
if (!silentUpdate)
api.ShowMsg("Please wait...", "Checking for new update");
api.ShowMsg(api.GetTranslation("pleaseWait"),
api.GetTranslation("update_flowlauncher_update_check"));
using var updateManager = await GitHubUpdateManager(GitHubRepository).ConfigureAwait(false);
@ -51,12 +52,13 @@ namespace Flow.Launcher.Core
if (newReleaseVersion <= currentVersion)
{
if (!silentUpdate)
MessageBox.Show("You already have the latest Flow Launcher version");
MessageBox.Show(api.GetTranslation("update_flowlauncher_already_on_latest"));
return;
}
if (!silentUpdate)
api.ShowMsg("Update found", "Updating...");
api.ShowMsg(api.GetTranslation("update_flowlauncher_update_found"),
api.GetTranslation("update_flowlauncher_updating"));
await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false);
@ -67,8 +69,9 @@ namespace Flow.Launcher.Core
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination);
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination))
MessageBox.Show("Flow Launcher was not able to move your user profile data to the new update version. Please manually " +
$"move your profile data folder from {DataLocation.PortableDataPath} to {targetDestination}");
MessageBox.Show(string.Format(api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
DataLocation.PortableDataPath,
targetDestination));
}
else
{
@ -79,7 +82,7 @@ namespace Flow.Launcher.Core
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
if (MessageBox.Show(newVersionTips, "New Update", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
if (MessageBox.Show(newVersionTips, api.GetTranslation("update_flowlauncher_new_update"), MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
@ -87,7 +90,8 @@ namespace Flow.Launcher.Core
catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException)
{
Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
api.ShowMsg("Update Failed", "Check your connection and try updating proxy settings to github-cloud.s3.amazonaws.com.");
api.ShowMsg(api.GetTranslation("update_flowlauncher_fail"),
api.GetTranslation("update_flowlauncher_check_connection"));
return;
}
}

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

@ -1,8 +1,7 @@
using Flow.Launcher.Plugin.SharedModels;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using static Flow.Launcher.Infrastructure.StringMatcher;
namespace Flow.Launcher.Infrastructure
{
@ -32,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,
@ -44,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];
@ -75,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;
}
@ -107,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);
}
}
@ -127,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++)
@ -183,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>();
@ -202,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,
@ -239,74 +331,6 @@ namespace Flow.Launcher.Infrastructure
return score;
}
public enum SearchPrecisionScore
{
Regular = 50,
Low = 20,
None = 0
}
}
public class MatchResult
{
public MatchResult(bool success, SearchPrecisionScore searchPrecision)
{
Success = success;
SearchPrecision = searchPrecision;
}
public MatchResult(bool success, SearchPrecisionScore searchPrecision, List<int> matchData, int rawScore)
{
Success = success;
SearchPrecision = searchPrecision;
MatchData = matchData;
RawScore = rawScore;
}
public bool Success { get; set; }
/// <summary>
/// The final score of the match result with search precision filters applied.
/// </summary>
public int Score { get; private set; }
/// <summary>
/// The raw calculated search score without any search precision filtering applied.
/// </summary>
private int _rawScore;
public int RawScore
{
get { return _rawScore; }
set
{
_rawScore = value;
Score = ScoreAfterSearchPrecisionFilter(_rawScore);
}
}
/// <summary>
/// Matched data to highlight.
/// </summary>
public List<int> MatchData { get; set; }
public SearchPrecisionScore SearchPrecision { get; set; }
public bool IsSearchPrecisionScoreMet()
{
return IsSearchPrecisionScoreMet(_rawScore);
}
private bool IsSearchPrecisionScoreMet(int rawScore)
{
return rawScore >= (int)SearchPrecision;
}
private int ScoreAfterSearchPrecisionFilter(int rawScore)
{
return IsSearchPrecisionScoreMet(rawScore) ? rawScore : 0;
}
}
public class MatchOption

View file

@ -3,6 +3,7 @@ using System.Collections.ObjectModel;
using System.Drawing;
using System.Text.Json.Serialization;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
namespace Flow.Launcher.Infrastructure.UserSettings
{
@ -38,7 +39,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
/// </summary>
public bool ShouldUsePinyin { get; set; } = false;
internal StringMatcher.SearchPrecisionScore QuerySearchPrecision { get; private set; } = StringMatcher.SearchPrecisionScore.Regular;
internal SearchPrecisionScore QuerySearchPrecision { get; private set; } = SearchPrecisionScore.Regular;
[JsonIgnore]
public string QuerySearchPrecisionString
@ -48,8 +49,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
try
{
var precisionScore = (StringMatcher.SearchPrecisionScore)Enum
.Parse(typeof(StringMatcher.SearchPrecisionScore), value);
var precisionScore = (SearchPrecisionScore)Enum
.Parse(typeof(SearchPrecisionScore), value);
QuerySearchPrecision = precisionScore;
StringMatcher.Instance.UserSettingSearchPrecision = precisionScore;
@ -58,8 +59,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
Logger.Log.Exception(nameof(Settings), "Failed to load QuerySearchPrecisionString value from Settings file", e);
QuerySearchPrecision = StringMatcher.SearchPrecisionScore.Regular;
StringMatcher.Instance.UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular;
QuerySearchPrecision = SearchPrecisionScore.Regular;
StringMatcher.Instance.UserSettingSearchPrecision = SearchPrecisionScore.Regular;
throw;
}

View file

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

View file

@ -1,5 +1,9 @@
using System;
using Flow.Launcher.Plugin.SharedModels;
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Flow.Launcher.Plugin
@ -83,5 +87,18 @@ namespace Flow.Launcher.Plugin
/// if you want to hook something like Ctrl+R, you should use this event
/// </summary>
event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent;
MatchResult FuzzySearch(string query, string stringToCompare);
Task<string> HttpGetStringAsync(string url, CancellationToken token = default);
Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = default);
Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath);
void AddActionKeyword(string pluginId, string newActionKeyword);
void RemoveActionKeyword(string pluginId, string oldActionKeyword);
}
}

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))
@ -146,31 +146,23 @@ namespace Flow.Launcher.Plugin.SharedCommands
/// This checks whether a given string is a directory path or network location string.
/// It does not check if location actually exists.
///</summary>
public static bool IsLocationPathString(string querySearchString)
public static bool IsLocationPathString(this string querySearchString)
{
if (string.IsNullOrEmpty(querySearchString))
if (string.IsNullOrEmpty(querySearchString) || querySearchString.Length < 3)
return false;
// // shared folder location, and not \\\location\
if (querySearchString.Length >= 3
&& querySearchString.StartsWith(@"\\")
&& char.IsLetter(querySearchString[2]))
if (querySearchString.StartsWith(@"\\")
&& querySearchString[2] != '\\')
return true;
// c:\
if (querySearchString.Length == 3
&& char.IsLetter(querySearchString[0])
if (char.IsLetter(querySearchString[0])
&& querySearchString[1] == ':'
&& querySearchString[2] == '\\')
return true;
// c:\\
if (querySearchString.Length >= 4
&& char.IsLetter(querySearchString[0])
&& querySearchString[1] == ':'
&& querySearchString[2] == '\\'
&& char.IsLetter(querySearchString[3]))
return true;
{
return querySearchString.Length == 3 || querySearchString[3] != '\\';
}
return false;
}

View file

@ -0,0 +1,72 @@
using System.Collections.Generic;
namespace Flow.Launcher.Plugin.SharedModels
{
public class MatchResult
{
public MatchResult(bool success, SearchPrecisionScore searchPrecision)
{
Success = success;
SearchPrecision = searchPrecision;
}
public MatchResult(bool success, SearchPrecisionScore searchPrecision, List<int> matchData, int rawScore)
{
Success = success;
SearchPrecision = searchPrecision;
MatchData = matchData;
RawScore = rawScore;
}
public bool Success { get; set; }
/// <summary>
/// The final score of the match result with search precision filters applied.
/// </summary>
public int Score { get; private set; }
/// <summary>
/// The raw calculated search score without any search precision filtering applied.
/// </summary>
private int _rawScore;
public int RawScore
{
get { return _rawScore; }
set
{
_rawScore = value;
Score = ScoreAfterSearchPrecisionFilter(_rawScore);
}
}
/// <summary>
/// Matched data to highlight.
/// </summary>
public List<int> MatchData { get; set; }
public SearchPrecisionScore SearchPrecision { get; set; }
public bool IsSearchPrecisionScoreMet()
{
return IsSearchPrecisionScoreMet(_rawScore);
}
private bool IsSearchPrecisionScoreMet(int rawScore)
{
return rawScore >= (int)SearchPrecision;
}
private int ScoreAfterSearchPrecisionFilter(int rawScore)
{
return IsSearchPrecisionScoreMet(rawScore) ? rawScore : 0;
}
}
public enum SearchPrecisionScore
{
Regular = 50,
Low = 20,
None = 0
}
}

View file

@ -5,6 +5,7 @@ using System.Linq;
using NUnit.Framework;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
namespace Flow.Launcher.Test
{
@ -37,8 +38,8 @@ namespace Flow.Launcher.Test
{
var listToReturn = new List<int>();
Enum.GetValues(typeof(StringMatcher.SearchPrecisionScore))
.Cast<StringMatcher.SearchPrecisionScore>()
Enum.GetValues(typeof(SearchPrecisionScore))
.Cast<SearchPrecisionScore>()
.ToList()
.ForEach(x => listToReturn.Add((int)x));
@ -92,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();
@ -108,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("###############################################");
@ -119,6 +121,7 @@ namespace Flow.Launcher.Test
{
Debug.WriteLine("SCORE: " + item.Score.ToString() + ", FoundString: " + item.Title);
}
Debug.WriteLine("###############################################");
Debug.WriteLine("");
@ -128,37 +131,47 @@ 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}");
}
[TestCase("goo", "Google Chrome", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Google Chrome", StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("chr", "Chrome", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("chr", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.None, true)]
[TestCase("ccs", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("cand", "Candy Crush Saga from King",StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("cand", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("goo", "Google Chrome", SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Google Chrome", SearchPrecisionScore.Low, true)]
[TestCase("chr", "Chrome", SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Low, true)]
[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", "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,
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
SearchPrecisionScore expectedPrecisionScore,
bool expectedPrecisionResult)
{
// When
@ -170,48 +183,50 @@ 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}");
}
[TestCase("exce", "OverLeaf-Latex: An online LaTeX editor", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("term", "Windows Terminal (Preview)", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql s managa", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql' s manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql s manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql serv", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("servez", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql servz", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql serv man", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql studio", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("mic", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Shutdown", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("mssms", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("a test", "This is a test", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("test", "This is a test", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("cod", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("code", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("codes", "Visual Studio Codes", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("exce", "OverLeaf-Latex: An online LaTeX editor", SearchPrecisionScore.Regular, false)]
[TestCase("term", "Windows Terminal (Preview)", SearchPrecisionScore.Regular, true)]
[TestCase("sql s managa", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)]
[TestCase("sql' s manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)]
[TestCase("sql s manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
[TestCase("sql", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
[TestCase("sql serv", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
[TestCase("servez", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)]
[TestCase("sql servz", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)]
[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("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)]
[TestCase("test", "This is a test", SearchPrecisionScore.Regular, true)]
[TestCase("cod", VisualStudioCode, SearchPrecisionScore.Regular, true)]
[TestCase("code", VisualStudioCode, SearchPrecisionScore.Regular, true)]
[TestCase("codes", "Visual Studio Codes", SearchPrecisionScore.Regular, true)]
public void WhenGivenQuery_ShouldReturnResults_ContainingAllQuerySubstrings(
string queryString,
string compareString,
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
SearchPrecisionScore expectedPrecisionScore,
bool expectedPrecisionResult)
{
// When
var matcher = new StringMatcher { UserSettingSearchPrecision = expectedPrecisionScore };
var matcher = new StringMatcher {UserSettingSearchPrecision = expectedPrecisionScore};
// Given
var matchResult = matcher.FuzzyMatch(queryString, compareString);
@ -219,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("");
@ -238,7 +254,7 @@ namespace Flow.Launcher.Test
string queryString, string compareString1, string compareString2)
{
// When
var matcher = new StringMatcher { UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular };
var matcher = new StringMatcher {UserSettingSearchPrecision = SearchPrecisionScore.Regular};
// Given
var compareString1Result = matcher.FuzzyMatch(queryString, compareString1);
@ -247,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("");
@ -256,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
@ -275,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

@ -24,7 +24,7 @@ namespace Flow.Launcher.Test.Plugins
return new List<Result>();
}
private List<Result> MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString)
private List<Result> MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString, CancellationToken token)
{
return new List<Result>
{
@ -60,8 +60,8 @@ namespace Flow.Launcher.Test.Plugins
$"Actual: {result}{Environment.NewLine}");
}
[TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\'")]
[TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\'")]
[TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\' ORDER BY System.FileName")]
[TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\' ORDER BY System.FileName")]
public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString)
{
// Given
@ -79,7 +79,7 @@ namespace Flow.Launcher.Test.Plugins
[TestCase("C:\\SomeFolder\\flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " +
"FROM SystemIndex WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033))" +
" AND directory='file:C:\\SomeFolder'")]
" AND directory='file:C:\\SomeFolder' ORDER BY System.FileName")]
public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString(
string userSearchString, string expectedString)
{
@ -116,11 +116,8 @@ namespace Flow.Launcher.Test.Plugins
[TestCase("scope='file:'")]
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereRestrictionsShouldUseScopeString(string expectedString)
{
// Given
var queryConstructor = new QueryConstructor(new Settings());
//When
var resultString = queryConstructor.QueryWhereRestrictionsForAllFilesAndFoldersSearch();
var resultString = QueryConstructor.QueryWhereRestrictionsForAllFilesAndFoldersSearch;
// Then
Assert.IsTrue(resultString == expectedString,
@ -130,7 +127,7 @@ namespace Flow.Launcher.Test.Plugins
[TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" " +
"FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:'")]
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY System.FileName")]
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString(
string userSearchString, string expectedString)
{
@ -205,7 +202,7 @@ namespace Flow.Launcher.Test.Plugins
}
[TestCase("some words", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " +
"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:'")]
"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY System.FileName")]
public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString(
string userSearchString, string expectedString)
{
@ -243,6 +240,9 @@ namespace Flow.Launcher.Test.Plugins
[TestCase(@"cc:\", false)]
[TestCase(@"\\\SomeNetworkLocation\", false)]
[TestCase("RandomFile", false)]
[TestCase(@"c:\>*", true)]
[TestCase(@"c:\>", true)]
[TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)]
public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult)
{
// When, Given
@ -295,9 +295,9 @@ namespace Flow.Launcher.Test.Plugins
}
[TestCase("c:\\SomeFolder\\>", "scope='file:c:\\SomeFolder'")]
[TestCase("c:\\SomeFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' " +
"OR CONTAINS(System.FileName,'\"SomeName*\"',1033)) AND " +
"scope='file:c:\\SomeFolder'")]
[TestCase("c:\\SomeFolder\\>SomeName", "(System.FileName LIKE 'SomeName%' "
+ "OR CONTAINS(System.FileName,'\"SomeName*\"',1033)) AND "
+ "scope='file:c:\\SomeFolder'")]
public void GivenWindowsIndexSearch_WhenSearchPatternHotKeyIsSearchAll_ThenQueryWhereRestrictionsShouldUseScopeString(string path, string expectedString)
{
// Given
@ -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

@ -5,7 +5,7 @@
Icon="Images\app.png"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
Title="Custom Plugin Hotkey" Height="200" Width="674.766">
Title="{DynamicResource customeQueryHotkeyTitle}" Height="200" Width="674.766">
<Window.InputBindings>
<KeyBinding Key="Escape" Command="Close"/>
</Window.InputBindings>

View file

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

View file

@ -17,6 +17,7 @@
<!--Setting General-->
<system:String x:Key="flowlauncher_settings">Flow Launcher Settings</system:String>
<system:String x:Key="general">General</system:String>
<system:String x:Key="portableMode">Portable Mode</system:String>
<system:String x:Key="startFlowLauncherOnSystemStartup">Start Flow Launcher on system startup</system:String>
<system:String x:Key="hideFlowLauncherWhenLoseFocus">Hide Flow Launcher when focus is lost</system:String>
<system:String x:Key="dontPromptUpdateMsg">Do not show new version notifications</system:String>
@ -39,12 +40,13 @@
<!--Setting Plugin-->
<system:String x:Key="plugin">Plugin</system:String>
<system:String x:Key="browserMorePlugins">Find more plugins</system:String>
<system:String x:Key="enable">Enable</system:String>
<system:String x:Key="disable">Disable</system:String>
<system:String x:Key="actionKeywords">Action keyword:</system:String>
<system:String x:Key="currentActionKeywords">Current action keyword:</system:String>
<system:String x:Key="newActionKeyword">New action keyword:</system:String>
<system:String x:Key="currentPriority">Current Priority: </system:String>
<system:String x:Key="newPriority">New Priority: </system:String>
<system:String x:Key="currentPriority">Current Priority:</system:String>
<system:String x:Key="newPriority">New Priority:</system:String>
<system:String x:Key="pluginDirectory">Plugin Directory</system:String>
<system:String x:Key="author">Author</system:String>
<system:String x:Key="plugin_init_time">Init time:</system:String>
@ -109,7 +111,7 @@
<!--Priority Setting Dialog-->
<system:String x:Key="priority_tips">Greater the number, the higher the result will be ranked. Try setting it as 5. If you want the results to be lower than any other plugin's, provide a negative number</system:String>
<system:String x:Key="invalidPriority">Please provide an valid integer for Priority!</system:String>
<!--Action Keyword Setting Dialog-->
<system:String x:Key="oldActionKeywords">Old Action Keyword</system:String>
<system:String x:Key="newActionKeywords">New Action Keyword</system:String>
@ -122,6 +124,7 @@
<system:String x:Key="actionkeyword_tips">Use * if you don't want to specify an action keyword</system:String>
<!--Custom Query Hotkey Dialog-->
<system:String x:Key="customeQueryHotkeyTitle">Custom Plugin Hotkey</system:String>
<system:String x:Key="preview">Preview</system:String>
<system:String x:Key="hotkeyIsNotUnavailable">Hotkey is unavailable, please select a new hotkey</system:String>
<system:String x:Key="invalidPluginHotkey">Invalid plugin hotkey</system:String>
@ -146,11 +149,23 @@
<system:String x:Key="reportWindow_report_failed">Failed to send report</system:String>
<system:String x:Key="reportWindow_flowlauncher_got_an_error">Flow Launcher got an error</system:String>
<!--General Notice-->
<system:String x:Key="pleaseWait">Please wait...</system:String>
<!--update-->
<system:String x:Key="update_flowlauncher_update_check">Checking for new update</system:String>
<system:String x:Key="update_flowlauncher_already_on_latest">You already have the latest Flow Launcher version</system:String>
<system:String x:Key="update_flowlauncher_update_found">Update found</system:String>
<system:String x:Key="update_flowlauncher_updating">Updating...</system:String>
<system:String x:Key="update_flowlauncher_fail_moving_portable_user_profile_data">Flow Launcher was not able to move your user profile data to the new update version.
Please manually move your profile data folder from {0} to {1}</system:String>
<system:String x:Key="update_flowlauncher_new_update">New Update</system:String>
<system:String x:Key="update_flowlauncher_update_new_version_available">New Flow Launcher release {0} is now available</system:String>
<system:String x:Key="update_flowlauncher_update_error">An error occurred while trying to install software updates</system:String>
<system:String x:Key="update_flowlauncher_update">Update</system:String>
<system:String x:Key="update_flowlauncher_update_cancel">Cancel</system:String>
<system:String x:Key="update_flowlauncher_fail">Update Failed</system:String>
<system:String x:Key="update_flowlauncher_check_connection">Check your connection and try updating proxy settings to github-cloud.s3.amazonaws.com.</system:String>
<system:String x:Key="update_flowlauncher_update_restart_flowlauncher_tip">This upgrade will restart Flow Launcher</system:String>
<system:String x:Key="update_flowlauncher_update_upadte_files">Following files will be updated</system:String>
<system:String x:Key="update_flowlauncher_update_files">Update files</system:String>

View file

@ -17,6 +17,7 @@
<!--Setting General-->
<system:String x:Key="flowlauncher_settings">Nastavenia Flow Launchera</system:String>
<system:String x:Key="general">Všeobecné</system:String>
<system:String x:Key="portableMode">Prenosný režim</system:String>
<system:String x:Key="startFlowLauncherOnSystemStartup">Spustiť Flow Launcher po štarte systému</system:String>
<system:String x:Key="hideFlowLauncherWhenLoseFocus">Schovať Flow Launcher po strate fokusu</system:String>
<system:String x:Key="dontPromptUpdateMsg">Nezobrazovať upozornenia na novú verziu</system:String>
@ -39,10 +40,13 @@
<!--Setting Plugin-->
<system:String x:Key="plugin">Plugin</system:String>
<system:String x:Key="browserMorePlugins">Nájsť ďalšie pluginy</system:String>
<system:String x:Key="enable">Povoliť</system:String>
<system:String x:Key="disable">Zakázať</system:String>
<system:String x:Key="actionKeywords">Skratka akcie</system:String>
<system:String x:Key="currentActionKeywords">Aktuálna akcia skratky:</system:String>
<system:String x:Key="newActionKeyword">Nová akcia skratky:</system:String>
<system:String x:Key="currentPriority">Aktuálna priorita:</system:String>
<system:String x:Key="newPriority">Nová priorita:</system:String>
<system:String x:Key="pluginDirectory">Priečinok s pluginmi</system:String>
<system:String x:Key="author">Autor</system:String>
<system:String x:Key="plugin_init_time">Príprava:</system:String>
@ -104,6 +108,10 @@
</system:String>
<system:String x:Key="releaseNotes">Poznámky k vydaniu</system:String>
<!--Priority Setting Dialog-->
<system:String x:Key="priority_tips">Vyššie číslo znamená, že výsledok bude vyššie. Skúste nastaviť napr. 5. Ak chcete, aby boli výsledky nižšie ako ktorékoľvek iné doplnky, zadajte záporné číslo</system:String>
<system:String x:Key="invalidPriority">Prosím, zadajte platné číslo pre prioritu!</system:String>
<!--Action Keyword Setting Dialog-->
<system:String x:Key="oldActionKeywords">Stará skratka akcie</system:String>
<system:String x:Key="newActionKeywords">Nová skratka akcie</system:String>
@ -116,6 +124,7 @@
<system:String x:Key="actionkeyword_tips">Použite * ak nechcete určiť skratku pre akciu</system:String>
<!--Custom Query Hotkey Dialog-->
<system:String x:Key="customeQueryHotkeyTitle">Vlastná klávesová skratka pre plugin</system:String>
<system:String x:Key="preview">Náhľad</system:String>
<system:String x:Key="hotkeyIsNotUnavailable">Klávesová skratka je nedostupná, prosím, zadajte novú</system:String>
<system:String x:Key="invalidPluginHotkey">Neplatná klávesová skratka pluginu</system:String>
@ -140,11 +149,23 @@
<system:String x:Key="reportWindow_report_failed">Odoslanie hlásenia zlyhalo</system:String>
<system:String x:Key="reportWindow_flowlauncher_got_an_error">Flow Launcher zaznamenal chybu</system:String>
<!--General Notice-->
<system:String x:Key="pleaseWait">Čakajte, prosím…</system:String>
<!--update-->
<system:String x:Key="update_flowlauncher_update_new_version_available">Je dostupná nová verzia Flow Launcher {0}</system:String>
<system:String x:Key="update_flowlauncher_update_check">Kontrolujú sa akutalizácie</system:String>
<system:String x:Key="update_flowlauncher_already_on_latest">Už máte najnovšiu verizu Flow Launchera</system:String>
<system:String x:Key="update_flowlauncher_update_found">Bola nájdená aktualizácia</system:String>
<system:String x:Key="update_flowlauncher_updating">Aktualizuje sa…</system:String>
<system:String x:Key="update_flowlauncher_fail_moving_portable_user_profile_data">Flow Launcher nedokázal presunúť používateľské údaje do aktualizovanej verzie.
Prosím, presuňte profilový priečinok „data“ z {0} do {1}</system:String>
<system:String x:Key="update_flowlauncher_new_update">Nová aktualizácia</system:String>
<system:String x:Key="update_flowlauncher_update_new_version_available">Je dostupná nová verzia Flow Launchera {0}</system:String>
<system:String x:Key="update_flowlauncher_update_error">Počas inštalácie aktualizácií došlo k chybe</system:String>
<system:String x:Key="update_flowlauncher_update">Aktualizovať</system:String>
<system:String x:Key="update_flowlauncher_update_cancel">Zrušiť</system:String>
<system:String x:Key="update_flowlauncher_fail">Aktualizácia zlyhala</system:String>
<system:String x:Key="update_flowlauncher_check_connection">Skontrolujte pripojenie a skúste aktualizovať nastavenia servera proxy na github-cloud.s3.amazonaws.com.</system:String>
<system:String x:Key="update_flowlauncher_update_restart_flowlauncher_tip">Tento upgrade reštartuje Flow Launcher</system:String>
<system:String x:Key="update_flowlauncher_update_upadte_files">Nasledujúce súbory budú aktualizované</system:String>
<system:String x:Key="update_flowlauncher_update_files">Aktualizovať súbory</system:String>

View file

@ -97,7 +97,7 @@
Background="Transparent"/>
</Grid>
<Line x:Name="ProgressBar" HorizontalAlignment="Right"
Style="{DynamicResource PendingLineStyle}" Visibility="{Binding ProgressBarVisibility, Mode=TwoWay}"
Style="{DynamicResource PendingLineStyle}" Visibility="{Binding ProgressBarVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Y1="0" Y2="0" X2="100" Height="2" Width="752" StrokeThickness="1">
</Line>
<ContentControl>

View file

@ -26,6 +26,7 @@ namespace Flow.Launcher
#region Private Fields
private readonly Storyboard _progressBarStoryboard = new Storyboard();
private bool isProgressBarStoryboardPaused;
private Settings _settings;
private NotifyIcon _notifyIcon;
private MainViewModel _viewModel;
@ -52,7 +53,7 @@ namespace Flow.Launcher
private void OnInitialized(object sender, EventArgs e)
{
}
private void OnLoaded(object sender, RoutedEventArgs _)
@ -73,7 +74,7 @@ namespace Flow.Launcher
{
if (e.PropertyName == nameof(MainViewModel.MainWindowVisibility))
{
if (Visibility == Visibility.Visible)
if (_viewModel.MainWindowVisibility == Visibility.Visible)
{
Activate();
QueryTextBox.Focus();
@ -84,7 +85,34 @@ namespace Flow.Launcher
QueryTextBox.SelectAll();
_viewModel.LastQuerySelected = true;
}
if (_viewModel.ProgressBarVisibility == Visibility.Visible && isProgressBarStoryboardPaused)
{
_progressBarStoryboard.Resume();
isProgressBarStoryboardPaused = false;
}
}
else if (!isProgressBarStoryboardPaused)
{
_progressBarStoryboard.Pause();
isProgressBarStoryboardPaused = true;
}
}
else if (e.PropertyName == nameof(MainViewModel.ProgressBarVisibility))
{
Dispatcher.Invoke(() =>
{
if (_viewModel.ProgressBarVisibility == Visibility.Hidden && !isProgressBarStoryboardPaused)
{
_progressBarStoryboard.Pause();
isProgressBarStoryboardPaused = true;
}
else if (_viewModel.MainWindowVisibility == Visibility.Visible && isProgressBarStoryboardPaused)
{
_progressBarStoryboard.Resume();
isProgressBarStoryboardPaused = false;
}
}, System.Windows.Threading.DispatcherPriority.Render);
}
};
_settings.PropertyChanged += (o, e) =>
@ -170,6 +198,7 @@ namespace Flow.Launcher
_progressBarStoryboard.RepeatBehavior = RepeatBehavior.Forever;
ProgressBar.BeginStoryboard(_progressBarStoryboard);
_viewModel.ProgressBarVisibility = Visibility.Hidden;
isProgressBarStoryboardPaused = true;
}
private void OnMouseDown(object sender, MouseButtonEventArgs e)

View file

@ -5,7 +5,6 @@ using System.Net;
using System.Threading.Tasks;
using System.Windows;
using Squirrel;
using Flow.Launcher.Core;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
@ -14,6 +13,11 @@ using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
using Flow.Launcher.Plugin.SharedModels;
using System.Threading;
using System.IO;
using Flow.Launcher.Infrastructure.Http;
using JetBrains.Annotations;
namespace Flow.Launcher
{
@ -127,6 +131,32 @@ namespace Flow.Launcher
public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent;
public MatchResult FuzzySearch(string query, string stringToCompare) => StringMatcher.FuzzySearch(query, stringToCompare);
public Task<string> HttpGetStringAsync(string url, CancellationToken token = default)
{
return Http.GetAsync(url);
}
public Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = default)
{
return Http.GetStreamAsync(url);
}
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath)
{
return Http.DownloadAsync(url, filePath);
}
public void AddActionKeyword(string pluginId, string newActionKeyword)
{
PluginManager.AddActionKeyword(pluginId, newActionKeyword);
}
public void RemoveActionKeyword(string pluginId, string oldActionKeyword)
{
PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword);
}
#endregion
#region Private Methods
@ -139,6 +169,7 @@ namespace Flow.Launcher
}
return true;
}
#endregion
}
}

View file

@ -37,7 +37,7 @@
<ScrollViewer ui:ScrollViewerHelper.AutoHideScrollBars="True" Margin="60,30,0,30">
<StackPanel Orientation="Vertical">
<ui:ToggleSwitch Margin="10" IsOn="{Binding PortableMode}">
<TextBlock Text="Portable Mode" />
<TextBlock Text="{DynamicResource portableMode}" />
</ui:ToggleSwitch>
<CheckBox Margin="10" IsChecked="{Binding Settings.StartFlowLauncherOnSystemStartup}"
Checked="OnAutoStartupChecked" Unchecked="OnAutoStartupUncheck">
@ -166,7 +166,7 @@
</TextBlock>
</ToolTipService.ToolTip>
</TextBlock>
<ui:ToggleSwitch Grid.Column="1" OffContent="Disabled" OnContent="Enabled"
<ui:ToggleSwitch Grid.Column="1" OffContent="{DynamicResource disable}" OnContent="{DynamicResource enable}"
MaxWidth="110" HorizontalAlignment="Right"
IsOn="{Binding PluginState}"/>
</Grid>

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

@ -139,39 +139,9 @@ namespace Flow.Launcher.ViewModel
/// </summary>
public void AddResults(List<Result> newRawResults, string resultId)
{
lock (_collectionLock)
{
var newResults = NewResults(newRawResults, resultId);
var newResults = NewResults(newRawResults, resultId);
// https://social.msdn.microsoft.com/Forums/vstudio/en-US/5ff71969-f183-4744-909d-50f7cd414954/binding-a-tabcontrols-selectedindex-not-working?forum=wpf
// fix selected index flow
var updateTask = Task.Run(() =>
{
// update UI in one run, so it can avoid UI flickering
Results.Update(newResults);
if (Results.Any())
SelectedItem = Results[0];
});
if (!updateTask.Wait(300))
{
updateTask.Dispose();
throw new TimeoutException("Update result use too much time.");
}
}
if (Visbility != Visibility.Visible && Results.Count > 0)
{
Margin = new Thickness { Top = 8 };
SelectedIndex = 0;
Visbility = Visibility.Visible;
}
else
{
Margin = new Thickness { Top = 0 };
Visbility = Visibility.Collapsed;
}
UpdateResults(newResults);
}
/// <summary>
/// To avoid deadlock, this method should not called from main thread
@ -179,12 +149,18 @@ namespace Flow.Launcher.ViewModel
public void AddResults(IEnumerable<ResultsForUpdate> resultsForUpdates, CancellationToken token)
{
var newResults = NewResults(resultsForUpdates);
if (token.IsCancellationRequested)
return;
UpdateResults(newResults, token);
}
private void UpdateResults(List<ResultViewModel> newResults, CancellationToken token = default)
{
lock (_collectionLock)
{
// update UI in one run, so it can avoid UI flickering
Results.Update(newResults, token);
if (Results.Any())
SelectedItem = Results[0];
@ -202,7 +178,6 @@ namespace Flow.Launcher.ViewModel
Visbility = Visibility.Collapsed;
break;
}
}
private List<ResultViewModel> NewResults(List<Result> newRawResults, string resultId)
@ -212,10 +187,10 @@ namespace Flow.Launcher.ViewModel
var results = Results as IEnumerable<ResultViewModel>;
var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings)).ToList();
var newResults = newRawResults.Select(r => new ResultViewModel(r, _settings));
return results.Where(r => r.Result.PluginID != resultId)
.Concat(results.Intersect(newResults).Union(newResults))
.Concat(newResults)
.OrderByDescending(r => r.Result.Score)
.ToList();
}
@ -228,8 +203,7 @@ namespace Flow.Launcher.ViewModel
var results = Results as IEnumerable<ResultViewModel>;
return results.Where(r => r != null && !resultsForUpdates.Any(u => u.Metadata.ID == r.Result.PluginID))
.Concat(
resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings)))
.Concat(resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings)))
.OrderByDescending(rv => rv.Result.Score)
.ToList();
}
@ -266,49 +240,50 @@ namespace Flow.Launcher.ViewModel
}
#endregion
public class ResultCollection : ObservableCollection<ResultViewModel>
public class ResultCollection : List<ResultViewModel>, INotifyCollectionChanged
{
private long editTime = 0;
private bool _suppressNotifying = false;
private CancellationToken _token;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!_suppressNotifying)
{
base.OnCollectionChanged(e);
}
CollectionChanged?.Invoke(this, e);
}
public void BulkAddRange(IEnumerable<ResultViewModel> resultViews)
public void BulkAddAll(List<ResultViewModel> resultViews)
{
// suppress notifying before adding all element
_suppressNotifying = true;
foreach (var item in resultViews)
{
Add(item);
}
_suppressNotifying = false;
// manually update event
// wpf use directx / double buffered already, so just reset all won't cause ui flickering
AddRange(resultViews);
// can return because the list will be cleared next time updated, which include a reset event
if (_token.IsCancellationRequested)
return;
// manually update event
// wpf use directx / double buffered already, so just reset all won't cause ui flickering
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void AddRange(IEnumerable<ResultViewModel> Items)
private void AddAll(List<ResultViewModel> Items)
{
foreach (var item in Items)
for (int i = 0; i < Items.Count; i++)
{
var item = Items[i];
if (_token.IsCancellationRequested)
return;
Add(item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, i));
}
}
public void RemoveAll()
public void RemoveAll(int Capacity = 512)
{
ClearItems();
Clear();
if (this.Capacity > 8000 && Capacity < this.Capacity)
this.Capacity = Capacity;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// <summary>
@ -323,15 +298,19 @@ namespace Flow.Launcher.ViewModel
if (editTime < 10 || newItems.Count < 30)
{
if (Count != 0) ClearItems();
AddRange(newItems);
if (Count != 0) RemoveAll(newItems.Count);
AddAll(newItems);
editTime++;
return;
}
else
{
Clear();
BulkAddRange(newItems);
BulkAddAll(newItems);
if (Capacity > 8000 && newItems.Count < 3000)
{
Capacity = newItems.Count;
}
editTime++;
}
}

View file

@ -17,6 +17,7 @@ using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
namespace Flow.Launcher.ViewModel
{
@ -153,7 +154,7 @@ namespace Flow.Launcher.ViewModel
{
var precisionStrings = new List<string>();
var enumList = Enum.GetValues(typeof(StringMatcher.SearchPrecisionScore)).Cast<StringMatcher.SearchPrecisionScore>().ToList();
var enumList = Enum.GetValues(typeof(SearchPrecisionScore)).Cast<SearchPrecisionScore>().ToList();
enumList.ForEach(x => precisionStrings.Add(x.ToString()));

View file

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin.SharedModels;
namespace Flow.Launcher.Plugin.BrowserBookmark.Commands
{

View file

@ -7,12 +7,13 @@ using System.Windows;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin.Explorer.Search;
using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
using System.Linq;
using MessageBox = System.Windows.Forms.MessageBox;
using MessageBoxIcon = System.Windows.Forms.MessageBoxIcon;
using MessageBoxButton = System.Windows.Forms.MessageBoxButtons;
using DialogResult = System.Windows.Forms.DialogResult;
using Flow.Launcher.Plugin.Explorer.ViewModels;
namespace Flow.Launcher.Plugin.Explorer
{
@ -22,10 +23,13 @@ namespace Flow.Launcher.Plugin.Explorer
private Settings Settings { get; set; }
public ContextMenu(PluginInitContext context, Settings settings)
private SettingsViewModel ViewModel { get; set; }
public ContextMenu(PluginInitContext context, Settings settings, SettingsViewModel vm)
{
Context = context;
Settings = settings;
ViewModel = vm;
}
public List<Result> LoadContextMenus(Result selectedResult)
@ -50,6 +54,58 @@ namespace Flow.Launcher.Plugin.Explorer
var icoPath = (record.Type == ResultType.File) ? Constants.FileImagePath : Constants.FolderImagePath;
var fileOrFolder = (record.Type == ResultType.File) ? "file" : "folder";
if (!Settings.QuickAccessLinks.Any(x => x.Path == record.FullPath))
{
contextMenus.Add(new Result
{
Title = Context.API.GetTranslation("plugin_explorer_add_to_quickaccess_title"),
SubTitle = string.Format(Context.API.GetTranslation("plugin_explorer_add_to_quickaccess_subtitle"), fileOrFolder),
Action = (context) =>
{
Settings.QuickAccessLinks.Add(new AccessLink { Path = record.FullPath, Type = record.Type });
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess"),
string.Format(
Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess_detail"),
fileOrFolder),
Constants.ExplorerIconImageFullPath);
ViewModel.Save();
return true;
},
SubTitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_titletooltip"),
TitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_titletooltip"),
IcoPath = Constants.QuickAccessImagePath
});
}
else
{
contextMenus.Add(new Result
{
Title = Context.API.GetTranslation("plugin_explorer_remove_from_quickaccess_title"),
SubTitle = string.Format(Context.API.GetTranslation("plugin_explorer_remove_from_quickaccess_subtitle"), fileOrFolder),
Action = (context) =>
{
Settings.QuickAccessLinks.Remove(Settings.QuickAccessLinks.FirstOrDefault(x => x.Path == record.FullPath));
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess"),
string.Format(
Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess_detail"),
fileOrFolder),
Constants.ExplorerIconImageFullPath);
ViewModel.Save();
return true;
},
SubTitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_remove_titletooltip"),
TitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_remove_titletooltip"),
IcoPath = Constants.RemoveQuickAccessImagePath
});
}
contextMenus.Add(new Result
{
Title = Context.API.GetTranslation("plugin_explorer_copypath"),
@ -228,7 +284,7 @@ namespace Flow.Launcher.Plugin.Explorer
Action = _ =>
{
if(!Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x.Path == record.FullPath))
Settings.IndexSearchExcludedSubdirectoryPaths.Add(new FolderLink { Path = record.FullPath });
Settings.IndexSearchExcludedSubdirectoryPaths.Add(new AccessLink { Path = record.FullPath });
Task.Run(() =>
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -16,7 +16,7 @@
<system:String x:Key="plugin_explorer_edit">Edit</system:String>
<system:String x:Key="plugin_explorer_add">Add</system:String>
<system:String x:Key="plugin_explorer_manageactionkeywords_header">Customise Action Keywords</system:String>
<system:String x:Key="plugin_explorer_quickfolderaccess_header">Quick Folder Access Paths</system:String>
<system:String x:Key="plugin_explorer_quickaccesslinks_header">Quick Access Links</system:String>
<system:String x:Key="plugin_explorer_indexsearchexcludedpaths_header">Index Search Excluded Paths</system:String>
<system:String x:Key="plugin_explorer_manageindexoptions">Indexing Options</system:String>
<system:String x:Key="plugin_explorer_actionkeywordview_search">Search Activation:</system:String>
@ -42,5 +42,15 @@
<system:String x:Key="plugin_explorer_openindexingoptions">Open Windows Indexing Options</system:String>
<system:String x:Key="plugin_explorer_openindexingoptions_subtitle">Manage indexed files and folders</system:String>
<system:String x:Key="plugin_explorer_openindexingoptions_errormsg">Failed to open Windows Indexing Options</system:String>
<system:String x:Key="plugin_explorer_add_to_quickaccess_title">Add to Quick Access</system:String>
<system:String x:Key="plugin_explorer_add_to_quickaccess_subtitle">Add the current {0} to Quick Access</system:String>
<system:String x:Key="plugin_explorer_addfilefoldersuccess">Successfully Added</system:String>
<system:String x:Key="plugin_explorer_addfilefoldersuccess_detail">Successfully added to Quick Access</system:String>
<system:String x:Key="plugin_explorer_removefilefoldersuccess">Successfully Removed</system:String>
<system:String x:Key="plugin_explorer_removefilefoldersuccess_detail">Successfully removed from Quick Access</system:String>
<system:String x:Key="plugin_explorer_contextmenu_titletooltip">Add to Quick Access so it can be opened with Explorer's Search Activation action keyword</system:String>
<system:String x:Key="plugin_explorer_contextmenu_remove_titletooltip">Remove from Quick Access</system:String>
<system:String x:Key="plugin_explorer_remove_from_quickaccess_title">Remove from Quick Access</system:String>
<system:String x:Key="plugin_explorer_remove_from_quickaccess_subtitle">Remove the current {0} from Quick Access</system:String>
</ResourceDictionary>

View file

@ -1,8 +1,10 @@
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin.Explorer.Search;
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
using Flow.Launcher.Plugin.Explorer.ViewModels;
using Flow.Launcher.Plugin.Explorer.Views;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
@ -32,8 +34,17 @@ namespace Flow.Launcher.Plugin.Explorer
viewModel = new SettingsViewModel(context);
await viewModel.LoadStorage();
Settings = viewModel.Settings;
contextMenu = new ContextMenu(Context, Settings);
// as at v1.7.0 this is to maintain backwards compatibility, need to be removed afterwards.
if (Settings.QuickFolderAccessLinks.Any())
{
Settings.QuickAccessLinks = Settings.QuickFolderAccessLinks;
Settings.QuickFolderAccessLinks = new List<AccessLink>();
}
contextMenu = new ContextMenu(Context, Settings, viewModel);
searchManager = new SearchManager(Settings, Context);
ResultManager.Init(Context);
}
public List<Result> LoadContextMenus(Result selectedResult)

View file

@ -15,6 +15,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
internal const string ExplorerIconImagePath = "Images\\explorer.png";
internal const string DifferentUserIconImagePath = "Images\\user.png";
internal const string IndexingOptionsIconImagePath = "Images\\windowsindexingoptions.png";
internal const string QuickAccessImagePath = "Images\\quickaccess.png";
internal const string RemoveQuickAccessImagePath = "Images\\removequickaccess.png";
internal const string ToolTipOpenDirectory = "Ctrl + Enter to open the directory";

View file

@ -4,29 +4,27 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
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)
internal static List<Result> TopLevelDirectorySearch(Query query, string search, CancellationToken token)
{
var criteria = ConstructSearchCriteria(search);
if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > search.LastIndexOf(Constants.DirectorySeperator))
return DirectorySearch(SearchOption.AllDirectories, query, search, criteria);
if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) >
search.LastIndexOf(Constants.DirectorySeperator))
return DirectorySearch(new EnumerationOptions
{
RecurseSubdirectories = true
}, query, search, criteria, token);
return DirectorySearch(SearchOption.TopDirectoryOnly, query, search, criteria);
return DirectorySearch(new EnumerationOptions(), query, search, criteria, token); // null will be passed as default
}
public string ConstructSearchCriteria(string search)
public static string ConstructSearchCriteria(string search)
{
string incompleteName = "";
@ -45,7 +43,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
return incompleteName;
}
private List<Result> DirectorySearch(SearchOption searchOption, Query query, string search, string searchCriteria)
private static List<Result> DirectorySearch(EnumerationOptions enumerationOption, Query query, string search,
string searchCriteria, CancellationToken token)
{
var results = new List<Result>();
@ -58,38 +57,38 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
{
var directoryInfo = new System.IO.DirectoryInfo(path);
foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, searchOption))
foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, enumerationOption))
{
if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue;
if (fileSystemInfo is System.IO.DirectoryInfo)
{
folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, fileSystemInfo.FullName, query, true, false));
folderList.Add(ResultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName,
fileSystemInfo.FullName, query, true, false));
}
else
{
fileList.Add(resultManager.CreateFileResult(fileSystemInfo.FullName, query, true, false));
fileList.Add(ResultManager.CreateFileResult(fileSystemInfo.FullName, query, true, false));
}
token.ThrowIfCancellationRequested();
}
}
catch (Exception e)
{
if (e is UnauthorizedAccessException || e is ArgumentException)
{
results.Add(new Result { Title = e.Message, Score = 501 });
if (!(e is ArgumentException))
throw e;
results.Add(new Result {Title = e.Message, Score = 501});
return results;
}
return results;
#if DEBUG // Please investigate and handle error from DirectoryInfo search
throw e;
#else
Log.Exception($"|Flow.Launcher.Plugin.Explorer.DirectoryInfoSearch|Error from performing DirectoryInfoSearch", e);
#endif
#endif
}
// Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
// Initial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList();
}
}
}
}

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

@ -1,30 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks
{
public class QuickFolderAccess
{
internal List<Result> FolderListMatched(Query query, List<FolderLink> folderLinks, PluginInitContext context)
{
if (string.IsNullOrEmpty(query.Search))
return new List<Result>();
string search = query.Search.ToLower();
var queriedFolderLinks = folderLinks.Where(x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase));
return queriedFolderLinks.Select(item =>
new ResultManager(context)
.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
.ToList();
}
internal List<Result> FolderListAll(Query query, List<FolderLink> folderLinks, PluginInitContext context)
=> folderLinks
.Select(item =>
new ResultManager(context).CreateFolderResult(item.Nickname, item.Path, item.Path, query))
.ToList();
}
}

View file

@ -1,14 +1,15 @@
using System;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks
namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks
{
public class FolderLink
public class AccessLink
{
public string Path { get; set; }
public ResultType Type { get; set; } = ResultType.Folder;
[JsonIgnore]
public string Nickname
{

View file

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks
{
internal static class QuickAccess
{
internal static List<Result> AccessLinkListMatched(Query query, List<AccessLink> accessLinks)
{
if (string.IsNullOrEmpty(query.Search))
return new List<Result>();
string search = query.Search.ToLower();
var queriedAccessLinks =
accessLinks
.Where(x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase))
.OrderBy(x => x.Type)
.ThenBy(x => x.Nickname);
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),
_ => throw new ArgumentOutOfRangeException()
}).ToList();
}
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),
_ => 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
{
@ -140,7 +142,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
public bool ShowIndexState { get; set; }
}
internal enum ResultType
public enum ResultType
{
Volume,
Folder,

View file

@ -1,5 +1,5 @@
using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo;
using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex;
using Flow.Launcher.Plugin.SharedCommands;
using System;
@ -14,19 +14,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
private readonly PluginInitContext context;
private readonly IndexSearch indexSearch;
private readonly QuickFolderAccess quickFolderAccess = new QuickFolderAccess();
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;
}
@ -40,15 +32,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return await WindowsIndexFileContentSearchAsync(query, querySearch, token).ConfigureAwait(false);
// This allows the user to type the assigned action keyword and only see the list of quick folder links
if (settings.QuickFolderAccessLinks.Count > 0
&& query.ActionKeyword == settings.SearchActionKeyword
&& string.IsNullOrEmpty(query.Search))
return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks, context);
if (string.IsNullOrEmpty(query.Search))
return QuickAccess.AccessLinkListAll(query, settings.QuickAccessLinks);
var quickFolderLinks = quickFolderAccess.FolderListMatched(query, settings.QuickFolderAccessLinks, context);
var quickaccessLinks = QuickAccess.AccessLinkListMatched(query, settings.QuickAccessLinks);
if (quickFolderLinks.Count > 0)
results.AddRange(quickFolderLinks);
if (quickaccessLinks.Count > 0)
results.AddRange(quickaccessLinks);
var isEnvironmentVariable = EnvironmentVariables.IsEnvironmentVariableSearch(querySearch);
@ -58,7 +48,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
// Query is a location path with a full environment variable, eg. %appdata%\somefolder\
var isEnvironmentVariablePath = querySearch[1..].Contains("%\\");
if (!FilesFolders.IsLocationPathString(querySearch) && !isEnvironmentVariablePath)
if (!querySearch.IsLocationPathString() && !isEnvironmentVariablePath)
{
results.AddRange(await WindowsIndexFilesAndFoldersSearchAsync(query, querySearch, token).ConfigureAwait(false));
@ -70,22 +60,26 @@ namespace Flow.Launcher.Plugin.Explorer.Search
if (isEnvironmentVariablePath)
locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath);
// Check that actual location exists, otherwise directory search will throw directory not found exception
if (!FilesFolders.LocationExists(FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath)))
return results;
var useIndexSearch = UseWindowsIndexForDirectorySearch(locationPath);
results.Add(resultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch));
results.Add(ResultManager.CreateOpenCurrentFolderResult(locationPath, useIndexSearch));
if (token.IsCancellationRequested)
return null;
token.ThrowIfCancellationRequested();
results.AddRange(await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync,
DirectoryInfoClassSearch,
useIndexSearch,
query,
locationPath,
token).ConfigureAwait(false));
var directoryResult = await TopLevelDirectorySearchBehaviourAsync(WindowsIndexTopLevelFolderSearchAsync,
DirectoryInfoClassSearch,
useIndexSearch,
query,
locationPath,
token).ConfigureAwait(false);
token.ThrowIfCancellationRequested();
results.AddRange(directoryResult);
return results;
}
@ -97,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,
@ -109,23 +103,21 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return actionKeyword == settings.FileContentSearchActionKeyword;
}
private List<Result> DirectoryInfoClassSearch(Query query, string querySearch)
private List<Result> DirectoryInfoClassSearch(Query query, string querySearch, CancellationToken token)
{
var directoryInfoSearch = new DirectoryInfoSearch(context);
return directoryInfoSearch.TopLevelDirectorySearch(query, querySearch);
return DirectoryInfoSearch.TopLevelDirectorySearch(query, querySearch, token);
}
public async Task<List<Result>> TopLevelDirectorySearchBehaviourAsync(
Func<Query, string, CancellationToken, Task<List<Result>>> windowsIndexSearch,
Func<Query, string, List<Result>> directoryInfoClassSearch,
Func<Query, string, CancellationToken, List<Result>> directoryInfoClassSearch,
bool useIndexSearch,
Query query,
string querySearchString,
CancellationToken token)
{
if (!useIndexSearch)
return directoryInfoClassSearch(query, querySearchString);
return directoryInfoClassSearch(query, querySearchString, token);
return await windowsIndexSearch(query, querySearchString, token);
}
@ -134,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,
@ -145,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,
@ -164,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

@ -1,4 +1,4 @@
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Logger;
using Microsoft.Search.Interop;
using System;
using System.Collections.Generic;
@ -10,23 +10,16 @@ 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)
internal async static Task<List<Result>> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token)
{
resultManager = new ResultManager(context);
}
internal async Task<List<Result>> ExecuteWindowsIndexSearchAsync(string indexQueryString, string connectionString, Query query, CancellationToken token)
{
var folderResults = new List<Result>();
var fileResults = new List<Result>();
var results = new List<Result>();
var fileResults = new List<Result>();
try
{
@ -55,7 +48,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
if (dataReaderResults.GetString(2) == "Directory")
{
folderResults.Add(resultManager.CreateFolderResult(
results.Add(ResultManager.CreateFolderResult(
dataReaderResults.GetString(0),
path,
path,
@ -63,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));
}
}
}
@ -83,11 +76,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
LogException("General error from performing index search", e);
}
results.AddRange(fileResults);
// Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
return results.Concat(folderResults.OrderBy(x => x.Title)).Concat(fileResults.OrderBy(x => x.Title)).ToList(); ;
return results;
}
internal async Task<List<Result>> WindowsIndexSearchAsync(string searchString, string connectionString,
internal async static Task<List<Result>> WindowsIndexSearchAsync(string searchString, string connectionString,
Func<string, string> constructQuery, Query query,
CancellationToken token)
{
@ -101,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

@ -42,7 +42,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
// Get the ISearchQueryHelper which will help us to translate AQS --> SQL necessary to query the indexer
var queryHelper = catalogManager.GetQueryHelper();
return queryHelper;
}
@ -81,11 +81,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
var previousLevelDirectory = path.Substring(0, indexOfSeparator);
if (string.IsNullOrEmpty(itemName))
return searchDepth + $"{previousLevelDirectory}'";
return $"{searchDepth}{previousLevelDirectory}'";
return $"(System.FileName LIKE '{itemName}%' " +
$"OR CONTAINS(System.FileName,'\"{itemName}*\"',1033)) AND " +
searchDepth + $"{previousLevelDirectory}'";
return $"(System.FileName LIKE '{itemName}%' OR CONTAINS(System.FileName,'\"{itemName}*\"',1033)) AND {searchDepth}{previousLevelDirectory}'";
}
///<summary>
@ -96,9 +94,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
string query = "SELECT TOP " + settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE ";
if (path.LastIndexOf(Constants.AllFilesFolderSearchWildcard) > path.LastIndexOf(Constants.DirectorySeperator))
return query + QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path);
return query + QueryWhereRestrictionsForTopLevelDirectoryAllFilesAndFoldersSearch(path) + QueryOrderByFileNameRestriction;
return query + QueryWhereRestrictionsForTopLevelDirectorySearch(path);
return query + QueryWhereRestrictionsForTopLevelDirectorySearch(path) + QueryOrderByFileNameRestriction;
}
///<summary>
@ -107,16 +105,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
public string QueryForAllFilesAndFolders(string userSearchString)
{
// Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause
return CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch();
return CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch
+ QueryOrderByFileNameRestriction;
}
///<summary>
/// Set the required WHERE clause restriction to search for all files and folders.
///</summary>
public string QueryWhereRestrictionsForAllFilesAndFoldersSearch()
{
return $"scope='file:'";
}
public const string QueryWhereRestrictionsForAllFilesAndFoldersSearch = "scope='file:'";
public const string QueryOrderByFileNameRestriction = " ORDER BY System.FileName";
///<summary>
/// Search will be performed on all indexed file contents for the specified search keywords.
@ -125,7 +124,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
{
string query = "SELECT TOP " + settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE ";
return query + QueryWhereRestrictionsForFileContentSearch(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch();
return query + QueryWhereRestrictionsForFileContentSearch(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch
+ QueryOrderByFileNameRestriction;
}
///<summary>

View file

@ -1,7 +1,6 @@
using Flow.Launcher.Plugin.Explorer.Search;
using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Flow.Launcher.Plugin.Explorer
{
@ -9,11 +8,14 @@ namespace Flow.Launcher.Plugin.Explorer
{
public int MaxResult { get; set; } = 100;
public List<FolderLink> QuickFolderAccessLinks { get; set; } = new List<FolderLink>();
public List<AccessLink> QuickAccessLinks { get; set; } = new List<AccessLink>();
// as at v1.7.0 this is to maintain backwards compatibility, need to be removed afterwards.
public List<AccessLink> QuickFolderAccessLinks { get; set; } = new List<AccessLink>();
public bool UseWindowsIndexForDirectorySearch { get; set; } = true;
public List<FolderLink> IndexSearchExcludedSubdirectoryPaths { get; set; } = new List<FolderLink>();
public List<AccessLink> IndexSearchExcludedSubdirectoryPaths { get; set; } = new List<AccessLink>();
public string SearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign;

View file

@ -1,7 +1,7 @@
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin.Explorer.Search;
using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
using System.Diagnostics;
using System.Threading.Tasks;
@ -32,9 +32,9 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
storage.Save();
}
internal void RemoveFolderLinkFromQuickFolders(FolderLink selectedRow) => Settings.QuickFolderAccessLinks.Remove(selectedRow);
internal void RemoveLinkFromQuickAccess(AccessLink selectedRow) => Settings.QuickAccessLinks.Remove(selectedRow);
internal void RemoveFolderLinkFromExcludedIndexPaths(FolderLink selectedRow) => Settings.IndexSearchExcludedSubdirectoryPaths.Remove(selectedRow);
internal void RemoveAccessLinkFromExcludedIndexPaths(AccessLink selectedRow) => Settings.IndexSearchExcludedSubdirectoryPaths.Remove(selectedRow);
internal void OpenWindowsIndexingOptions()
{

View file

@ -7,7 +7,7 @@
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate x:Key="ListViewTemplateFolderLinks">
<DataTemplate x:Key="ListViewTemplateAccessLinks">
<TextBlock
Text="{Binding Nickname, Mode=OneTime}"
Margin="0,5,0,5" />
@ -40,22 +40,22 @@
<ListView x:Name="lbxActionKeywords"
ItemTemplate="{StaticResource ListViewActionKeywords}"/>
</Expander>
<Expander Name="expFolderLinks" Header="{DynamicResource plugin_explorer_quickfolderaccess_header}"
Expanded="expFolderLinks_Click" Collapsed="expFolderLinks_Collapsed"
<Expander Name="expAccessLinks" Header="{DynamicResource plugin_explorer_quickaccesslinks_header}"
Expanded="expAccessLinks_Click" Collapsed="expAccessLinks_Collapsed"
Margin="0 10 0 0">
<ListView
x:Name="lbxFolderLinks" AllowDrop="True"
Drop="lbxFolders_Drop"
DragEnter="lbxFolders_DragEnter"
ItemTemplate="{StaticResource ListViewTemplateFolderLinks}"/>
x:Name="lbxAccessLinks" AllowDrop="True"
Drop="lbxAccessLinks_Drop"
DragEnter="lbxAccessLinks_DragEnter"
ItemTemplate="{StaticResource ListViewTemplateAccessLinks}"/>
</Expander>
<Expander x:Name="expExcludedPaths" Header="{DynamicResource plugin_explorer_indexsearchexcludedpaths_header}"
Expanded="expExcludedPaths_Click"
Margin="0 10 0 0">
<ListView
x:Name="lbxExcludedPaths" AllowDrop="True"
Drop="lbxFolders_Drop"
DragEnter="lbxFolders_DragEnter"
Drop="lbxAccessLinks_Drop"
DragEnter="lbxAccessLinks_DragEnter"
ItemTemplate="{StaticResource ListViewTemplateExcludedPaths}"/>
</Expander>
</StackPanel>

View file

@ -1,4 +1,4 @@
using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
using Flow.Launcher.Plugin.Explorer.ViewModels;
using System;
using System.Collections.Generic;
@ -29,7 +29,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
this.viewModel = viewModel;
lbxFolderLinks.ItemsSource = this.viewModel.Settings.QuickFolderAccessLinks;
lbxAccessLinks.ItemsSource = this.viewModel.Settings.QuickAccessLinks;
lbxExcludedPaths.ItemsSource = this.viewModel.Settings.IndexSearchExcludedSubdirectoryPaths;
@ -54,7 +54,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
public void RefreshView()
{
lbxFolderLinks.Items.SortDescriptions.Add(new SortDescription("Path", ListSortDirection.Ascending));
lbxAccessLinks.Items.SortDescriptions.Add(new SortDescription("Path", ListSortDirection.Ascending));
lbxExcludedPaths.Items.SortDescriptions.Add(new SortDescription("Path", ListSortDirection.Ascending));
@ -62,7 +62,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
btnEdit.Visibility = Visibility.Hidden;
btnAdd.Visibility = Visibility.Hidden;
if (expFolderLinks.IsExpanded || expExcludedPaths.IsExpanded || expActionKeywords.IsExpanded)
if (expAccessLinks.IsExpanded || expExcludedPaths.IsExpanded || expActionKeywords.IsExpanded)
{
if (!expActionKeywords.IsExpanded)
btnAdd.Visibility = Visibility.Visible;
@ -71,7 +71,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
&& btnEdit.Visibility == Visibility.Hidden)
btnEdit.Visibility = Visibility.Visible;
if ((lbxFolderLinks.Items.Count == 0 && lbxExcludedPaths.Items.Count == 0)
if ((lbxAccessLinks.Items.Count == 0 && lbxExcludedPaths.Items.Count == 0)
&& btnDelete.Visibility == Visibility.Visible
&& btnEdit.Visibility == Visibility.Visible)
{
@ -79,8 +79,8 @@ namespace Flow.Launcher.Plugin.Explorer.Views
btnEdit.Visibility = Visibility.Hidden;
}
if (expFolderLinks.IsExpanded
&& lbxFolderLinks.Items.Count > 0
if (expAccessLinks.IsExpanded
&& lbxAccessLinks.Items.Count > 0
&& btnDelete.Visibility == Visibility.Hidden
&& btnEdit.Visibility == Visibility.Hidden)
{
@ -98,7 +98,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
}
}
lbxFolderLinks.Items.Refresh();
lbxAccessLinks.Items.Refresh();
lbxExcludedPaths.Items.Refresh();
@ -113,8 +113,8 @@ namespace Flow.Launcher.Plugin.Explorer.Views
if (expExcludedPaths.IsExpanded)
expExcludedPaths.IsExpanded = false;
if (expFolderLinks.IsExpanded)
expFolderLinks.IsExpanded = false;
if (expAccessLinks.IsExpanded)
expAccessLinks.IsExpanded = false;
RefreshView();
}
@ -125,10 +125,10 @@ namespace Flow.Launcher.Plugin.Explorer.Views
expActionKeywords.Height = Double.NaN;
}
private void expFolderLinks_Click(object sender, RoutedEventArgs e)
private void expAccessLinks_Click(object sender, RoutedEventArgs e)
{
if (expFolderLinks.IsExpanded)
expFolderLinks.Height = 215;
if (expAccessLinks.IsExpanded)
expAccessLinks.Height = 215;
if (expExcludedPaths.IsExpanded)
expExcludedPaths.IsExpanded = false;
@ -139,19 +139,19 @@ namespace Flow.Launcher.Plugin.Explorer.Views
RefreshView();
}
private void expFolderLinks_Collapsed(object sender, RoutedEventArgs e)
private void expAccessLinks_Collapsed(object sender, RoutedEventArgs e)
{
if (!expFolderLinks.IsExpanded)
expFolderLinks.Height = Double.NaN;
if (!expAccessLinks.IsExpanded)
expAccessLinks.Height = Double.NaN;
}
private void expExcludedPaths_Click(object sender, RoutedEventArgs e)
{
if (expExcludedPaths.IsExpanded)
expFolderLinks.Height = Double.NaN;
expAccessLinks.Height = Double.NaN;
if (expFolderLinks.IsExpanded)
expFolderLinks.IsExpanded = false;
if (expAccessLinks.IsExpanded)
expAccessLinks.IsExpanded = false;
if (expActionKeywords.IsExpanded)
expActionKeywords.IsExpanded = false;
@ -161,7 +161,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
var selectedRow = lbxFolderLinks.SelectedItem as FolderLink?? lbxExcludedPaths.SelectedItem as FolderLink;
var selectedRow = lbxAccessLinks.SelectedItem as AccessLink?? lbxExcludedPaths.SelectedItem as AccessLink;
if (selectedRow != null)
{
@ -169,11 +169,11 @@ namespace Flow.Launcher.Plugin.Explorer.Views
if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
if (expFolderLinks.IsExpanded)
viewModel.RemoveFolderLinkFromQuickFolders(selectedRow);
if (expAccessLinks.IsExpanded)
viewModel.RemoveLinkFromQuickAccess(selectedRow);
if (expExcludedPaths.IsExpanded)
viewModel.RemoveFolderLinkFromExcludedIndexPaths(selectedRow);
viewModel.RemoveAccessLinkFromExcludedIndexPaths(selectedRow);
RefreshView();
}
@ -199,7 +199,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
}
else
{
var selectedRow = lbxFolderLinks.SelectedItem as FolderLink ?? lbxExcludedPaths.SelectedItem as FolderLink;
var selectedRow = lbxAccessLinks.SelectedItem as AccessLink ?? lbxExcludedPaths.SelectedItem as AccessLink;
if (selectedRow != null)
{
@ -207,9 +207,9 @@ namespace Flow.Launcher.Plugin.Explorer.Views
folderBrowserDialog.SelectedPath = selectedRow.Path;
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
{
if (expFolderLinks.IsExpanded)
if (expAccessLinks.IsExpanded)
{
var link = viewModel.Settings.QuickFolderAccessLinks.First(x => x.Path == selectedRow.Path);
var link = viewModel.Settings.QuickAccessLinks.First(x => x.Path == selectedRow.Path);
link.Path = folderBrowserDialog.SelectedPath;
}
@ -235,36 +235,36 @@ namespace Flow.Launcher.Plugin.Explorer.Views
var folderBrowserDialog = new FolderBrowserDialog();
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
{
var newFolderLink = new FolderLink
var newAccessLink = new AccessLink
{
Path = folderBrowserDialog.SelectedPath
};
AddFolderLink(newFolderLink);
AddAccessLink(newAccessLink);
}
RefreshView();
}
private void lbxFolders_Drop(object sender, DragEventArgs e)
private void lbxAccessLinks_Drop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files != null && files.Count() > 0)
{
if (expFolderLinks.IsExpanded && viewModel.Settings.QuickFolderAccessLinks == null)
viewModel.Settings.QuickFolderAccessLinks = new List<FolderLink>();
if (expAccessLinks.IsExpanded && viewModel.Settings.QuickAccessLinks == null)
viewModel.Settings.QuickAccessLinks = new List<AccessLink>();
foreach (string s in files)
{
if (Directory.Exists(s))
{
var newFolderLink = new FolderLink
var newFolderLink = new AccessLink
{
Path = s
};
AddFolderLink(newFolderLink);
AddAccessLink(newFolderLink);
}
RefreshView();
@ -272,28 +272,28 @@ namespace Flow.Launcher.Plugin.Explorer.Views
}
}
private void AddFolderLink(FolderLink newFolderLink)
private void AddAccessLink(AccessLink newAccessLink)
{
if (expFolderLinks.IsExpanded
&& !viewModel.Settings.QuickFolderAccessLinks.Any(x => x.Path == newFolderLink.Path))
if (expAccessLinks.IsExpanded
&& !viewModel.Settings.QuickAccessLinks.Any(x => x.Path == newAccessLink.Path))
{
if (viewModel.Settings.QuickFolderAccessLinks == null)
viewModel.Settings.QuickFolderAccessLinks = new List<FolderLink>();
if (viewModel.Settings.QuickAccessLinks == null)
viewModel.Settings.QuickAccessLinks = new List<AccessLink>();
viewModel.Settings.QuickFolderAccessLinks.Add(newFolderLink);
viewModel.Settings.QuickAccessLinks.Add(newAccessLink);
}
if (expExcludedPaths.IsExpanded
&& !viewModel.Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x.Path == newFolderLink.Path))
&& !viewModel.Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => x.Path == newAccessLink.Path))
{
if (viewModel.Settings.IndexSearchExcludedSubdirectoryPaths == null)
viewModel.Settings.IndexSearchExcludedSubdirectoryPaths = new List<FolderLink>();
viewModel.Settings.IndexSearchExcludedSubdirectoryPaths = new List<AccessLink>();
viewModel.Settings.IndexSearchExcludedSubdirectoryPaths.Add(newFolderLink);
viewModel.Settings.IndexSearchExcludedSubdirectoryPaths.Add(newAccessLink);
}
}
private void lbxFolders_DragEnter(object sender, DragEventArgs e)
private void lbxAccessLinks_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{

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.3.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>

View file

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;

View file

@ -2,6 +2,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="flowlauncher_plugin_url_open_search_in">Open search in:</system:String>
<system:String x:Key="flowlauncher_plugin_new_window">New Window</system:String>
<system:String x:Key="flowlauncher_plugin_new_tab">New Tab</system:String>
<system:String x:Key="flowlauncher_plugin_url_open_url">Open url:{0}</system:String>
<system:String x:Key="flowlauncher_plugin_url_canot_open_url">Can't open url:{0}</system:String>

View file

@ -2,6 +2,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="flowlauncher_plugin_url_open_search_in">Otvoriť vyhľadávanie v:</system:String>
<system:String x:Key="flowlauncher_plugin_new_window">Nové okno</system:String>
<system:String x:Key="flowlauncher_plugin_new_tab">Nová karta</system:String>
<system:String x:Key="flowlauncher_plugin_url_open_url">Otvoriť url:{0}</system:String>
<system:String x:Key="flowlauncher_plugin_url_canot_open_url">Adresa URL sa nedá otvoriť: {0}</system:String>

View file

@ -10,11 +10,11 @@
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<Label Content="Open search in:" Margin="0 10 15 0" />
<Label Content="{DynamicResource flowlauncher_plugin_url_open_search_in}" Margin="0 10 15 0" />
<RadioButton x:Name="NewWindowBrowser" GroupName="OpenSearchBehaviour"
Content="New window" Click="OnNewBrowserWindowClick" />
Content="{DynamicResource flowlauncher_plugin_new_window}" Click="OnNewBrowserWindowClick" />
<RadioButton x:Name="NewTabInBrowser" GroupName="OpenSearchBehaviour"
Content="New tab" Click="OnNewTabClick" />
Content="{DynamicResource flowlauncher_plugin_new_tab}" Click="OnNewTabClick" />
</StackPanel>
<StackPanel VerticalAlignment="Top" Grid.Row="1" Height="106" Margin="0 20 0 0">
<Label Content="{DynamicResource flowlauncher_plugin_url_plugin_set_tip}" Height="28" Margin="0,0,155,0"

View file

@ -4,7 +4,7 @@
"Name": "URL",
"Description": "Open the typed URL from Flow Launcher",
"Author": "qianlifeng",
"Version": "1.1.2",
"Version": "1.1.4",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Url.dll",

View file

@ -2,6 +2,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="flowlauncher_plugin_websearch_open_search_in">Open search in:</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_new_window">New Window</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_new_tab">New Tab</system:String>
<system:String x:Key="flowlaucnher_plugin_websearch_set_browser_path">Set browser from path:</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_choose">Choose</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_delete">Delete</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_edit">Edit</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_add">Add</system:String>

View file

@ -1,7 +1,11 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="flowlauncher_plugin_websearch_open_search_in">Otvoriť vyhľadávanie v:</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_new_window">Nové okno</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_new_tab">Nová karta</system:String>
<system:String x:Key="flowlaucnher_plugin_websearch_set_browser_path">Zadajte cestu k prehliadaču:</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_choose">Vybrať</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_delete">Odstrániť</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_edit">Upraviť</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_add">Pridať</system:String>

View file

@ -33,17 +33,17 @@
<!-- Not sure why binding IsEnabled directly to Settings.EnableWebSaerchSuggestion is not working -->
</StackPanel>
<StackPanel Grid.Row="1" HorizontalAlignment="Left" Orientation="Horizontal">
<Label Content="Open search in:" Margin="0 15 20 0"/>
<RadioButton Name="NewWindowBrowser" GroupName="OpenSearchBehaviour" Content="New window" Click="OnNewBrowserWindowClick"
<Label Content="{DynamicResource flowlauncher_plugin_websearch_open_search_in}" Margin="0 15 20 0"/>
<RadioButton Name="NewWindowBrowser" GroupName="OpenSearchBehaviour" Content="{DynamicResource flowlauncher_plugin_websearch_new_window}" Click="OnNewBrowserWindowClick"
Margin="0 0 20 0"/>
<RadioButton Name="NewTabInBrowser" GroupName="OpenSearchBehaviour" Content="New tab" Click="OnNewTabClick" />
<RadioButton Name="NewTabInBrowser" GroupName="OpenSearchBehaviour" Content="{DynamicResource flowlauncher_plugin_websearch_new_tab}" Click="OnNewTabClick" />
</StackPanel>
<StackPanel Grid.Row="2" HorizontalAlignment="Left" Margin="0 3 0 0">
<Label Content="Set browser from path:" Margin="0 0 350 0" HorizontalAlignment="Left" Width="140"/>
<Label Content="{DynamicResource flowlaucnher_plugin_websearch_set_browser_path}" Margin="0 0 350 0" HorizontalAlignment="Left" Width="140"/>
<TextBox x:Name="browserPathBox" HorizontalAlignment="Left" Margin="160,-22,0,0" TextChanged="OnBrowserPathTextChanged"
Width="214" Style="{StaticResource BrowserPathBoxStyle}"/>
<Button x:Name="viewButton" HorizontalAlignment="Left" Margin="400,-30,0,0"
Click="OnChooseClick" FontSize="13" Content="Choose" Width="80"/>
Click="OnChooseClick" FontSize="13" Content="{DynamicResource flowlauncher_plugin_websearch_choose}" Width="80"/>
</StackPanel>
<ListView ItemsSource="{Binding Settings.SearchSources}"
SelectedItem="{Binding Settings.SelectedSearchSource}"

View file

@ -25,7 +25,7 @@
"Name": "Web Searches",
"Description": "Provide the web search ability",
"Author": "qianlifeng",
"Version": "1.3.0",
"Version": "1.3.2",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.WebSearch.dll",