mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev'
This commit is contained in:
commit
21fae1f457
28 changed files with 707 additions and 340 deletions
|
|
@ -191,9 +191,14 @@ namespace Wox.Plugin.Folder
|
|||
if (incompleteName.StartsWith(">"))
|
||||
{
|
||||
searchOption = SearchOption.AllDirectories;
|
||||
incompleteName = incompleteName.Substring(1);
|
||||
|
||||
// match everything before and after search term using supported wildcard '*', ie. *searchterm*
|
||||
incompleteName = "*" + incompleteName.Substring(1);
|
||||
}
|
||||
|
||||
|
||||
var folderList = new List<Result>();
|
||||
var fileList = new List<Result>();
|
||||
|
||||
try
|
||||
{
|
||||
// search folder and add results
|
||||
|
|
@ -204,11 +209,14 @@ namespace Wox.Plugin.Folder
|
|||
{
|
||||
if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue;
|
||||
|
||||
var result =
|
||||
fileSystemInfo is DirectoryInfo
|
||||
? CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, query)
|
||||
: CreateFileResult(fileSystemInfo.FullName, query);
|
||||
results.Add(result);
|
||||
if(fileSystemInfo is DirectoryInfo)
|
||||
{
|
||||
folderList.Add(CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, query));
|
||||
}
|
||||
else
|
||||
{
|
||||
fileList.Add(CreateFileResult(fileSystemInfo.FullName, query));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
@ -223,7 +231,8 @@ namespace Wox.Plugin.Folder
|
|||
throw;
|
||||
}
|
||||
|
||||
return results;
|
||||
// Intial 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();
|
||||
}
|
||||
|
||||
private static Result CreateFileResult(string filePath, Query query)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
WoX
|
||||
===
|
||||
|
||||

|
||||

