diff --git a/Plugins/Wox.Plugin.ControlPanel/Main.cs b/Plugins/Wox.Plugin.ControlPanel/Main.cs index 0e0a8898d6..77b57fe865 100644 --- a/Plugins/Wox.Plugin.ControlPanel/Main.cs +++ b/Plugins/Wox.Plugin.ControlPanel/Main.cs @@ -27,7 +27,6 @@ namespace Wox.Plugin.ControlPanel Directory.CreateDirectory(iconFolder); } - foreach (ControlPanelItem item in controlPanelItems) { if (!File.Exists(iconFolder + item.GUID + fileType) && item.Icon != null) @@ -43,7 +42,10 @@ namespace Wox.Plugin.ControlPanel foreach (var item in controlPanelItems) { - item.Score = Score(item, query.Search); + var titleMatch = StringMatcher.FuzzySearch(query.Search, item.LocalizedString); + var subTitleMatch = StringMatcher.FuzzySearch(query.Search, item.InfoTip); + + item.Score = Math.Max(titleMatch.Score, subTitleMatch.Score); if (item.Score > 0) { var result = new Result @@ -66,6 +68,16 @@ namespace Wox.Plugin.ControlPanel return true; } }; + + if (item.Score == titleMatch.Score) + { + result.TitleHighlightData = titleMatch.MatchData; + } + else + { + result.SubTitleHighlightData = subTitleMatch.MatchData; + } + results.Add(result); } } @@ -74,26 +86,6 @@ namespace Wox.Plugin.ControlPanel return panelItems; } - private int Score(ControlPanelItem item, string query) - { - var scores = new List {0}; - if (!string.IsNullOrEmpty(item.LocalizedString)) - { - var score1 = StringMatcher.FuzzySearch(query, item.LocalizedString).ScoreAfterSearchPrecisionFilter(); - var score2 = StringMatcher.ScoreForPinyin(item.LocalizedString, query); - scores.Add(score1); - scores.Add(score2); - } - if (!string.IsNullOrEmpty(item.InfoTip)) - { - var score1 = StringMatcher.FuzzySearch(query, item.InfoTip).ScoreAfterSearchPrecisionFilter(); - var score2 = StringMatcher.ScoreForPinyin(item.InfoTip, query); - scores.Add(score1); - scores.Add(score2); - } - return scores.Max(); - } - public string GetTranslatedPluginTitle() { return context.API.GetTranslation("wox_plugin_controlpanel_plugin_name"); diff --git a/Plugins/Wox.Plugin.Everything/Main.cs b/Plugins/Wox.Plugin.Everything/Main.cs index 7969a079fe..4188f89dda 100644 --- a/Plugins/Wox.Plugin.Everything/Main.cs +++ b/Plugins/Wox.Plugin.Everything/Main.cs @@ -55,6 +55,7 @@ namespace Wox.Plugin.Everything r.Title = Path.GetFileName(path); r.SubTitle = path; r.IcoPath = path; + r.TitleHighlightData = StringMatcher.FuzzySearch(keyword, Path.GetFileName(path)).MatchData; r.Action = c => { bool hide; @@ -78,6 +79,7 @@ namespace Wox.Plugin.Everything return hide; }; r.ContextData = s; + r.SubTitleHighlightData = StringMatcher.FuzzySearch(keyword, path).MatchData; results.Add(r); } } diff --git a/Plugins/Wox.Plugin.Folder/Main.cs b/Plugins/Wox.Plugin.Folder/Main.cs index 2c8cf2703d..d7b5a15697 100644 --- a/Plugins/Wox.Plugin.Folder/Main.cs +++ b/Plugins/Wox.Plugin.Folder/Main.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; +using Wox.Infrastructure; using Wox.Infrastructure.Storage; namespace Wox.Plugin.Folder @@ -58,13 +59,14 @@ namespace Wox.Plugin.Folder return results; } - private Result CreateFolderResult(string title, string path, string queryActionKeyword) + private Result CreateFolderResult(string title, string path, Query query) { return new Result { Title = title, IcoPath = path, SubTitle = "Ctrl + Enter to open the directory", + TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData, Action = c => { if (c.SpecialKeyState.CtrlPressed) @@ -82,7 +84,9 @@ namespace Wox.Plugin.Folder } string changeTo = path.EndsWith("\\") ? path : path + "\\"; - _context.API.ChangeQuery(string.IsNullOrEmpty(queryActionKeyword)? changeTo : queryActionKeyword + " " + changeTo); + _context.API.ChangeQuery(string.IsNullOrEmpty(query.ActionKeyword) ? + changeTo : + query.ActionKeyword + " " + changeTo); return false; } }; @@ -93,8 +97,8 @@ namespace Wox.Plugin.Folder string search = query.Search.ToLower(); var userFolderLinks = _settings.FolderLinks.Where( x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase)); - var results = userFolderLinks.Select(item => CreateFolderResult(item.Nickname, item.Path, query.ActionKeyword)) - .ToList(); + var results = userFolderLinks.Select(item => + CreateFolderResult(item.Nickname, item.Path, query)).ToList(); return results; } @@ -132,34 +136,40 @@ namespace Wox.Plugin.Folder incompleteName = search.Substring(index + 1).ToLower(); search = search.Substring(0, index + 1); if (!Directory.Exists(search)) + { return results; + } } else + { return results; + } } else - { // folder exist, add \ at the end of doesn't exist + { + // folder exist, add \ at the end of doesn't exist if (!search.EndsWith("\\")) + { search += "\\"; + } } results.Add(CreateOpenCurrentFolderResult(incompleteName, search)); - - var directoryInfo = new DirectoryInfo(search); - - var searchOption= SearchOption.TopDirectoryOnly; + var searchOption = SearchOption.TopDirectoryOnly; incompleteName += "*"; - if (incompleteName.StartsWith(">")) // give the ability to search all folder when starting with > + // give the ability to search all folder when starting with > + if (incompleteName.StartsWith(">")) { searchOption = SearchOption.AllDirectories; incompleteName = incompleteName.Substring(1); } - + try { // search folder and add results + var directoryInfo = new DirectoryInfo(search); var fileSystemInfos = directoryInfo.GetFileSystemInfos(incompleteName, searchOption); foreach (var fileSystemInfo in fileSystemInfos) @@ -168,12 +178,12 @@ namespace Wox.Plugin.Folder var result = fileSystemInfo is DirectoryInfo - ? CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, query.ActionKeyword) - : CreateFileResult(fileSystemInfo.FullName); + ? CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, query) + : CreateFileResult(fileSystemInfo.FullName, query); results.Add(result); } } - catch(Exception e) + catch (Exception e) { if (e is UnauthorizedAccessException || e is ArgumentException) { @@ -188,12 +198,13 @@ namespace Wox.Plugin.Folder return results; } - private static Result CreateFileResult(string filePath) + private static Result CreateFileResult(string filePath, Query query) { var result = new Result { Title = Path.GetFileName(filePath), IcoPath = filePath, + TitleHighlightData = StringMatcher.FuzzySearch(query.Search, Path.GetFileName(filePath)).MatchData, Action = c => { try diff --git a/Plugins/Wox.Plugin.PluginManagement/Main.cs b/Plugins/Wox.Plugin.PluginManagement/Main.cs index 785ca3b1f3..1ee0fd7ae8 100644 --- a/Plugins/Wox.Plugin.PluginManagement/Main.cs +++ b/Plugins/Wox.Plugin.PluginManagement/Main.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using System.Windows; using Newtonsoft.Json; +using Wox.Infrastructure; using Wox.Infrastructure.Http; using Wox.Infrastructure.Logger; @@ -142,6 +143,8 @@ namespace Wox.Plugin.PluginManagement Title = r.name, SubTitle = r.description, IcoPath = "Images\\plugin.png", + TitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, r.name).MatchData, + SubTitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, r.description).MatchData, Action = c => { MessageBoxResult result = MessageBox.Show("Are you sure you wish to install the \'" + r.name + "\' plugin", @@ -191,6 +194,8 @@ namespace Wox.Plugin.PluginManagement Title = plugin.Name, SubTitle = plugin.Description, IcoPath = plugin.IcoPath, + TitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, plugin.Name).MatchData, + SubTitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, plugin.Description).MatchData, Action = e => { UnInstallPlugin(plugin); diff --git a/Plugins/Wox.Plugin.Program/Images/user.png b/Plugins/Wox.Plugin.Program/Images/user.png new file mode 100644 index 0000000000..2d45c1ee91 Binary files /dev/null and b/Plugins/Wox.Plugin.Program/Images/user.png differ diff --git a/Plugins/Wox.Plugin.Program/Languages/en.xaml b/Plugins/Wox.Plugin.Program/Languages/en.xaml index bb218887c4..b7202d590f 100644 --- a/Plugins/Wox.Plugin.Program/Languages/en.xaml +++ b/Plugins/Wox.Plugin.Program/Languages/en.xaml @@ -31,6 +31,7 @@ Successfully updated file suffixes File suffixes can't be empty + Run As Different User Run As Administrator Open containing folder Disable this program from displaying diff --git a/Plugins/Wox.Plugin.Program/Main.cs b/Plugins/Wox.Plugin.Program/Main.cs index a37e9f2028..a8f74655fb 100644 --- a/Plugins/Wox.Plugin.Program/Main.cs +++ b/Plugins/Wox.Plugin.Program/Main.cs @@ -78,8 +78,8 @@ namespace Wox.Plugin.Program } var results1 = win32.AsParallel() - .Where(p => p.Enabled) - .Select(p => p.Result(query.Search, _context.API)); + .Where(p => p.Enabled) + .Select(p => p.Result(query.Search, _context.API)); var results2 = uwps.AsParallel() .Where(p => p.Enabled) @@ -191,12 +191,12 @@ namespace Wox.Plugin.Program ); } - public static bool StartProcess(ProcessStartInfo info) + public static bool StartProcess(Func runProcess, ProcessStartInfo info) { bool hide; try { - Process.Start(info); + runProcess(info); hide = true; } catch (Exception) diff --git a/Plugins/Wox.Plugin.Program/Programs/UWP.cs b/Plugins/Wox.Plugin.Program/Programs/UWP.cs index 2c608807b4..66b23eb817 100644 --- a/Plugins/Wox.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Wox.Plugin.Program/Programs/UWP.cs @@ -35,7 +35,6 @@ namespace Wox.Plugin.Program.Programs public UWP(Package package) { - Location = package.InstalledLocation.Path; Name = package.Id.Name; FullName = package.Id.FullName; @@ -266,11 +265,9 @@ namespace Wox.Plugin.Program.Programs private int Score(string query) { - var score1 = StringMatcher.FuzzySearch(query, DisplayName).ScoreAfterSearchPrecisionFilter(); - var score2 = StringMatcher.ScoreForPinyin(DisplayName, query); - var score3 = StringMatcher.FuzzySearch(query, Description).ScoreAfterSearchPrecisionFilter(); - var score4 = StringMatcher.ScoreForPinyin(Description, query); - var score = new[] { score1, score2, score3, score4 }.Max(); + var displayNameMatch = StringMatcher.FuzzySearch(query, DisplayName); + var descriptionMatch = StringMatcher.FuzzySearch(query, Description); + var score = new[] { displayNameMatch.Score, descriptionMatch.Score }.Max(); return score; } @@ -299,14 +296,18 @@ namespace Wox.Plugin.Program.Programs Description.Substring(0, DisplayName.Length) == DisplayName) { result.Title = Description; + result.TitleHighlightData = StringMatcher.FuzzySearch(query, Description).MatchData; } else if (!string.IsNullOrEmpty(Description)) { - result.Title = $"{DisplayName}: {Description}"; + var title = $"{DisplayName}: {Description}"; + result.Title = title; + result.TitleHighlightData = StringMatcher.FuzzySearch(query, title).MatchData; } else { result.Title = DisplayName; + result.TitleHighlightData = StringMatcher.FuzzySearch(query, DisplayName).MatchData; } return result; } @@ -320,7 +321,7 @@ namespace Wox.Plugin.Program.Programs Title = api.GetTranslation("wox_plugin_program_open_containing_folder"), Action = _ => { - var hide = Main.StartProcess(new ProcessStartInfo(Package.Location)); + var hide = Main.StartProcess(Process.Start, new ProcessStartInfo(Package.Location)); return hide; }, IcoPath = "Images/folder.png" diff --git a/Plugins/Wox.Plugin.Program/Programs/Win32.cs b/Plugins/Wox.Plugin.Program/Programs/Win32.cs index 36e08a3c46..f204477bb3 100644 --- a/Plugins/Wox.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Wox.Plugin.Program/Programs/Win32.cs @@ -6,10 +6,12 @@ using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Text; +using System.Threading.Tasks; using Microsoft.Win32; using Shell; using Wox.Infrastructure; using Wox.Plugin.Program.Logger; +using Wox.Plugin.SharedCommands; namespace Wox.Plugin.Program.Programs { @@ -33,12 +35,10 @@ namespace Wox.Plugin.Program.Programs private int Score(string query) { - var score1 = StringMatcher.FuzzySearch(query, Name).ScoreAfterSearchPrecisionFilter(); - var score2 = StringMatcher.ScoreForPinyin(Name, query); - var score3 = StringMatcher.FuzzySearch(query, Description).ScoreAfterSearchPrecisionFilter(); - var score4 = StringMatcher.ScoreForPinyin(Description, query); - var score5 = StringMatcher.FuzzySearch(query, ExecutableName).ScoreAfterSearchPrecisionFilter(); - var score = new[] { score1, score2, score3, score4, score5 }.Max(); + var nameMatch = StringMatcher.FuzzySearch(query, Name); + var descriptionMatch = StringMatcher.FuzzySearch(query, Description); + var executableNameMatch = StringMatcher.FuzzySearch(query, ExecutableName); + var score = new[] { nameMatch.Score, descriptionMatch.Score, executableNameMatch.Score }.Max(); return score; } @@ -64,7 +64,7 @@ namespace Wox.Plugin.Program.Programs FileName = FullPath, WorkingDirectory = ParentDirectory }; - var hide = Main.StartProcess(info); + var hide = Main.StartProcess(Process.Start, info); return hide; } }; @@ -73,14 +73,18 @@ namespace Wox.Plugin.Program.Programs Description.Substring(0, Name.Length) == Name) { result.Title = Description; + result.TitleHighlightData = StringMatcher.FuzzySearch(query, Description).MatchData; } else if (!string.IsNullOrEmpty(Description)) { - result.Title = $"{Name}: {Description}"; + var title = $"{Name}: {Description}"; + result.Title = title; + result.TitleHighlightData = StringMatcher.FuzzySearch(query, title).MatchData; } else { result.Title = Name; + result.TitleHighlightData = StringMatcher.FuzzySearch(query, Name).MatchData; } return result; @@ -91,6 +95,19 @@ namespace Wox.Plugin.Program.Programs { var contextMenus = new List { + new Result + { + Title = api.GetTranslation("wox_plugin_program_run_as_different_user"), + Action = _ => + { + var info = FullPath.SetProcessStartInfo(ParentDirectory); + + Task.Run(() => Main.StartProcess(ShellCommand.RunAsDifferentUser, info)); + + return true; + }, + IcoPath = "Images/user.png" + }, new Result { Title = api.GetTranslation("wox_plugin_program_run_as_administrator"), @@ -102,7 +119,7 @@ namespace Wox.Plugin.Program.Programs WorkingDirectory = ParentDirectory, Verb = "runas" }; - var hide = Main.StartProcess(info); + var hide = Main.StartProcess(Process.Start, info); return hide; }, IcoPath = "Images/cmd.png" @@ -112,7 +129,7 @@ namespace Wox.Plugin.Program.Programs Title = api.GetTranslation("wox_plugin_program_open_containing_folder"), Action = _ => { - var hide = Main.StartProcess(new ProcessStartInfo(ParentDirectory)); + var hide = Main.StartProcess(Process.Start, new ProcessStartInfo(ParentDirectory)); return hide; }, IcoPath = "Images/folder.png" diff --git a/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj b/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj index 850f972ca4..91dada0670 100644 --- a/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj +++ b/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj @@ -112,6 +112,9 @@ PreserveNewest + + Always + MSBuild:Compile Designer diff --git a/Plugins/Wox.Plugin.Shell/Images/user.png b/Plugins/Wox.Plugin.Shell/Images/user.png new file mode 100644 index 0000000000..2d45c1ee91 Binary files /dev/null and b/Plugins/Wox.Plugin.Shell/Images/user.png differ diff --git a/Plugins/Wox.Plugin.Shell/Languages/en.xaml b/Plugins/Wox.Plugin.Shell/Languages/en.xaml index 6f56e83f58..0d0a41b3f1 100644 --- a/Plugins/Wox.Plugin.Shell/Languages/en.xaml +++ b/Plugins/Wox.Plugin.Shell/Languages/en.xaml @@ -5,6 +5,7 @@ Replace Win+R Do not close Command Prompt after command execution Always run as administrator + Run as different user Shell Allows to execute system commands from Wox. Commands should start with > this command has been executed {0} times diff --git a/Plugins/Wox.Plugin.Shell/Main.cs b/Plugins/Wox.Plugin.Shell/Main.cs index 323cfb5cd3..1a5174c4e4 100644 --- a/Plugins/Wox.Plugin.Shell/Main.cs +++ b/Plugins/Wox.Plugin.Shell/Main.cs @@ -1,8 +1,10 @@ -using System; +using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; +using System.Threading.Tasks; using System.Windows; using WindowsInput; using WindowsInput.Native; @@ -84,7 +86,7 @@ namespace Wox.Plugin.Shell IcoPath = Image, Action = c => { - Execute(m); + Execute(Process.Start, PrepareProcessStartInfo(m)); return true; } })); @@ -117,7 +119,7 @@ namespace Wox.Plugin.Shell IcoPath = Image, Action = c => { - Execute(m.Key); + Execute(Process.Start, PrepareProcessStartInfo(m.Key)); return true; } }; @@ -136,7 +138,7 @@ namespace Wox.Plugin.Shell IcoPath = Image, Action = c => { - Execute(cmd); + Execute(Process.Start, PrepareProcessStartInfo(cmd)); return true; } }; @@ -154,14 +156,14 @@ namespace Wox.Plugin.Shell IcoPath = Image, Action = c => { - Execute(m.Key); + Execute(Process.Start, PrepareProcessStartInfo(m.Key)); return true; } }).Take(5); return history.ToList(); } - private void Execute(string command, bool runAsAdministrator = false) + private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false) { command = command.Trim(); command = Environment.ExpandEnvironmentVariables(command); @@ -212,19 +214,33 @@ namespace Wox.Plugin.Shell } else { - return; + throw new NotImplementedException(); } info.UseShellExecute = true; + _settings.AddCmdHistory(command); + + return info; + } + + private void Execute(Func startProcess,ProcessStartInfo info) + { try { - Process.Start(info); - _settings.AddCmdHistory(command); + startProcess(info); } catch (FileNotFoundException e) { - MessageBox.Show($"Command not found: {e.Message}"); + var name = "Plugin: Shell"; + var message = $"Command not found: {e.Message}"; + _context.API.ShowMsg(name, message); + } + catch(Win32Exception e) + { + var name = "Plugin: Shell"; + var message = $"Error running the command: {e.Message}"; + _context.API.ShowMsg(name, message); } } @@ -306,19 +322,31 @@ namespace Wox.Plugin.Shell public List LoadContextMenus(Result selectedResult) { - return new List + var resultlist = new List { - new Result - { - Title = _context.API.GetTranslation("wox_plugin_cmd_run_as_administrator"), - Action = c => - { - Execute(selectedResult.Title, true); - return true; - }, - IcoPath = Image - } - }; + new Result + { + Title = _context.API.GetTranslation("wox_plugin_cmd_run_as_different_user"), + Action = c => + { + Task.Run(() =>Execute(ShellCommand.RunAsDifferentUser, PrepareProcessStartInfo(selectedResult.Title))); + return true; + }, + IcoPath = "Images/user.png" + }, + new Result + { + Title = _context.API.GetTranslation("wox_plugin_cmd_run_as_administrator"), + Action = c => + { + Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, true)); + return true; + }, + IcoPath = Image + } + }; + + return resultlist; } } } diff --git a/Plugins/Wox.Plugin.Shell/Wox.Plugin.Shell.csproj b/Plugins/Wox.Plugin.Shell/Wox.Plugin.Shell.csproj index 2291f78bb8..270a55c3b4 100644 --- a/Plugins/Wox.Plugin.Shell/Wox.Plugin.Shell.csproj +++ b/Plugins/Wox.Plugin.Shell/Wox.Plugin.Shell.csproj @@ -70,6 +70,9 @@ + + Always + MSBuild:Compile Designer diff --git a/Plugins/Wox.Plugin.Sys/Main.cs b/Plugins/Wox.Plugin.Sys/Main.cs index 32e888514d..e888dbb378 100644 --- a/Plugins/Wox.Plugin.Sys/Main.cs +++ b/Plugins/Wox.Plugin.Sys/Main.cs @@ -56,12 +56,21 @@ namespace Wox.Plugin.Sys var results = new List(); foreach (var c in commands) { - var titleScore = StringMatcher.FuzzySearch(query.Search, c.Title).ScoreAfterSearchPrecisionFilter(); - var subTitleScore = StringMatcher.FuzzySearch(query.Search, c.SubTitle).ScoreAfterSearchPrecisionFilter(); - var score = Math.Max(titleScore, subTitleScore); + var titleMatch = StringMatcher.FuzzySearch(query.Search, c.Title); + var subTitleMatch = StringMatcher.FuzzySearch(query.Search, c.SubTitle); + + var score = Math.Max(titleMatch.Score, subTitleMatch.Score); if (score > 0) { c.Score = score; + if (score == titleMatch.Score) + { + c.TitleHighlightData = titleMatch.MatchData; + } + else + { + c.SubTitleHighlightData = subTitleMatch.MatchData; + } results.Add(c); } } diff --git a/Wox.Infrastructure/Alphabet.cs b/Wox.Infrastructure/Alphabet.cs index d7fae882cb..63e178ba1e 100644 --- a/Wox.Infrastructure/Alphabet.cs +++ b/Wox.Infrastructure/Alphabet.cs @@ -16,16 +16,22 @@ namespace Wox.Infrastructure private static ConcurrentDictionary PinyinCache; private static BinaryStorage> _pinyinStorage; private static Settings _settings; - + public static void Initialize(Settings settings) { _settings = settings; + InitializePinyinHelpers(); + } + + private static void InitializePinyinHelpers() + { Format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); Stopwatch.Normal("|Wox.Infrastructure.Alphabet.Initialize|Preload pinyin cache", () => { _pinyinStorage = new BinaryStorage>("Pinyin"); PinyinCache = _pinyinStorage.TryLoad(new ConcurrentDictionary()); + // force pinyin library static constructor initialize PinyinHelper.toHanyuPinyinStringArray('T', Format); }); @@ -34,6 +40,10 @@ namespace Wox.Infrastructure public static void Save() { + if (!_settings.ShouldUsePinyin) + { + return; + } _pinyinStorage.Save(PinyinCache); } @@ -68,39 +78,36 @@ namespace Wox.Infrastructure /// public static string[][] PinyinComination(string characters) { - if (_settings.ShouldUsePinyin && !string.IsNullOrEmpty(characters)) + if (!_settings.ShouldUsePinyin || string.IsNullOrEmpty(characters)) { - if (!PinyinCache.ContainsKey(characters)) - { + return Empty2DStringArray; + } - var allPinyins = new List(); - foreach (var c in characters) + if (!PinyinCache.ContainsKey(characters)) + { + var allPinyins = new List(); + foreach (var c in characters) + { + var pinyins = PinyinHelper.toHanyuPinyinStringArray(c, Format); + if (pinyins != null) { - var pinyins = PinyinHelper.toHanyuPinyinStringArray(c, Format); - if (pinyins != null) - { - var r = pinyins.Distinct().ToArray(); - allPinyins.Add(r); - } - else - { - var r = new[] { c.ToString() }; - allPinyins.Add(r); - } + var r = pinyins.Distinct().ToArray(); + allPinyins.Add(r); } + else + { + var r = new[] { c.ToString() }; + allPinyins.Add(r); + } + } - var combination = allPinyins.Aggregate(Combination).Select(c => c.Split(';')).ToArray(); - PinyinCache[characters] = combination; - return combination; - } - else - { - return PinyinCache[characters]; - } + var combination = allPinyins.Aggregate(Combination).Select(c => c.Split(';')).ToArray(); + PinyinCache[characters] = combination; + return combination; } else { - return Empty2DStringArray; + return PinyinCache[characters]; } } @@ -142,7 +149,5 @@ namespace Wox.Infrastructure ).ToArray(); return combination; } - - } } diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index 5b82b18918..58ffa336fc 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -1,16 +1,19 @@ -using System; +using System; +using System.Collections.Generic; using System.Linq; using System.Text; 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 bool ShouldUsePinyin { get; set; } [Obsolete("This method is obsolete and should not be used. Please use the static function StringMatcher.FuzzySearch")] public static int Score(string source, string target) @@ -54,6 +57,9 @@ namespace Wox.Infrastructure var firstMatchIndex = -1; var lastMatchIndex = 0; char ch; + + var indexList = new List(); + for (var idx = 0; idx < len; idx++) { ch = stringToCompare[idx]; @@ -63,6 +69,7 @@ namespace Wox.Infrastructure firstMatchIndex = idx; lastMatchIndex = idx + 1; + indexList.Add(idx); sb.Append(opt.Prefix + ch + opt.Suffix); patternIdx += 1; } @@ -82,27 +89,38 @@ namespace Wox.Infrastructure // return rendered string if we have a match for every char if (patternIdx == pattern.Length) { - return new MatchResult + var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex); + var pinyinScore = ScoreForPinyin(stringToCompare, query); + + var result = new MatchResult { Success = true, - Value = sb.ToString(), - Score = CalScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex) + MatchData = indexList, + RawScore = Math.Max(score, pinyinScore) }; + + return result; } return new MatchResult { Success = false }; } - private static int CalScore(string query, string stringToCompare, int firstIndex, int matchLen) + private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen) { - //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, while the score is lower if they are more spread out + // 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, + // while the score is lower if they are more spread out var score = 100 * (query.Length + 1) / ((1 + firstIndex) + (matchLen + 1)); - //a match with less characters assigning more weights + + // A match with less characters assigning more weights if (stringToCompare.Length - query.Length < 5) + { score += 20; + } else if (stringToCompare.Length - query.Length < 10) + { score += 10; + } return score; } @@ -114,21 +132,13 @@ namespace Wox.Infrastructure None = 0 } - public static bool IsSearchPrecisionScoreMet(this MatchResult matchResult) - { - var precisionScore = (SearchPrecisionScore)Enum.Parse(typeof(SearchPrecisionScore), - UserSettingSearchPrecision ?? SearchPrecisionScore.Regular.ToString()); - return matchResult.Score >= (int)precisionScore; - } - - public static int ScoreAfterSearchPrecisionFilter(this MatchResult matchResult) - { - return matchResult.IsSearchPrecisionScoreMet() ? matchResult.Score : 0; - - } - public static int ScoreForPinyin(string source, string target) { + if (!ShouldUsePinyin) + { + return 0; + } + if (!string.IsNullOrEmpty(source) && !string.IsNullOrEmpty(target)) { if (Alphabet.ContainsChinese(source)) @@ -158,12 +168,48 @@ namespace Wox.Infrastructure public class MatchResult { public bool Success { get; set; } - public int Score { get; set; } - + /// - /// highlight string + /// The final score of the match result with all search precision filters applied. /// - public string Value { get; set; } + public int Score { get; private set; } + + /// + /// The raw calculated search score without any search precision filtering applied. + /// + private int _rawScore; + public int RawScore + { + get { return _rawScore; } + set + { + _rawScore = value; + Score = ApplySearchPrecisionFilter(_rawScore); + } + } + + /// + /// Matched data to highlight. + /// + public List MatchData { get; set; } + + public bool IsSearchPrecisionScoreMet() + { + return IsSearchPrecisionScoreMet(Score); + } + + private bool IsSearchPrecisionScoreMet(int score) + { + var precisionScore = (SearchPrecisionScore)Enum.Parse( + typeof(SearchPrecisionScore), + UserSettingSearchPrecision ?? SearchPrecisionScore.Regular.ToString()); + return score >= (int)precisionScore; + } + + private int ApplySearchPrecisionFilter(int score) + { + return IsSearchPrecisionScoreMet(score) ? score : 0; + } } public class MatchOption diff --git a/Wox.Infrastructure/UserSettings/Settings.cs b/Wox.Infrastructure/UserSettings/Settings.cs index 7371ea5d05..de5a2e6624 100644 --- a/Wox.Infrastructure/UserSettings/Settings.cs +++ b/Wox.Infrastructure/UserSettings/Settings.cs @@ -24,7 +24,17 @@ namespace Wox.Infrastructure.UserSettings /// /// when false Alphabet static service will always return empty results /// - public bool ShouldUsePinyin { get; set; } = true; + private bool _shouldUsePinyin = true; + public bool ShouldUsePinyin + { + get { return _shouldUsePinyin; } + set + { + _shouldUsePinyin = value; + StringMatcher.ShouldUsePinyin = value; + } + } + private string _querySearchPrecision { get; set; } = StringMatcher.SearchPrecisionScore.Regular.ToString(); public string QuerySearchPrecision diff --git a/Wox.Plugin/Result.cs b/Wox.Plugin/Result.cs index 592bcac5c6..6e0559d35f 100644 --- a/Wox.Plugin/Result.cs +++ b/Wox.Plugin/Result.cs @@ -42,6 +42,16 @@ namespace Wox.Plugin public int Score { get; set; } + /// + /// A list of indexes for the characters to be highlighted in Title + /// + public IList TitleHighlightData { get; set; } + + /// + /// A list of indexes for the characters to be highlighted in SubTitle + /// + public IList SubTitleHighlightData { get; set; } + /// /// Only resulsts that originQuery match with curren query will be displayed in the panel /// @@ -69,7 +79,9 @@ namespace Wox.Plugin var equality = string.Equals(r?.Title, Title) && string.Equals(r?.SubTitle, SubTitle) && - string.Equals(r?.IcoPath, IcoPath); + string.Equals(r?.IcoPath, IcoPath) && + TitleHighlightData == r.TitleHighlightData && + SubTitleHighlightData == r.SubTitleHighlightData; return equality; } diff --git a/Wox.Plugin/SharedCommands/ShellCommand.cs b/Wox.Plugin/SharedCommands/ShellCommand.cs index d7071e7353..9da535150c 100644 --- a/Wox.Plugin/SharedCommands/ShellCommand.cs +++ b/Wox.Plugin/SharedCommands/ShellCommand.cs @@ -2,14 +2,65 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace Wox.Plugin.SharedCommands { public static class ShellCommand { - public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory="", string arguments = "", string verb = "") + public delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam); + [DllImport("user32.dll")] static extern bool EnumThreadWindows(uint threadId, EnumThreadDelegate lpfn, IntPtr lParam); + [DllImport("user32.dll")] static extern int GetWindowText(IntPtr hwnd, StringBuilder lpString, int nMaxCount); + [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr hwnd); + + private static bool containsSecurityWindow; + + public static Process RunAsDifferentUser(ProcessStartInfo processStartInfo) + { + processStartInfo.Verb = "RunAsUser"; + var process = Process.Start(processStartInfo); + + containsSecurityWindow = false; + while (!containsSecurityWindow) // wait for windows to bring up the "Windows Security" dialog + { + CheckSecurityWindow(); + Thread.Sleep(25); + } + while (containsSecurityWindow) // while this process contains a "Windows Security" dialog, stay open + { + containsSecurityWindow = false; + CheckSecurityWindow(); + Thread.Sleep(50); + } + + return process; + } + + private static void CheckSecurityWindow() + { + ProcessThreadCollection ptc = Process.GetCurrentProcess().Threads; + for (int i = 0; i < ptc.Count; i++) + EnumThreadWindows((uint)ptc[i].Id, CheckSecurityThread, IntPtr.Zero); + } + + private static bool CheckSecurityThread(IntPtr hwnd, IntPtr lParam) + { + if (GetWindowTitle(hwnd) == "Windows Security") + containsSecurityWindow = true; + return true; + } + + private static string GetWindowTitle(IntPtr hwnd) + { + StringBuilder sb = new StringBuilder(GetWindowTextLength(hwnd) + 1); + GetWindowText(hwnd, sb, sb.Capacity); + return sb.ToString(); + } + + public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", string arguments = "", string verb = "") { var info = new ProcessStartInfo { diff --git a/Wox.Test/FuzzyMatcherTest.cs b/Wox.Test/FuzzyMatcherTest.cs index ce1746a3a3..21563f91f9 100644 --- a/Wox.Test/FuzzyMatcherTest.cs +++ b/Wox.Test/FuzzyMatcherTest.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using NUnit.Framework; using Wox.Infrastructure; +using Wox.Infrastructure.UserSettings; using Wox.Plugin; namespace Wox.Test @@ -11,7 +12,7 @@ namespace Wox.Test [TestFixture] public class FuzzyMatcherTest { - public List GetSearchStrings() + public List GetSearchStrings() => new List { "Chrome", @@ -48,14 +49,13 @@ namespace Wox.Test "aac" }; - var results = new List(); foreach (var str in sources) { results.Add(new Result { Title = str, - Score = StringMatcher.FuzzySearch("inst", str).Score + Score = StringMatcher.FuzzySearch("inst", str).RawScore }); } @@ -72,7 +72,7 @@ namespace Wox.Test { var compareString = "Can have rum only in my glass"; - var scoreResult = StringMatcher.FuzzySearch(searchString, compareString).Score; + var scoreResult = StringMatcher.FuzzySearch(searchString, compareString).RawScore; Assert.True(scoreResult == 0); } @@ -129,13 +129,12 @@ namespace Wox.Test .ToList(); var results = new List(); - foreach (var str in searchStrings) { results.Add(new Result { Title = str, - Score = StringMatcher.FuzzySearch(searchTerm, str).Score + Score = StringMatcher.FuzzySearch(searchTerm, str).RawScore }); } @@ -168,8 +167,11 @@ namespace Wox.Test [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)] - public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual(string queryString, string compareString, - int expectedPrecisionScore, bool expectedPrecisionResult) + public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual( + string queryString, + string compareString, + int expectedPrecisionScore, + bool expectedPrecisionResult) { var expectedPrecisionString = (StringMatcher.SearchPrecisionScore)expectedPrecisionScore; StringMatcher.UserSettingSearchPrecision = expectedPrecisionString.ToString(); diff --git a/Wox/App.xaml.cs b/Wox/App.xaml.cs index 0ec1614bf9..9436df4758 100644 --- a/Wox/App.xaml.cs +++ b/Wox/App.xaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using System.Timers; @@ -56,6 +56,7 @@ namespace Wox Alphabet.Initialize(_settings); StringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision; + StringMatcher.ShouldUsePinyin = _settings.ShouldUsePinyin; PluginManager.LoadPlugins(_settings.PluginSettings); _mainVM = new MainViewModel(_settings); diff --git a/Wox/Converters/HighlightTextConverter.cs b/Wox/Converters/HighlightTextConverter.cs new file mode 100644 index 0000000000..b7d6d06838 --- /dev/null +++ b/Wox/Converters/HighlightTextConverter.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; +using System.Windows.Documents; + +namespace Wox.Converters +{ + public class HighlightTextConverter : IMultiValueConverter + { + public object Convert(object[] value, Type targetType, object parameter, CultureInfo cultureInfo) + { + var text = value[0] as string; + var highlightData = value[1] as List; + + var textBlock = new Span(); + + if (highlightData == null || !highlightData.Any()) + { + // No highlight data, just return the text + return new Run(text); + } + + for (var i = 0; i < text.Length; i++) + { + var currentCharacter = text.Substring(i, 1); + if (this.ShouldHighlight(highlightData, i)) + { + textBlock.Inlines.Add(new Bold(new Run(currentCharacter))); + } + else + { + textBlock.Inlines.Add(new Run(currentCharacter)); + } + } + return textBlock; + } + + public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture) + { + return new[] { DependencyProperty.UnsetValue, DependencyProperty.UnsetValue }; + } + + private bool ShouldHighlight(List highlightData, int index) + { + return highlightData.Contains(index); + } + } +} diff --git a/Wox/ResultListBox.xaml b/Wox/ResultListBox.xaml index cad02d5198..dc3100e0e7 100644 --- a/Wox/ResultListBox.xaml +++ b/Wox/ResultListBox.xaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vm="clr-namespace:Wox.ViewModel" + xmlns:converter="clr-namespace:Wox.Converters" mc:Ignorable="d" d:DesignWidth="100" d:DesignHeight="100" d:DataContext="{d:DesignInstance vm:ResultsViewModel}" MaxHeight="{Binding MaxHeight}" @@ -30,6 +31,9 @@ + + + @@ -44,9 +48,23 @@ + Text="{Binding Result.Title}"> + + + + + + + + Grid.Row="1" x:Name="SubTitle" Text="{Binding Result.SubTitle}"> + + + + + + + diff --git a/Wox/ViewModel/RelayCommand.cs b/Wox/ViewModel/RelayCommand.cs index 3a52f30007..b1dbc551c1 100644 --- a/Wox/ViewModel/RelayCommand.cs +++ b/Wox/ViewModel/RelayCommand.cs @@ -5,7 +5,6 @@ namespace Wox.ViewModel { public class RelayCommand : ICommand { - private Action _action; public RelayCommand(Action action) diff --git a/Wox/ViewModel/ResultsViewModel.cs b/Wox/ViewModel/ResultsViewModel.cs index f8197f78e6..e36ddc94d3 100644 --- a/Wox/ViewModel/ResultsViewModel.cs +++ b/Wox/ViewModel/ResultsViewModel.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Windows; +using System.Windows.Controls; using System.Windows.Data; +using System.Windows.Documents; using Wox.Infrastructure.UserSettings; using Wox.Plugin; @@ -197,8 +199,37 @@ namespace Wox.ViewModel return results; } + #endregion + #region FormattedText Dependency Property + public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached( + "FormattedText", + typeof(Inline), + typeof(ResultsViewModel), + new PropertyMetadata(null, FormattedTextPropertyChanged)); + public static void SetFormattedText(DependencyObject textBlock, IList value) + { + textBlock.SetValue(FormattedTextProperty, value); + } + + public static Inline GetFormattedText(DependencyObject textBlock) + { + return (Inline)textBlock.GetValue(FormattedTextProperty); + } + + private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var textBlock = d as TextBlock; + if (textBlock == null) return; + + var inline = (Inline)e.NewValue; + + textBlock.Inlines.Clear(); + if (inline == null) return; + + textBlock.Inlines.Add(inline); + } #endregion public class ResultCollection : ObservableCollection diff --git a/Wox/Wox.csproj b/Wox/Wox.csproj index ef77148291..4a4dc62bfc 100644 --- a/Wox/Wox.csproj +++ b/Wox/Wox.csproj @@ -158,6 +158,7 @@ Properties\SolutionAssemblyInfo.cs +