|
||||
[](https://github.com/jjw24/Wox/releases/latest)
|
||||

|
||||

|
||||
[](https://dev.azure.com/Wox-Launcher/Wox/_build/latest?definitionId=1&branchName=master)
|
||||
[](https://github.com/Wox-launcher/Wox/releases)
|
||||
[](https://github.com/jjw24/Wox/releases)
|
||||
[](https://github.com/LunaGao/BlessYourCodeTag)
|
||||
|
||||
**WoX** is a launcher for Windows that simply works. It's an alternative to [Alfred](https://www.alfredapp.com/) and [Launchy](http://www.launchy.net/). You can call it Windows omni-eXecutor if you want a long name.
|
||||
|
|
|
|||
|
|
@ -16,18 +16,23 @@ using Wox.Infrastructure.Logger;
|
|||
|
||||
namespace Wox.Core
|
||||
{
|
||||
public static class Updater
|
||||
public class Updater
|
||||
{
|
||||
private static readonly Internationalization Translater = InternationalizationManager.Instance;
|
||||
public string GitHubRepository { get; }
|
||||
|
||||
public static async Task UpdateApp()
|
||||
public Updater(string gitHubRepository)
|
||||
{
|
||||
GitHubRepository = gitHubRepository;
|
||||
}
|
||||
|
||||
public async Task UpdateApp()
|
||||
{
|
||||
UpdateManager m;
|
||||
UpdateInfo u;
|
||||
|
||||
try
|
||||
{
|
||||
m = await GitHubUpdateManager(Constant.Repository);
|
||||
m = await GitHubUpdateManager(GitHubRepository);
|
||||
}
|
||||
catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException)
|
||||
{
|
||||
|
|
@ -66,8 +71,8 @@ namespace Wox.Core
|
|||
await m.ApplyReleases(u);
|
||||
await m.CreateUninstallerRegistryEntry();
|
||||
|
||||
var newVersionTips = Translater.GetTranslation("newVersionTips");
|
||||
newVersionTips = string.Format(newVersionTips, fr.Version);
|
||||
var newVersionTips = this.NewVersinoTips(fr.Version.ToString());
|
||||
|
||||
MessageBox.Show(newVersionTips);
|
||||
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
|
||||
}
|
||||
|
|
@ -90,7 +95,7 @@ namespace Wox.Core
|
|||
}
|
||||
|
||||
/// https://github.com/Squirrel/Squirrel.Windows/blob/master/src/Squirrel/UpdateManager.Factory.cs
|
||||
private static async Task<UpdateManager> GitHubUpdateManager(string repository)
|
||||
private async Task<UpdateManager> GitHubUpdateManager(string repository)
|
||||
{
|
||||
var uri = new Uri(repository);
|
||||
var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases";
|
||||
|
|
@ -109,7 +114,7 @@ namespace Wox.Core
|
|||
return manager;
|
||||
}
|
||||
|
||||
public static string NewVersinoTips(string version)
|
||||
public string NewVersinoTips(string version)
|
||||
{
|
||||
var translater = InternationalizationManager.Instance;
|
||||
var tips = string.Format(translater.GetTranslation("newVersionTips"), version);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,16 @@ namespace Wox.Infrastructure.Http
|
|||
{
|
||||
private const string UserAgent = @"Mozilla/5.0 (Trident/7.0; rv:11.0) like Gecko";
|
||||
|
||||
static Http()
|
||||
{
|
||||
// need to be added so it would work on a win10 machine
|
||||
ServicePointManager.Expect100Continue = true;
|
||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls
|
||||
| SecurityProtocolType.Tls11
|
||||
| SecurityProtocolType.Tls12
|
||||
| SecurityProtocolType.Ssl3;
|
||||
}
|
||||
|
||||
public static HttpProxy Proxy { private get; set; }
|
||||
public static IWebProxy WebProxy()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -39,6 +39,19 @@ namespace Wox.Infrastructure.Image
|
|||
var contains = _data.ContainsKey(key);
|
||||
return contains;
|
||||
}
|
||||
|
||||
public int CacheSize()
|
||||
{
|
||||
return _data.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// return the number of unique images in the cache (by reference not by checking images content)
|
||||
/// </summary>
|
||||
public int UniqueImagesInCache()
|
||||
{
|
||||
return _data.Values.Distinct().Count();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
49
Wox.Infrastructure/Image/ImageHashGenerator.cs
Normal file
49
Wox.Infrastructure/Image/ImageHashGenerator.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Wox.Infrastructure.Image
|
||||
{
|
||||
public interface IImageHashGenerator
|
||||
{
|
||||
string GetHashFromImage(ImageSource image);
|
||||
}
|
||||
public class ImageHashGenerator : IImageHashGenerator
|
||||
{
|
||||
public string GetHashFromImage(ImageSource imageSource)
|
||||
{
|
||||
if (!(imageSource is BitmapSource image))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var outStream = new MemoryStream())
|
||||
{
|
||||
// PngBitmapEncoder enc2 = new PngBitmapEncoder();
|
||||
// enc2.Frames.Add(BitmapFrame.Create(tt));
|
||||
|
||||
var enc = new JpegBitmapEncoder();
|
||||
var bitmapFrame = BitmapFrame.Create(image);
|
||||
bitmapFrame.Freeze();
|
||||
enc.Frames.Add(bitmapFrame);
|
||||
enc.Save(outStream);
|
||||
var byteArray = outStream.GetBuffer();
|
||||
using (var sha1 = new SHA1CryptoServiceProvider())
|
||||
{
|
||||
var hash = Convert.ToBase64String(sha1.ComputeHash(byteArray));
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ namespace Wox.Infrastructure.Image
|
|||
{
|
||||
private static readonly ImageCache ImageCache = new ImageCache();
|
||||
private static BinaryStorage<ConcurrentDictionary<string, int>> _storage;
|
||||
private static readonly ConcurrentDictionary<string, string> GuidToKey = new ConcurrentDictionary<string, string>();
|
||||
private static IImageHashGenerator _hashGenerator;
|
||||
|
||||
|
||||
private static readonly string[] ImageExtensions =
|
||||
|
|
@ -30,7 +32,8 @@ namespace Wox.Infrastructure.Image
|
|||
|
||||
public static void Initialize()
|
||||
{
|
||||
_storage = new BinaryStorage<ConcurrentDictionary<string, int>> ("Image");
|
||||
_storage = new BinaryStorage<ConcurrentDictionary<string, int>>("Image");
|
||||
_hashGenerator = new ImageHashGenerator();
|
||||
ImageCache.Usage = _storage.TryLoad(new ConcurrentDictionary<string, int>());
|
||||
|
||||
foreach (var icon in new[] { Constant.DefaultIcon, Constant.ErrorIcon })
|
||||
|
|
@ -43,16 +46,12 @@ namespace Wox.Infrastructure.Image
|
|||
{
|
||||
Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () =>
|
||||
{
|
||||
ImageCache.Usage.AsParallel().Where(i => !ImageCache.ContainsKey(i.Key)).ForAll(i =>
|
||||
ImageCache.Usage.AsParallel().ForAll(x =>
|
||||
{
|
||||
var img = Load(i.Key);
|
||||
if (img != null)
|
||||
{
|
||||
ImageCache[i.Key] = img;
|
||||
}
|
||||
Load(x.Key);
|
||||
});
|
||||
});
|
||||
Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.Usage.Count}>");
|
||||
Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.Usage.Count}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -61,31 +60,56 @@ namespace Wox.Infrastructure.Image
|
|||
ImageCache.Cleanup();
|
||||
_storage.Save(ImageCache.Usage);
|
||||
}
|
||||
|
||||
public static ImageSource Load(string path, bool loadFullImage = false)
|
||||
|
||||
private class ImageResult
|
||||
{
|
||||
public ImageResult(ImageSource imageSource, ImageType imageType)
|
||||
{
|
||||
ImageSource = imageSource;
|
||||
ImageType = imageType;
|
||||
}
|
||||
|
||||
public ImageType ImageType { get; }
|
||||
public ImageSource ImageSource { get; }
|
||||
}
|
||||
|
||||
private enum ImageType
|
||||
{
|
||||
File,
|
||||
Folder,
|
||||
Data,
|
||||
ImageFile,
|
||||
Error,
|
||||
Cache
|
||||
}
|
||||
|
||||
private static ImageResult LoadInternal(string path, bool loadFullImage = false)
|
||||
{
|
||||
ImageSource image;
|
||||
ImageType type = ImageType.Error;
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return ImageCache[Constant.ErrorIcon];
|
||||
return new ImageResult(ImageCache[Constant.ErrorIcon], ImageType.Error);
|
||||
}
|
||||
if (ImageCache.ContainsKey(path))
|
||||
{
|
||||
return ImageCache[path];
|
||||
return new ImageResult(ImageCache[path], ImageType.Cache);
|
||||
}
|
||||
|
||||
|
||||
if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new BitmapImage(new Uri(path));
|
||||
var imageSource = new BitmapImage(new Uri(path));
|
||||
imageSource.Freeze();
|
||||
return new ImageResult(imageSource, ImageType.Data);
|
||||
}
|
||||
|
||||
if (!Path.IsPathRooted(path))
|
||||
{
|
||||
path = Path.Combine(Constant.ProgramDirectory, "Images", Path.GetFileName(path));
|
||||
}
|
||||
|
||||
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
/* Directories can also have thumbnails instead of shell icons.
|
||||
|
|
@ -94,14 +118,17 @@ namespace Wox.Infrastructure.Image
|
|||
* Wox responsibility.
|
||||
* - Solution: just load the icon
|
||||
*/
|
||||
type = ImageType.Folder;
|
||||
image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize,
|
||||
Constant.ThumbnailSize, ThumbnailOptions.IconOnly);
|
||||
|
||||
}
|
||||
else if (File.Exists(path))
|
||||
{
|
||||
var extension = Path.GetExtension(path).ToLower();
|
||||
if (ImageExtensions.Contains(extension))
|
||||
{
|
||||
type = ImageType.ImageFile;
|
||||
if (loadFullImage)
|
||||
{
|
||||
image = LoadFullImage(path);
|
||||
|
|
@ -119,6 +146,7 @@ namespace Wox.Infrastructure.Image
|
|||
}
|
||||
else
|
||||
{
|
||||
type = ImageType.File;
|
||||
image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize,
|
||||
Constant.ThumbnailSize, ThumbnailOptions.None);
|
||||
}
|
||||
|
|
@ -128,17 +156,50 @@ namespace Wox.Infrastructure.Image
|
|||
image = ImageCache[Constant.ErrorIcon];
|
||||
path = Constant.ErrorIcon;
|
||||
}
|
||||
ImageCache[path] = image;
|
||||
image.Freeze();
|
||||
|
||||
if (type != ImageType.Error)
|
||||
{
|
||||
image.Freeze();
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e);
|
||||
|
||||
type = ImageType.Error;
|
||||
image = ImageCache[Constant.ErrorIcon];
|
||||
ImageCache[path] = image;
|
||||
}
|
||||
return image;
|
||||
return new ImageResult(image, type);
|
||||
}
|
||||
|
||||
private static bool EnableImageHash = true;
|
||||
|
||||
public static ImageSource Load(string path, bool loadFullImage = false)
|
||||
{
|
||||
var imageResult = LoadInternal(path, loadFullImage);
|
||||
|
||||
var img = imageResult.ImageSource;
|
||||
if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache)
|
||||
{ // we need to get image hash
|
||||
string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null;
|
||||
if (hash != null)
|
||||
{
|
||||
if (GuidToKey.TryGetValue(hash, out string key))
|
||||
{ // image already exists
|
||||
img = ImageCache[key];
|
||||
}
|
||||
else
|
||||
{ // new guid
|
||||
GuidToKey[hash] = path;
|
||||
}
|
||||
}
|
||||
|
||||
// update cache
|
||||
ImageCache[path] = img;
|
||||
}
|
||||
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
private static BitmapImage LoadFullImage(string path)
|
||||
|
|
|
|||
|
|
@ -110,7 +110,6 @@ namespace Wox.Infrastructure.Image
|
|||
|
||||
try
|
||||
{
|
||||
|
||||
return Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
|
||||
}
|
||||
finally
|
||||
|
|
|
|||
|
|
@ -11,18 +11,20 @@ namespace Wox.Infrastructure.Logger
|
|||
{
|
||||
public const string DirectoryName = "Logs";
|
||||
|
||||
public static string CurrentLogDirectory { get; private set; }
|
||||
|
||||
static Log()
|
||||
{
|
||||
var path = Path.Combine(Constant.DataDirectory, DirectoryName, Constant.Version);
|
||||
if (!Directory.Exists(path))
|
||||
CurrentLogDirectory = Path.Combine(Constant.DataDirectory, DirectoryName, Constant.Version);
|
||||
if (!Directory.Exists(CurrentLogDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(CurrentLogDirectory);
|
||||
}
|
||||
|
||||
var configuration = new LoggingConfiguration();
|
||||
var target = new FileTarget();
|
||||
configuration.AddTarget("file", target);
|
||||
target.FileName = path.Replace(@"\", "/") + "/${shortdate}.txt";
|
||||
target.FileName = CurrentLogDirectory.Replace(@"\", "/") + "/${shortdate}.txt";
|
||||
#if DEBUG
|
||||
var rule = new LoggingRule("*", LogLevel.Debug, target);
|
||||
#else
|
||||
|
|
@ -47,8 +49,53 @@ namespace Wox.Infrastructure.Logger
|
|||
return valid;
|
||||
}
|
||||
|
||||
/// <param name="message">example: "|prefix|unprefixed" </param>
|
||||
public static void Error(string message)
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public static void Exception(string className, string message, System.Exception exception, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(className))
|
||||
{
|
||||
LogFaultyFormat($"Fail to specify a class name during logging of message: {message ?? "no message entered"}");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{ // todo: not sure we really need that
|
||||
LogFaultyFormat($"Fail to specify a message during logging");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(methodName))
|
||||
{
|
||||
className += "." + methodName;
|
||||
}
|
||||
|
||||
ExceptionInternal(className, message, exception);
|
||||
}
|
||||
|
||||
private static void ExceptionInternal(string classAndMethod, string message, System.Exception e)
|
||||
{
|
||||
var logger = LogManager.GetLogger(classAndMethod);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"ERROR|{message}");
|
||||
|
||||
logger.Error("-------------------------- Begin exception --------------------------");
|
||||
logger.Error(message);
|
||||
|
||||
do
|
||||
{
|
||||
logger.Error($"Exception full name:\n <{e.GetType().FullName}>");
|
||||
logger.Error($"Exception message:\n <{e.Message}>");
|
||||
logger.Error($"Exception stack trace:\n <{e.StackTrace}>");
|
||||
logger.Error($"Exception source:\n <{e.Source}>");
|
||||
logger.Error($"Exception target site:\n <{e.TargetSite}>");
|
||||
logger.Error($"Exception HResult:\n <{e.HResult}>");
|
||||
e = e.InnerException;
|
||||
} while (e != null);
|
||||
|
||||
logger.Error("-------------------------- End exception --------------------------");
|
||||
}
|
||||
|
||||
private static void LogInternal(string message, LogLevel level)
|
||||
{
|
||||
if (FormatValid(message))
|
||||
{
|
||||
|
|
@ -57,8 +104,8 @@ namespace Wox.Infrastructure.Logger
|
|||
var unprefixed = parts[2];
|
||||
var logger = LogManager.GetLogger(prefix);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"ERROR|{message}");
|
||||
logger.Error(unprefixed);
|
||||
System.Diagnostics.Debug.WriteLine($"{level.Name}|{message}");
|
||||
logger.Log(level, unprefixed);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -78,25 +125,7 @@ namespace Wox.Infrastructure.Logger
|
|||
var parts = message.Split('|');
|
||||
var prefix = parts[1];
|
||||
var unprefixed = parts[2];
|
||||
var logger = LogManager.GetLogger(prefix);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"ERROR|{message}");
|
||||
|
||||
logger.Error("-------------------------- Begin exception --------------------------");
|
||||
logger.Error(unprefixed);
|
||||
|
||||
do
|
||||
{
|
||||
logger.Error($"Exception full name:\n <{e.GetType().FullName}>");
|
||||
logger.Error($"Exception message:\n <{e.Message}>");
|
||||
logger.Error($"Exception stack trace:\n <{e.StackTrace}>");
|
||||
logger.Error($"Exception source:\n <{e.Source}>");
|
||||
logger.Error($"Exception target site:\n <{e.TargetSite}>");
|
||||
logger.Error($"Exception HResult:\n <{e.HResult}>");
|
||||
e = e.InnerException;
|
||||
} while (e != null);
|
||||
|
||||
logger.Error("-------------------------- End exception --------------------------");
|
||||
ExceptionInternal(prefix, unprefixed, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -104,62 +133,29 @@ namespace Wox.Infrastructure.Logger
|
|||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/// <param name="message">example: "|prefix|unprefixed" </param>
|
||||
public static void Error(string message)
|
||||
{
|
||||
LogInternal(message, LogLevel.Error);
|
||||
}
|
||||
|
||||
/// <param name="message">example: "|prefix|unprefixed" </param>
|
||||
public static void Debug(string message)
|
||||
{
|
||||
if (FormatValid(message))
|
||||
{
|
||||
var parts = message.Split('|');
|
||||
var prefix = parts[1];
|
||||
var unprefixed = parts[2];
|
||||
var logger = LogManager.GetLogger(prefix);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"DEBUG|{message}");
|
||||
logger.Debug(unprefixed);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogFaultyFormat(message);
|
||||
}
|
||||
LogInternal(message, LogLevel.Debug);
|
||||
}
|
||||
|
||||
/// <param name="message">example: "|prefix|unprefixed" </param>
|
||||
public static void Info(string message)
|
||||
{
|
||||
if (FormatValid(message))
|
||||
{
|
||||
var parts = message.Split('|');
|
||||
var prefix = parts[1];
|
||||
var unprefixed = parts[2];
|
||||
var logger = LogManager.GetLogger(prefix);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"INFO|{message}");
|
||||
logger.Info(unprefixed);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogFaultyFormat(message);
|
||||
}
|
||||
LogInternal(message, LogLevel.Info);
|
||||
}
|
||||
|
||||
/// <param name="message">example: "|prefix|unprefixed" </param>
|
||||
public static void Warn(string message)
|
||||
{
|
||||
if (FormatValid(message))
|
||||
{
|
||||
var parts = message.Split('|');
|
||||
var prefix = parts[1];
|
||||
var unprefixed = parts[2];
|
||||
var logger = LogManager.GetLogger(prefix);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"WARN|{message}");
|
||||
logger.Warn(unprefixed);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogFaultyFormat(message);
|
||||
}
|
||||
LogInternal(message, LogLevel.Warn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,13 +6,14 @@ using Wox.Infrastructure.Logger;
|
|||
using Wox.Infrastructure.UserSettings;
|
||||
using static Wox.Infrastructure.StringMatcher;
|
||||
|
||||
namespace Wox.Infrastructure
|
||||
namespace Wox.Infrastructure
|
||||
{
|
||||
public static class StringMatcher
|
||||
{
|
||||
public static MatchOption DefaultMatchOption = new MatchOption();
|
||||
|
||||
public static string UserSettingSearchPrecision { get; set; }
|
||||
public static SearchPrecisionScore UserSettingSearchPrecision { get; set; }
|
||||
|
||||
public static bool ShouldUsePinyin { get; set; }
|
||||
|
||||
[Obsolete("This method is obsolete and should not be used. Please use the static function StringMatcher.FuzzySearch")]
|
||||
|
|
@ -40,56 +41,104 @@ namespace Wox.Infrastructure
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// refer to https://github.com/mattyork/fuzzy
|
||||
/// Current method:
|
||||
/// 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,
|
||||
/// 3. if a character in the substring is matched, loop back to verify the previous character.
|
||||
/// 4. If previous character also matches, and is the start of the substring, update list.
|
||||
/// 5. Once the previous character is verified, move on to the next character in the query substring.
|
||||
/// 6. Move onto the next substring's characters until all substrings are checked.
|
||||
/// 7. Consider success and move onto scoring if every char or substring without whitespaces matched
|
||||
/// </summary>
|
||||
public static MatchResult FuzzySearch(string query, string stringToCompare, MatchOption opt)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) return new MatchResult { Success = false };
|
||||
|
||||
|
||||
query = query.Trim();
|
||||
|
||||
var len = stringToCompare.Length;
|
||||
var compareString = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare;
|
||||
var pattern = opt.IgnoreCase ? query.ToLower() : query;
|
||||
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];
|
||||
var currentQuerySubstringCharacterIndex = 0;
|
||||
|
||||
var sb = new StringBuilder(stringToCompare.Length + (query.Length * (opt.Prefix.Length + opt.Suffix.Length)));
|
||||
var patternIdx = 0;
|
||||
var firstMatchIndex = -1;
|
||||
var firstMatchIndexInWord = -1;
|
||||
var lastMatchIndex = 0;
|
||||
char ch;
|
||||
bool allQuerySubstringsMatched = false;
|
||||
bool matchFoundInPreviousLoop = false;
|
||||
bool allSubstringsContainedInCompareString = true;
|
||||
|
||||
var indexList = new List<int>();
|
||||
|
||||
for (var idx = 0; idx < len; idx++)
|
||||
for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++)
|
||||
{
|
||||
ch = stringToCompare[idx];
|
||||
if (compareString[idx] == pattern[patternIdx])
|
||||
if (fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex])
|
||||
{
|
||||
if (firstMatchIndex < 0)
|
||||
firstMatchIndex = idx;
|
||||
lastMatchIndex = idx + 1;
|
||||
|
||||
indexList.Add(idx);
|
||||
sb.Append(opt.Prefix + ch + opt.Suffix);
|
||||
patternIdx += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(ch);
|
||||
matchFoundInPreviousLoop = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// match success, append remain char
|
||||
if (patternIdx == pattern.Length && (idx + 1) != compareString.Length)
|
||||
if (firstMatchIndex < 0)
|
||||
{
|
||||
sb.Append(stringToCompare.Substring(idx + 1));
|
||||
break;
|
||||
// first matched char will become the start of the compared string
|
||||
firstMatchIndex = compareStringIndex;
|
||||
}
|
||||
|
||||
if (currentQuerySubstringCharacterIndex == 0)
|
||||
{
|
||||
// first letter of current word
|
||||
matchFoundInPreviousLoop = true;
|
||||
firstMatchIndexInWord = compareStringIndex;
|
||||
}
|
||||
else if (!matchFoundInPreviousLoop)
|
||||
{
|
||||
// we want to verify that there is not a better match if this is not a full word
|
||||
// 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))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
lastMatchIndex = compareStringIndex + 1;
|
||||
indexList.Add(compareStringIndex);
|
||||
|
||||
currentQuerySubstringCharacterIndex++;
|
||||
|
||||
// if finished looping through every character in the current substring
|
||||
if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length)
|
||||
{
|
||||
// if any of the substrings was not matched then consider as all are not matched
|
||||
allSubstringsContainedInCompareString = matchFoundInPreviousLoop && allSubstringsContainedInCompareString;
|
||||
|
||||
currentQuerySubstringIndex++;
|
||||
|
||||
allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length);
|
||||
if (allQuerySubstringsMatched)
|
||||
break;
|
||||
|
||||
// otherwise move to the next query substring
|
||||
currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
|
||||
currentQuerySubstringCharacterIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// return rendered string if we have a match for every char
|
||||
if (patternIdx == pattern.Length)
|
||||
|
||||
// proceed to calculate score if every char or substring without whitespaces matched
|
||||
if (allQuerySubstringsMatched)
|
||||
{
|
||||
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex);
|
||||
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
|
||||
var pinyinScore = ScoreForPinyin(stringToCompare, query);
|
||||
|
||||
var result = new MatchResult
|
||||
|
|
@ -105,7 +154,44 @@ namespace Wox.Infrastructure
|
|||
return new MatchResult { Success = false };
|
||||
}
|
||||
|
||||
private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen)
|
||||
private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex,
|
||||
string fullStringToCompareWithoutCase, string currentQuerySubstring)
|
||||
{
|
||||
var allMatch = true;
|
||||
for (int indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++)
|
||||
{
|
||||
if (fullStringToCompareWithoutCase[startIndexToVerify + indexToCheck] !=
|
||||
currentQuerySubstring[indexToCheck])
|
||||
{
|
||||
allMatch = false;
|
||||
}
|
||||
}
|
||||
|
||||
return allMatch;
|
||||
}
|
||||
|
||||
private static List<int> GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, int firstMatchIndexInWord, List<int> indexList)
|
||||
{
|
||||
var updatedList = new List<int>();
|
||||
|
||||
indexList.RemoveAll(x => x >= firstMatchIndexInWord);
|
||||
|
||||
updatedList.AddRange(indexList);
|
||||
|
||||
for (int indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++)
|
||||
{
|
||||
updatedList.Add(startIndexToVerify + indexToCheck);
|
||||
}
|
||||
|
||||
return updatedList;
|
||||
}
|
||||
|
||||
private static bool AllQuerySubstringsMatched(int currentQuerySubstringIndex, int querySubstringsLength)
|
||||
{
|
||||
return currentQuerySubstringIndex >= querySubstringsLength;
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
@ -122,6 +208,13 @@ namespace Wox.Infrastructure
|
|||
score += 10;
|
||||
}
|
||||
|
||||
if (allSubstringsContainedInCompareString)
|
||||
{
|
||||
int count = query.Count(c => !char.IsWhiteSpace(c));
|
||||
int factor = count < 4 ? 10 : 5;
|
||||
score += factor * count;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
|
|
@ -143,11 +236,11 @@ namespace Wox.Infrastructure
|
|||
{
|
||||
if (Alphabet.ContainsChinese(source))
|
||||
{
|
||||
var combination = Alphabet.PinyinComination(source);
|
||||
var combination = Alphabet.PinyinComination(source);
|
||||
var pinyinScore = combination
|
||||
.Select(pinyin => FuzzySearch(target, string.Join("", pinyin)).Score)
|
||||
.Max();
|
||||
var acronymScore = combination.Select(Alphabet.Acronym)
|
||||
var acronymScore = combination.Select(Alphabet.Acronym)
|
||||
.Select(pinyin => FuzzySearch(target, pinyin).Score)
|
||||
.Max();
|
||||
var score = Math.Max(pinyinScore, acronymScore);
|
||||
|
|
@ -162,7 +255,7 @@ namespace Wox.Infrastructure
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MatchResult
|
||||
|
|
@ -178,6 +271,7 @@ namespace Wox.Infrastructure
|
|||
/// The raw calculated search score without any search precision filtering applied.
|
||||
/// </summary>
|
||||
private int _rawScore;
|
||||
|
||||
public int RawScore
|
||||
{
|
||||
get { return _rawScore; }
|
||||
|
|
@ -200,10 +294,7 @@ namespace Wox.Infrastructure
|
|||
|
||||
private bool IsSearchPrecisionScoreMet(int score)
|
||||
{
|
||||
var precisionScore = (SearchPrecisionScore)Enum.Parse(
|
||||
typeof(SearchPrecisionScore),
|
||||
UserSettingSearchPrecision ?? SearchPrecisionScore.Regular.ToString());
|
||||
return score >= (int)precisionScore;
|
||||
return score >= (int)UserSettingSearchPrecision;
|
||||
}
|
||||
|
||||
private int ApplySearchPrecisionFilter(int score)
|
||||
|
|
@ -214,22 +305,18 @@ namespace Wox.Infrastructure
|
|||
|
||||
public class MatchOption
|
||||
{
|
||||
public MatchOption()
|
||||
{
|
||||
Prefix = "";
|
||||
Suffix = "";
|
||||
IgnoreCase = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// prefix of match char, use for hightlight
|
||||
/// </summary>
|
||||
public string Prefix { get; set; }
|
||||
[Obsolete("this is never used")]
|
||||
public string Prefix { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// suffix of match char, use for hightlight
|
||||
/// </summary>
|
||||
public string Suffix { get; set; }
|
||||
[Obsolete("this is never used")]
|
||||
public string Suffix { get; set; } = "";
|
||||
|
||||
public bool IgnoreCase { get; set; }
|
||||
public bool IgnoreCase { get; set; } = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,14 +36,30 @@ namespace Wox.Infrastructure.UserSettings
|
|||
}
|
||||
|
||||
|
||||
private string _querySearchPrecision { get; set; } = StringMatcher.SearchPrecisionScore.Regular.ToString();
|
||||
public string QuerySearchPrecision
|
||||
internal StringMatcher.SearchPrecisionScore QuerySearchPrecision { get; private set; } = StringMatcher.SearchPrecisionScore.Regular;
|
||||
|
||||
public string QuerySearchPrecisionString
|
||||
{
|
||||
get { return _querySearchPrecision; }
|
||||
get { return QuerySearchPrecision.ToString(); }
|
||||
set
|
||||
{
|
||||
_querySearchPrecision = value;
|
||||
StringMatcher.UserSettingSearchPrecision = value;
|
||||
try
|
||||
{
|
||||
var precisionScore = (StringMatcher.SearchPrecisionScore)Enum
|
||||
.Parse(typeof(StringMatcher.SearchPrecisionScore), value);
|
||||
|
||||
QuerySearchPrecision = precisionScore;
|
||||
StringMatcher.UserSettingSearchPrecision = precisionScore;
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
Logger.Log.Exception(nameof(Settings), "Failed to load QuerySearchPrecisionString value from Settings file", e);
|
||||
|
||||
QuerySearchPrecision = StringMatcher.SearchPrecisionScore.Regular;
|
||||
StringMatcher.UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular;
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@
|
|||
<Compile Include="Hotkey\InterceptKeys.cs" />
|
||||
<Compile Include="Hotkey\KeyEvent.cs" />
|
||||
<Compile Include="Image\ImageCache.cs" />
|
||||
<Compile Include="Image\ImageHashGenerator.cs" />
|
||||
<Compile Include="Image\ImageLoader.cs" />
|
||||
<Compile Include="Image\ThumbnailReader.cs" />
|
||||
<Compile Include="Logger\Log.cs" />
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ namespace Wox.Infrastructure
|
|||
public static readonly string DataDirectory = DetermineDataDirectory();
|
||||
public static readonly string PluginsDirectory = Path.Combine(DataDirectory, Plugins);
|
||||
public static readonly string PreinstalledDirectory = Path.Combine(ProgramDirectory, Plugins);
|
||||
public const string Repository = "https://github.com/Wox-launcher/Wox";
|
||||
public const string Issue = "https://github.com/Wox-launcher/Wox/issues/new";
|
||||
public static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location.NonNull()).ProductVersion;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ using System.Diagnostics;
|
|||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Infrastructure.UserSettings;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Wox.Test
|
||||
|
|
@ -12,17 +11,25 @@ namespace Wox.Test
|
|||
[TestFixture]
|
||||
public class FuzzyMatcherTest
|
||||
{
|
||||
private const string Chrome = "Chrome";
|
||||
private const string CandyCrushSagaFromKing = "Candy Crush Saga from King";
|
||||
private const string HelpCureHopeRaiseOnMindEntityChrome = "Help cure hope raise on mind entity Chrome";
|
||||
private const string UninstallOrChangeProgramsOnYourComputer = "Uninstall or change programs on your computer";
|
||||
private const string LastIsChrome = "Last is chrome";
|
||||
private const string OneOneOneOne = "1111";
|
||||
private const string MicrosoftSqlServerManagementStudio = "Microsoft SQL Server Management Studio";
|
||||
|
||||
public List<string> GetSearchStrings()
|
||||
=> new List<string>
|
||||
{
|
||||
"Chrome",
|
||||
Chrome,
|
||||
"Choose which programs you want Windows to use for activities like web browsing, editing photos, sending e-mail, and playing music.",
|
||||
"Help cure hope raise on mind entity Chrome ",
|
||||
"Candy Crush Saga from King",
|
||||
"Uninstall or change programs on your computer",
|
||||
HelpCureHopeRaiseOnMindEntityChrome,
|
||||
CandyCrushSagaFromKing,
|
||||
UninstallOrChangeProgramsOnYourComputer,
|
||||
"Add, change, and manage fonts on your computer",
|
||||
"Last is chrome",
|
||||
"1111"
|
||||
LastIsChrome,
|
||||
OneOneOneOne
|
||||
};
|
||||
|
||||
public List<int> GetPrecisionScores()
|
||||
|
|
@ -76,17 +83,17 @@ namespace Wox.Test
|
|||
|
||||
Assert.True(scoreResult == 0);
|
||||
}
|
||||
|
||||
|
||||
[TestCase("chr")]
|
||||
[TestCase("chrom")]
|
||||
[TestCase("chrome")]
|
||||
[TestCase("chrome")]
|
||||
[TestCase("cand")]
|
||||
[TestCase("cpywa")]
|
||||
[TestCase("ccs")]
|
||||
public void WhenGivenStringsAndAppliedPrecisionFilteringThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
|
||||
|
||||
foreach (var str in GetSearchStrings())
|
||||
{
|
||||
results.Add(new Result
|
||||
|
|
@ -94,7 +101,7 @@ namespace Wox.Test
|
|||
Title = str,
|
||||
Score = StringMatcher.FuzzySearch(searchTerm, str).Score
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var precisionScore in GetPrecisionScores())
|
||||
{
|
||||
|
|
@ -114,78 +121,103 @@ namespace Wox.Test
|
|||
}
|
||||
}
|
||||
|
||||
[TestCase("chrome")]
|
||||
public void WhenGivenStringsForCalScoreMethodThenShouldReturnCurrentScoring(string searchTerm)
|
||||
[TestCase(Chrome, Chrome, 137)]
|
||||
[TestCase(Chrome, LastIsChrome, 83)]
|
||||
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 21)]
|
||||
[TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 15)]
|
||||
[TestCase(Chrome, CandyCrushSagaFromKing, 0)]
|
||||
[TestCase("sql", MicrosoftSqlServerManagementStudio, 56)]
|
||||
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, 79)]//double spacing intended
|
||||
public void WhenGivenQueryStringThenShouldReturnCurrentScoring(string queryString, string compareString, int expectedScore)
|
||||
{
|
||||
var searchStrings = new List<string>
|
||||
{
|
||||
"Chrome",//SCORE: 107
|
||||
"Last is chrome",//SCORE: 53
|
||||
"Help cure hope raise on mind entity Chrome",//SCORE: 21
|
||||
"Uninstall or change programs on your computer", //SCORE: 15
|
||||
"Candy Crush Saga from King"//SCORE: 0
|
||||
}
|
||||
.OrderByDescending(x => x)
|
||||
.ToList();
|
||||
// When, Given
|
||||
var rawScore = StringMatcher.FuzzySearch(queryString, compareString).RawScore;
|
||||
|
||||
var results = new List<Result>();
|
||||
foreach (var str in searchStrings)
|
||||
{
|
||||
results.Add(new Result
|
||||
{
|
||||
Title = str,
|
||||
Score = StringMatcher.FuzzySearch(searchTerm, str).RawScore
|
||||
});
|
||||
}
|
||||
|
||||
var orderedResults = results.OrderByDescending(x => x.Title).ToList();
|
||||
|
||||
Debug.WriteLine("");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine("SEARCHTERM: " + searchTerm);
|
||||
foreach (var item in orderedResults)
|
||||
{
|
||||
Debug.WriteLine("SCORE: " + item.Score.ToString() + ", FoundString: " + item.Title);
|
||||
}
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine("");
|
||||
|
||||
Assert.IsTrue(orderedResults[0].Score == 15 && orderedResults[0].Title == searchStrings[0]);
|
||||
Assert.IsTrue(orderedResults[1].Score == 53 && orderedResults[1].Title == searchStrings[1]);
|
||||
Assert.IsTrue(orderedResults[2].Score == 21 && orderedResults[2].Title == searchStrings[2]);
|
||||
Assert.IsTrue(orderedResults[3].Score == 107 && orderedResults[3].Title == searchStrings[3]);
|
||||
Assert.IsTrue(orderedResults[4].Score == 0 && orderedResults[4].Title == searchStrings[4]);
|
||||
// Should
|
||||
Assert.AreEqual(expectedScore, rawScore, $"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}");
|
||||
}
|
||||
|
||||
[TestCase("goo", "Google Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("chr", "Google Chrome", (int)StringMatcher.SearchPrecisionScore.Low, true)]
|
||||
[TestCase("chr", "Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("chr", "Help cure hope raise on mind entity Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("chr", "Help cure hope raise on mind entity Chrome", (int)StringMatcher.SearchPrecisionScore.Low, true)]
|
||||
[TestCase("chr", "Candy Crush Saga from King", (int)StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("chr", "Candy Crush Saga from King", (int)StringMatcher.SearchPrecisionScore.None, true)]
|
||||
[TestCase("ccs", "Candy Crush Saga from King", (int)StringMatcher.SearchPrecisionScore.Low, true)]
|
||||
[TestCase("cand", "Candy Crush Saga from King", (int)StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||
[TestCase("cand", "Help cure hope raise on mind entity Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[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)]
|
||||
public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual(
|
||||
string queryString,
|
||||
string compareString,
|
||||
int expectedPrecisionScore,
|
||||
string queryString,
|
||||
string compareString,
|
||||
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
|
||||
bool expectedPrecisionResult)
|
||||
{
|
||||
var expectedPrecisionString = (StringMatcher.SearchPrecisionScore)expectedPrecisionScore;
|
||||
StringMatcher.UserSettingSearchPrecision = expectedPrecisionString.ToString();
|
||||
// When
|
||||
StringMatcher.UserSettingSearchPrecision = expectedPrecisionScore;
|
||||
|
||||
// Given
|
||||
var matchResult = StringMatcher.FuzzySearch(queryString, compareString);
|
||||
|
||||
Debug.WriteLine("");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine($"SearchTerm: {queryString} PrecisionLevelSetAt: {expectedPrecisionString} ({expectedPrecisionScore})");
|
||||
Debug.WriteLine($"SCORE: {matchResult.Score.ToString()}, ComparedString: {compareString}");
|
||||
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
|
||||
Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine("");
|
||||
|
||||
var matchPrecisionResult = matchResult.IsSearchPrecisionScoreMet();
|
||||
Assert.IsTrue(matchPrecisionResult == expectedPrecisionResult);
|
||||
// Should
|
||||
Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
|
||||
$"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("sqlserv", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||
[TestCase("sql servman", 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)]
|
||||
public void WhenGivenQueryShouldReturnResultsContainingAllQuerySubstrings(
|
||||
string queryString,
|
||||
string compareString,
|
||||
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
|
||||
bool expectedPrecisionResult)
|
||||
{
|
||||
// When
|
||||
StringMatcher.UserSettingSearchPrecision = expectedPrecisionScore;
|
||||
|
||||
// Given
|
||||
var matchResult = StringMatcher.FuzzySearch(queryString, compareString);
|
||||
|
||||
Debug.WriteLine("");
|
||||
Debug.WriteLine("###############################################");
|
||||
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
|
||||
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}" +
|
||||
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
|
||||
$"Precision Score: {(int)expectedPrecisionScore}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,19 @@
|
|||
<?xml version="1.0"?>
|
||||
<configuration>
|
||||
<!--https://msdn.microsoft.com/en-us/library/dd409252(v=vs.110).aspx-->
|
||||
<configSections>
|
||||
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
|
||||
<section name="Wox.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
|
||||
</sectionGroup>
|
||||
</configSections>
|
||||
<runtime>
|
||||
<loadFromRemoteSources enabled="true"/>
|
||||
</runtime>
|
||||
<applicationSettings>
|
||||
<Wox.Properties.Settings>
|
||||
<setting name="GithubRepo" serializeAs="String">
|
||||
<value>https://github.com/Wox-launcher/Wox</value>
|
||||
</setting>
|
||||
</Wox.Properties.Settings>
|
||||
</applicationSettings>
|
||||
</configuration>
|
||||
|
|
@ -25,6 +25,7 @@ namespace Wox
|
|||
private Settings _settings;
|
||||
private MainViewModel _mainVM;
|
||||
private SettingWindowViewModel _settingsVM;
|
||||
private readonly Updater _updater = new Updater(Wox.Properties.Settings.Default.GithubRepo);
|
||||
|
||||
[STAThread]
|
||||
public static void Main()
|
||||
|
|
@ -50,7 +51,7 @@ namespace Wox
|
|||
|
||||
ImageLoader.Initialize();
|
||||
|
||||
_settingsVM = new SettingWindowViewModel();
|
||||
_settingsVM = new SettingWindowViewModel(_updater);
|
||||
_settings = _settingsVM.Settings;
|
||||
|
||||
Alphabet.Initialize(_settings);
|
||||
|
|
@ -111,12 +112,12 @@ namespace Wox
|
|||
var timer = new Timer(1000 * 60 * 60 * 5);
|
||||
timer.Elapsed += async (s, e) =>
|
||||
{
|
||||
await Updater.UpdateApp();
|
||||
await _updater.UpdateApp();
|
||||
};
|
||||
timer.Start();
|
||||
|
||||
// check updates on startup
|
||||
await Updater.UpdateApp();
|
||||
await _updater.UpdateApp();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,4 +87,4 @@
|
|||
</ContentControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Window>
|
||||
</Window>
|
||||
|
|
|
|||
11
Wox/Properties/Settings.Designer.cs
generated
11
Wox/Properties/Settings.Designer.cs
generated
|
|
@ -12,7 +12,7 @@ namespace Wox.Properties {
|
|||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.3.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
|
@ -22,5 +22,14 @@ namespace Wox.Properties {
|
|||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("https://github.com/Wox-launcher/Wox")]
|
||||
public string GithubRepo {
|
||||
get {
|
||||
return ((string)(this["GithubRepo"]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="uri:_settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="Wox.Properties" GeneratedClassName="Settings">
|
||||
<Profiles />
|
||||
<Settings>
|
||||
<Setting Name="GithubRepo" Type="System.String" Scope="Application">
|
||||
<Value Profile="(Default)">https://github.com/Wox-launcher/Wox</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
|
|
@ -23,7 +23,7 @@ namespace Wox
|
|||
|
||||
private void SetException(Exception exception)
|
||||
{
|
||||
string path = Path.Combine(Constant.DataDirectory, Log.DirectoryName, Constant.Version);
|
||||
string path = Log.CurrentLogDirectory;
|
||||
var directory = new DirectoryInfo(path);
|
||||
var log = directory.GetFiles().OrderByDescending(f => f.LastWriteTime).First();
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
<TextBlock Text="{DynamicResource querySearchPrecision}" />
|
||||
<ComboBox Margin="10 0 0 0" Width="120"
|
||||
ItemsSource="{Binding QuerySearchPrecisionStrings}"
|
||||
SelectedItem="{Binding Settings.QuerySearchPrecision}" />
|
||||
SelectedItem="{Binding Settings.QuerySearchPrecisionString}" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10" Orientation="Horizontal">
|
||||
<TextBlock Text="{DynamicResource lastQueryMode}" />
|
||||
|
|
|
|||
|
|
@ -260,53 +260,16 @@ namespace Wox
|
|||
#region Proxy
|
||||
|
||||
private void OnTestProxyClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_settings.Proxy.Server))
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("serverCantBeEmpty"));
|
||||
return;
|
||||
}
|
||||
if (_settings.Proxy.Port <= 0)
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("portCantBeEmpty"));
|
||||
return;
|
||||
}
|
||||
|
||||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Infrastructure.Constant.Repository);
|
||||
if (string.IsNullOrEmpty(_settings.Proxy.UserName) || string.IsNullOrEmpty(_settings.Proxy.Password))
|
||||
{
|
||||
request.Proxy = new WebProxy(_settings.Proxy.Server, _settings.Proxy.Port);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Proxy = new WebProxy(_settings.Proxy.Server, _settings.Proxy.Port)
|
||||
{
|
||||
Credentials = new NetworkCredential(_settings.Proxy.UserName, _settings.Proxy.Password)
|
||||
};
|
||||
}
|
||||
try
|
||||
{
|
||||
var response = (HttpWebResponse)request.GetResponse();
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("proxyIsCorrect"));
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("proxyConnectFailed"));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("proxyConnectFailed"));
|
||||
}
|
||||
{ // TODO: change to command
|
||||
var msg = _viewModel.TestProxy();
|
||||
MessageBox.Show(msg); // TODO: add message box service
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private async void OnCheckUpdates(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await Updater.UpdateApp();
|
||||
_viewModel.UpdateApp(); // TODO: change to command
|
||||
}
|
||||
|
||||
private void OnRequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||
|
|
|
|||
28
Wox/Settings.cs
Normal file
28
Wox/Settings.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
namespace Wox.Properties {
|
||||
|
||||
|
||||
// This class allows you to handle specific events on the settings class:
|
||||
// The SettingChanging event is raised before a setting's value is changed.
|
||||
// The PropertyChanged event is raised after a setting's value is changed.
|
||||
// The SettingsLoaded event is raised after the setting values are loaded.
|
||||
// The SettingsSaving event is raised before the setting values are saved.
|
||||
internal sealed partial class Settings {
|
||||
|
||||
public Settings() {
|
||||
// // To add event handlers for saving and changing settings, uncomment the lines below:
|
||||
//
|
||||
// this.SettingChanging += this.SettingChangingEventHandler;
|
||||
//
|
||||
// this.SettingsSaving += this.SettingsSavingEventHandler;
|
||||
//
|
||||
}
|
||||
|
||||
private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) {
|
||||
// Add code to handle the SettingChangingEvent event here.
|
||||
}
|
||||
|
||||
private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) {
|
||||
// Add code to handle the SettingsSaving event here.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,6 @@ using Wox.Core.Resource;
|
|||
using Wox.Helper;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Infrastructure.Hotkey;
|
||||
using Wox.Infrastructure.Image;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using Wox.Infrastructure.UserSettings;
|
||||
using Wox.Plugin;
|
||||
|
|
@ -25,7 +24,7 @@ namespace Wox.ViewModel
|
|||
{
|
||||
#region Private Fields
|
||||
|
||||
private bool _queryHasReturn;
|
||||
private bool _isQueryRunning;
|
||||
private Query _lastQuery;
|
||||
private string _queryTextBeforeLeaveResults;
|
||||
|
||||
|
|
@ -41,7 +40,7 @@ namespace Wox.ViewModel
|
|||
private CancellationToken _updateToken;
|
||||
private bool _saved;
|
||||
|
||||
private Internationalization _translator = InternationalizationManager.Instance;
|
||||
private readonly Internationalization _translator = InternationalizationManager.Instance;
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
@ -312,7 +311,7 @@ namespace Wox.ViewModel
|
|||
{
|
||||
var filtered = results.Where
|
||||
(
|
||||
r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet()
|
||||
r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet()
|
||||
|| StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet()
|
||||
).ToList();
|
||||
ContextMenu.AddResults(filtered, id);
|
||||
|
|
@ -371,63 +370,59 @@ namespace Wox.ViewModel
|
|||
if (!string.IsNullOrEmpty(QueryText))
|
||||
{
|
||||
_updateSource?.Cancel();
|
||||
_updateSource = new CancellationTokenSource();
|
||||
_updateToken = _updateSource.Token;
|
||||
var currentUpdateSource = new CancellationTokenSource();
|
||||
_updateSource = currentUpdateSource;
|
||||
var currentCancellationToken = _updateSource.Token;
|
||||
_updateToken = currentCancellationToken;
|
||||
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
_queryHasReturn = false;
|
||||
_isQueryRunning = true;
|
||||
var query = PluginManager.QueryInit(QueryText.Trim());
|
||||
if (query != null)
|
||||
{
|
||||
// handle the exclusiveness of plugin using action keyword
|
||||
string lastKeyword = _lastQuery.ActionKeyword;
|
||||
string keyword = query.ActionKeyword;
|
||||
if (string.IsNullOrEmpty(lastKeyword))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(keyword))
|
||||
{
|
||||
Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(keyword))
|
||||
{
|
||||
Results.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata);
|
||||
}
|
||||
else if (lastKeyword != keyword)
|
||||
{
|
||||
Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata);
|
||||
}
|
||||
}
|
||||
RemoveOldQueryResults(query);
|
||||
|
||||
_lastQuery = query;
|
||||
Task.Delay(200, _updateToken).ContinueWith(_ =>
|
||||
{
|
||||
if (query.RawQuery == _lastQuery.RawQuery && !_queryHasReturn)
|
||||
Task.Delay(200, currentCancellationToken).ContinueWith(_ =>
|
||||
{ // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet
|
||||
if (currentUpdateSource == _updateSource && _isQueryRunning)
|
||||
{
|
||||
ProgressBarVisibility = Visibility.Visible;
|
||||
}
|
||||
}, _updateToken);
|
||||
}, currentCancellationToken);
|
||||
|
||||
var plugins = PluginManager.ValidPluginsForQuery(query);
|
||||
Task.Run(() =>
|
||||
{
|
||||
Parallel.ForEach(plugins, plugin =>
|
||||
// so looping will stop once it was cancelled
|
||||
var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken };
|
||||
try
|
||||
{
|
||||
var config = _settings.PluginSettings.Plugins[plugin.Metadata.ID];
|
||||
if (!config.Disabled)
|
||||
Parallel.ForEach(plugins, parallelOptions, plugin =>
|
||||
{
|
||||
var results = PluginManager.QueryForPlugin(plugin, query);
|
||||
UpdateResultView(results, plugin.Metadata, query);
|
||||
}
|
||||
});
|
||||
var config = _settings.PluginSettings.Plugins[plugin.Metadata.ID];
|
||||
if (!config.Disabled)
|
||||
{
|
||||
var results = PluginManager.QueryForPlugin(plugin, query);
|
||||
UpdateResultView(results, plugin.Metadata, query);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
|
||||
// this should happen once after all queries are done so progress bar should continue
|
||||
// until the end of all querying
|
||||
_queryHasReturn = true;
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
}, _updateToken);
|
||||
_isQueryRunning = false;
|
||||
if (currentUpdateSource == _updateSource)
|
||||
{ // update to hidden if this is still the current query
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
}
|
||||
}, currentCancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -437,6 +432,30 @@ namespace Wox.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private void RemoveOldQueryResults(Query query)
|
||||
{
|
||||
string lastKeyword = _lastQuery.ActionKeyword;
|
||||
string keyword = query.ActionKeyword;
|
||||
if (string.IsNullOrEmpty(lastKeyword))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(keyword))
|
||||
{
|
||||
Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(keyword))
|
||||
{
|
||||
Results.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata);
|
||||
}
|
||||
else if (lastKeyword != keyword)
|
||||
{
|
||||
Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Result ContextMenuTopMost(Result result)
|
||||
{
|
||||
|
|
@ -660,4 +679,4 @@ namespace Wox.ViewModel
|
|||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -156,14 +156,14 @@ namespace Wox.ViewModel
|
|||
private List<ResultViewModel> NewResults(List<Result> newRawResults, string resultId)
|
||||
{
|
||||
var results = Results.ToList();
|
||||
var newResults = newRawResults.Select(r => new ResultViewModel(r)).ToList();
|
||||
var newResults = newRawResults.Select(r => new ResultViewModel(r)).ToList();
|
||||
var oldResults = results.Where(r => r.Result.PluginID == resultId).ToList();
|
||||
|
||||
// Find the same results in A (old results) and B (new newResults)
|
||||
var sameResults = oldResults
|
||||
.Where(t1 => newResults.Any(x => x.Result.Equals(t1.Result)))
|
||||
.ToList();
|
||||
|
||||
|
||||
// remove result of relative complement of B in A
|
||||
foreach (var result in oldResults.Except(sameResults))
|
||||
{
|
||||
|
|
@ -248,6 +248,10 @@ namespace Wox.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the results collection with new results, try to keep identical results
|
||||
/// </summary>
|
||||
/// <param name="newItems"></param>
|
||||
public void Update(List<ResultViewModel> newItems)
|
||||
{
|
||||
int newCount = newItems.Count;
|
||||
|
|
@ -259,7 +263,7 @@ namespace Wox.ViewModel
|
|||
ResultViewModel oldResult = this[i];
|
||||
ResultViewModel newResult = newItems[i];
|
||||
if (!oldResult.Equals(newResult))
|
||||
{
|
||||
{ // result is not the same update it in the current index
|
||||
this[i] = newResult;
|
||||
}
|
||||
else if (oldResult.Result.Score != newResult.Result.Score)
|
||||
|
|
@ -286,4 +290,4 @@ namespace Wox.ViewModel
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
|
@ -20,10 +21,12 @@ namespace Wox.ViewModel
|
|||
{
|
||||
public class SettingWindowViewModel : BaseModel
|
||||
{
|
||||
private readonly Updater _updater;
|
||||
private readonly WoxJsonStorage<Settings> _storage;
|
||||
|
||||
public SettingWindowViewModel()
|
||||
public SettingWindowViewModel(Updater updater)
|
||||
{
|
||||
_updater = updater;
|
||||
_storage = new WoxJsonStorage<Settings>();
|
||||
Settings = _storage.Load();
|
||||
Settings.PropertyChanged += (s, e) =>
|
||||
|
|
@ -39,6 +42,10 @@ namespace Wox.ViewModel
|
|||
|
||||
public Settings Settings { get; set; }
|
||||
|
||||
public async void UpdateApp()
|
||||
{
|
||||
await _updater.UpdateApp();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
|
|
@ -73,7 +80,7 @@ namespace Wox.ViewModel
|
|||
public List<string> QuerySearchPrecisionStrings
|
||||
{
|
||||
get
|
||||
{
|
||||
{
|
||||
var precisionStrings = new List<string>();
|
||||
|
||||
var enumList = Enum.GetValues(typeof(StringMatcher.SearchPrecisionScore)).Cast<StringMatcher.SearchPrecisionScore>().ToList();
|
||||
|
|
@ -88,6 +95,50 @@ namespace Wox.ViewModel
|
|||
public List<Language> Languages => _translater.LoadAvailableLanguages();
|
||||
public IEnumerable<int> MaxResultsRange => Enumerable.Range(2, 16);
|
||||
|
||||
public string TestProxy()
|
||||
{
|
||||
var proxyServer = Settings.Proxy.Server;
|
||||
var proxyUserName = Settings.Proxy.UserName;
|
||||
if (string.IsNullOrEmpty(proxyServer))
|
||||
{
|
||||
return InternationalizationManager.Instance.GetTranslation("serverCantBeEmpty");
|
||||
}
|
||||
if (Settings.Proxy.Port <= 0)
|
||||
{
|
||||
return InternationalizationManager.Instance.GetTranslation("portCantBeEmpty");
|
||||
}
|
||||
|
||||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_updater.GitHubRepository);
|
||||
|
||||
if (string.IsNullOrEmpty(proxyUserName) || string.IsNullOrEmpty(Settings.Proxy.Password))
|
||||
{
|
||||
request.Proxy = new WebProxy(proxyServer, Settings.Proxy.Port);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Proxy = new WebProxy(proxyServer, Settings.Proxy.Port)
|
||||
{
|
||||
Credentials = new NetworkCredential(proxyUserName, Settings.Proxy.Password)
|
||||
};
|
||||
}
|
||||
try
|
||||
{
|
||||
var response = (HttpWebResponse)request.GetResponse();
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
return InternationalizationManager.Instance.GetTranslation("proxyIsCorrect");
|
||||
}
|
||||
else
|
||||
{
|
||||
return InternationalizationManager.Instance.GetTranslation("proxyConnectFailed");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return InternationalizationManager.Instance.GetTranslation("proxyConnectFailed");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region plugin
|
||||
|
|
@ -220,7 +271,7 @@ namespace Wox.ViewModel
|
|||
},
|
||||
new Result
|
||||
{
|
||||
Title = $"Open Source: {Constant.Repository}",
|
||||
Title = $"Open Source: {_updater.GitHubRepository}",
|
||||
SubTitle = "Please star it!"
|
||||
}
|
||||
};
|
||||
|
|
@ -330,8 +381,8 @@ namespace Wox.ViewModel
|
|||
|
||||
#region about
|
||||
|
||||
public static string Github => Constant.Repository;
|
||||
public static string ReleaseNotes => @"https://github.com/Wox-launcher/Wox/releases/latest";
|
||||
public string Github => _updater.GitHubRepository;
|
||||
public string ReleaseNotes => _updater.GitHubRepository + @"/releases/latest";
|
||||
public static string Version => Constant.Version;
|
||||
public string ActivatedTimes => string.Format(_translater.GetTranslation("about_activate_times"), Settings.ActivateTimes);
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@
|
|||
<Compile Include="ResultListBox.xaml.cs">
|
||||
<DependentUpon>ResultListBox.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Settings.cs" />
|
||||
<Compile Include="Storage\HistoryItem.cs" />
|
||||
<Compile Include="Storage\QueryHistory.cs" />
|
||||
<Compile Include="Storage\TopMostRecord.cs" />
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ trigger:
|
|||
- dev
|
||||
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016' #'windows-latest'
|
||||
vmImage: 'vs2017-win2016' #'due to windows SDK dependency for building UWP project'
|
||||
|
||||
variables:
|
||||
solution: '**/*.sln'
|
||||
|
|
|
|||
Loading…
Reference in a new issue