diff --git a/Plugins/Wox.Plugin.Folder/Main.cs b/Plugins/Wox.Plugin.Folder/Main.cs index f3aef7d1d5..7775398ff5 100644 --- a/Plugins/Wox.Plugin.Folder/Main.cs +++ b/Plugins/Wox.Plugin.Folder/Main.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -22,7 +22,6 @@ namespace Wox.Plugin.Folder { _storage = new PluginJsonStorage(); _settings = _storage.Load(); - InitialDriverList(); } public void Save() @@ -38,55 +37,20 @@ namespace Wox.Plugin.Folder public void Init(PluginInitContext context) { _context = context; - + InitialDriverList(); } public List Query(Query query) { + var results = GetUserFolderResults(query); + string search = query.Search.ToLower(); - - List userFolderLinks = _settings.FolderLinks.Where( - x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase)).ToList(); - List results = - userFolderLinks.Select( - item => new Result() - { - Title = item.Nickname, - IcoPath = item.Path, - SubTitle = "Ctrl + Enter to open the directory", - TitleHighlightData = StringMatcher.FuzzySearch(item.Nickname, search).MatchData, - Action = c => - { - if (c.SpecialKeyState.CtrlPressed) - { - try - { - Process.Start(item.Path); - return true; - } - catch (Exception ex) - { - MessageBox.Show(ex.Message, "Could not start " + item.Path); - return false; - } - } - _context.API.ChangeQuery($"{query.ActionKeyword} {item.Path}{(item.Path.EndsWith("\\") ? "" : "\\")}"); - return false; - }, - ContextData = item, - }).ToList(); - if (_driverNames != null && !_driverNames.Any(search.StartsWith)) return results; - //if (!input.EndsWith("\\")) - //{ - // //"c:" means "the current directory on the C drive" whereas @"c:\" means "root of the C drive" - // input = input + "\\"; - //} results.AddRange(QueryInternal_Directory_Exists(query)); - // todo temp hack for scores + // todo why was this hack here? foreach (var result in results) { result.Score += 10; @@ -94,12 +58,56 @@ namespace Wox.Plugin.Folder return results; } + + 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) + { + try + { + Process.Start(path); + return true; + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Could not start " + path); + return false; + } + } + + string changeTo = path.EndsWith("\\") ? path : path + "\\"; + _context.API.ChangeQuery(string.IsNullOrEmpty(query.ActionKeyword) ? + changeTo : + query.ActionKeyword + " " + changeTo); + return false; + } + }; + } + + private List GetUserFolderResults(Query query) + { + 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)).ToList(); + return results; + } + private void InitialDriverList() { if (_driverNames == null) { _driverNames = new List(); - DriveInfo[] allDrives = DriveInfo.GetDrives(); + var allDrives = DriveInfo.GetDrives(); foreach (DriveInfo driver in allDrives) { _driverNames.Add(driver.Name.ToLower().TrimEnd('\\')); @@ -107,121 +115,135 @@ namespace Wox.Plugin.Folder } } + private static readonly char[] _specialSearchChars = new char[] + { + '?', '*', '>' + }; + private List QueryInternal_Directory_Exists(Query query) { - var search = query.Search.ToLower(); + var search = query.Search; var results = new List(); - + var hasSpecial = search.IndexOfAny(_specialSearchChars) >= 0; string incompleteName = ""; - if (!Directory.Exists(search + "\\")) + if (hasSpecial || !Directory.Exists(search + "\\")) { - //if the last component of the path is incomplete, - //then make auto complete for it. + // if folder doesn't exist, we want to take the last part and use it afterwards to help the user + // find the right folder. int index = search.LastIndexOf('\\'); if (index > 0 && index < (search.Length - 1)) { - incompleteName = search.Substring(index + 1); - incompleteName = incompleteName.ToLower(); + 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 if (!search.EndsWith("\\")) + { search += "\\"; + } } - string firstResult = "Open current directory"; + results.Add(CreateOpenCurrentFolderResult(incompleteName, search)); + + var searchOption = SearchOption.TopDirectoryOnly; + incompleteName += "*"; + + // give the ability to search all folder when starting with > + if (incompleteName.StartsWith(">")) + { + searchOption = SearchOption.AllDirectories; + incompleteName = incompleteName.Substring(1); + } + + string searchNickname = new FolderLink { Path = query.Search }.Nickname; + try + { + // search folder and add results + var directoryInfo = new DirectoryInfo(search); + var fileSystemInfos = directoryInfo.GetFileSystemInfos(incompleteName, searchOption); + + foreach (var fileSystemInfo in fileSystemInfos) + { + 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); + } + } + catch (Exception e) + { + if (e is UnauthorizedAccessException || e is ArgumentException) + { + results.Add(new Result { Title = e.Message, Score = 501 }); + + return results; + } + + throw; + } + + return results; + } + + 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 + { + Process.Start(filePath); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Could not start " + filePath); + } + + return true; + } + }; + return result; + } + + private static Result CreateOpenCurrentFolderResult(string incompleteName, string search) + { + var firstResult = "Open current directory"; if (incompleteName.Length > 0) firstResult = "Open " + search; - results.Add(new Result + + var folderName = search.TrimEnd('\\').Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last(); + + return new Result { Title = firstResult, + SubTitle = $"Use > to search files and subfolders within {folderName}, " + + $"* to search for file extensions in {folderName} or both >* to combine the search", IcoPath = search, - Score = 10000, + Score = 500, Action = c => { Process.Start(search); return true; } - }); - - string searchNickname = new FolderLink { Path = query.Search }.Nickname; - - //Add children directories - DirectoryInfo[] dirs = new DirectoryInfo(search).GetDirectories(); - foreach (DirectoryInfo dir in dirs) - { - if ((dir.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue; - - if (incompleteName.Length != 0 && !dir.Name.ToLower().StartsWith(incompleteName)) - continue; - DirectoryInfo dirCopy = dir; - var result = new Result - { - Title = dir.Name, - IcoPath = dir.FullName, - SubTitle = "Ctrl + Enter to open the directory", - TitleHighlightData = StringMatcher.FuzzySearch(dir.Name, searchNickname).MatchData, - Action = c => - { - if (c.SpecialKeyState.CtrlPressed) - { - try - { - Process.Start(dirCopy.FullName); - return true; - } - catch (Exception ex) - { - MessageBox.Show(ex.Message, "Could not start " + dirCopy.FullName); - return false; - } - } - _context.API.ChangeQuery($"{query.ActionKeyword} {dirCopy.FullName}\\"); - return false; - } - }; - - results.Add(result); - } - - //Add children files - FileInfo[] files = new DirectoryInfo(search).GetFiles(); - foreach (FileInfo file in files) - { - if ((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue; - if (incompleteName.Length != 0 && !file.Name.ToLower().StartsWith(incompleteName)) - continue; - string filePath = file.FullName; - var result = new Result - { - Title = Path.GetFileName(filePath), - IcoPath = filePath, - TitleHighlightData = StringMatcher.FuzzySearch(file.Name, searchNickname).MatchData, - Action = c => - { - try - { - Process.Start(filePath); - } - catch (Exception ex) - { - MessageBox.Show(ex.Message, "Could not start " + filePath); - } - - return true; - } - }; - - results.Add(result); - } - - return results; + }; } public string GetTranslatedPluginTitle() diff --git a/Plugins/Wox.Plugin.Program/Main.cs b/Plugins/Wox.Plugin.Program/Main.cs index e165a10826..a37e9f2028 100644 --- a/Plugins/Wox.Plugin.Program/Main.cs +++ b/Plugins/Wox.Plugin.Program/Main.cs @@ -24,7 +24,7 @@ namespace Wox.Plugin.Program private static PluginInitContext _context; private static BinaryStorage _win32Storage; - private static BinaryStorage _uwpStorage; + private static BinaryStorage _uwpStorage; private readonly PluginJsonStorage _settingsStorage; public Main() @@ -41,7 +41,7 @@ namespace Wox.Plugin.Program }); Log.Info($"|Wox.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>"); Log.Info($"|Wox.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>"); - + var a = Task.Run(() => { if (IsStartupIndexProgramsRequired || !_win32s.Any()) @@ -68,19 +68,25 @@ namespace Wox.Plugin.Program public List Query(Query query) { + Win32[] win32; + UWP.Application[] uwps; + lock (IndexLock) - { - var results1 = _win32s.AsParallel() - .Where(p => p.Enabled) - .Select(p => p.Result(query.Search, _context.API)); - - var results2 = _uwps.AsParallel() - .Where(p => p.Enabled) - .Select(p => p.Result(query.Search, _context.API)); - - var result = results1.Concat(results2).Where(r => r.Score > 0).ToList(); - return result; + { // just take the reference inside the lock to eliminate query time issues. + win32 = _win32s; + uwps = _uwps; } + + var results1 = win32.AsParallel() + .Where(p => p.Enabled) + .Select(p => p.Result(query.Search, _context.API)); + + var results2 = uwps.AsParallel() + .Where(p => p.Enabled) + .Select(p => p.Result(query.Search, _context.API)); + + var result = results1.Concat(results2).Where(r => r != null && r.Score > 0).ToList(); + return result; } public void Init(PluginInitContext context) @@ -111,14 +117,14 @@ namespace Wox.Plugin.Program public static void IndexPrograms() { - var t1 = Task.Run(()=>IndexWin32Programs()); + var t1 = Task.Run(() => IndexWin32Programs()); - var t2 = Task.Run(()=>IndexUWPPrograms()); + var t2 = Task.Run(() => IndexUWPPrograms()); Task.WaitAll(t1, t2); _settings.LastIndexTime = DateTime.Today; - } + } public Control CreateSettingPanel() { @@ -141,7 +147,7 @@ namespace Wox.Plugin.Program var program = selectedResult.ContextData as IProgram; if (program != null) { - menuOptions = program.ContextMenus(_context.API); + menuOptions = program.ContextMenus(_context.API); } menuOptions.Add( @@ -151,7 +157,7 @@ namespace Wox.Plugin.Program Action = c => { DisableProgram(program); - _context.API.ShowMsg(_context.API.GetTranslation("wox_plugin_program_disable_dlgtitle_success"), + _context.API.ShowMsg(_context.API.GetTranslation("wox_plugin_program_disable_dlgtitle_success"), _context.API.GetTranslation("wox_plugin_program_disable_dlgtitle_success_message")); return false; }, diff --git a/Plugins/Wox.Plugin.Program/Programs/UWP.cs b/Plugins/Wox.Plugin.Program/Programs/UWP.cs index 3cd75fbae2..b86cad51cf 100644 --- a/Plugins/Wox.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Wox.Plugin.Program/Programs/UWP.cs @@ -273,11 +273,17 @@ namespace Wox.Plugin.Program.Programs public Result Result(string query, IPublicAPI api) { + var score = Score(query); + if (score <= 0) + { // no need to create result if score is 0 + return null; + } + var result = new Result { SubTitle = Package.Location, Icon = Logo, - Score = Score(query), + Score = score, ContextData = this, Action = e => { diff --git a/Plugins/Wox.Plugin.Program/Programs/Win32.cs b/Plugins/Wox.Plugin.Program/Programs/Win32.cs index b9a6e401c3..4208f82cc0 100644 --- a/Plugins/Wox.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Wox.Plugin.Program/Programs/Win32.cs @@ -43,11 +43,17 @@ namespace Wox.Plugin.Program.Programs public Result Result(string query, IPublicAPI api) { + var score = Score(query); + if (score <= 0) + { // no need to create result if this is zero + return null; + } + var result = new Result { SubTitle = FullPath, IcoPath = IcoPath, - Score = Score(query), + Score = score, ContextData = this, Action = e => { @@ -195,7 +201,7 @@ namespace Wox.Plugin.Program.Programs catch (COMException e) { // C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\MiracastView.lnk always cause exception - ProgramLogger.LogException($"|Win32|LnkProgram|{path}"+ + ProgramLogger.LogException($"|Win32|LnkProgram|{path}" + "|Error caused likely due to trying to get the description of the program", e); program.Valid = false; diff --git a/Wox.Core/Plugin/PluginManager.cs b/Wox.Core/Plugin/PluginManager.cs index d6806e5cb2..fc9afb7401 100644 --- a/Wox.Core/Plugin/PluginManager.cs +++ b/Wox.Core/Plugin/PluginManager.cs @@ -172,13 +172,13 @@ namespace Wox.Core.Plugin public static List QueryForPlugin(PluginPair pair, Query query) { - var results = new List(); try { + List results = null; var metadata = pair.Metadata; var milliseconds = Stopwatch.Debug($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", () => { - results = pair.Plugin.Query(query) ?? results; + results = pair.Plugin.Query(query) ?? new List(); UpdatePluginMetadata(results, metadata, query); }); metadata.QueryCount += 1; diff --git a/Wox.Infrastructure/Storage/WoxJsonStorage.cs b/Wox.Infrastructure/Storage/WoxJsonStorage.cs index f117aee229..da0dbd073d 100644 --- a/Wox.Infrastructure/Storage/WoxJsonStorage.cs +++ b/Wox.Infrastructure/Storage/WoxJsonStorage.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Wox.Infrastructure.Storage { - class WoxJsonStorage : JsonStrorage where T : new() + public class WoxJsonStorage : JsonStrorage where T : new() { public WoxJsonStorage() { @@ -18,4 +18,4 @@ namespace Wox.Infrastructure.Storage FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}"); } } -} +} \ No newline at end of file diff --git a/Wox.Plugin/Result.cs b/Wox.Plugin/Result.cs index aff56554af..6e0559d35f 100644 --- a/Wox.Plugin/Result.cs +++ b/Wox.Plugin/Result.cs @@ -120,7 +120,7 @@ namespace Wox.Plugin public object ContextData { get; set; } /// - /// Plugin ID that generate this result + /// Plugin ID that generated this result /// public string PluginID { get; internal set; } } diff --git a/Wox/MainWindow.xaml b/Wox/MainWindow.xaml index d1e610dfc8..7dfe8cd1e9 100644 --- a/Wox/MainWindow.xaml +++ b/Wox/MainWindow.xaml @@ -36,6 +36,7 @@ + diff --git a/Wox/Storage/TopMostRecord.cs b/Wox/Storage/TopMostRecord.cs index 4d33f91ebb..5f940f2184 100644 --- a/Wox/Storage/TopMostRecord.cs +++ b/Wox/Storage/TopMostRecord.cs @@ -1,47 +1,50 @@ using System.Collections.Generic; using System.Linq; -using Wox.Infrastructure.Storage; +using Newtonsoft.Json; using Wox.Plugin; namespace Wox.Storage { + // todo this class is not thread safe.... but used from multiple threads. public class TopMostRecord { - public Dictionary records = new Dictionary(); + [JsonProperty] + private Dictionary records = new Dictionary(); internal bool IsTopMost(Result result) { + if (records.Count == 0) + { + return false; + } + + // since this dictionary should be very small (or empty) going over it should be pretty fast. return records.Any(o => o.Value.Title == result.Title - && o.Value.SubTitle == result.SubTitle - && o.Value.PluginID == result.PluginID - && o.Key == result.OriginQuery.RawQuery); + && o.Value.SubTitle == result.SubTitle + && o.Value.PluginID == result.PluginID + && o.Key == result.OriginQuery.RawQuery); } internal void Remove(Result result) { - if (records.ContainsKey(result.OriginQuery.RawQuery)) - { - records.Remove(result.OriginQuery.RawQuery); - } + records.Remove(result.OriginQuery.RawQuery); } internal void AddOrUpdate(Result result) { - if (records.ContainsKey(result.OriginQuery.RawQuery)) + var record = new Record { - records[result.OriginQuery.RawQuery].Title = result.Title; - records[result.OriginQuery.RawQuery].SubTitle = result.SubTitle; - records[result.OriginQuery.RawQuery].PluginID = result.PluginID; - } - else - { - records.Add(result.OriginQuery.RawQuery, new Record - { - PluginID = result.PluginID, - Title = result.Title, - SubTitle = result.SubTitle - }); - } + PluginID = result.PluginID, + Title = result.Title, + SubTitle = result.SubTitle + }; + records[result.OriginQuery.RawQuery] = record; + + } + + public void Load(Dictionary dictionary) + { + records = dictionary; } } diff --git a/Wox/Storage/UserSelectedRecord.cs b/Wox/Storage/UserSelectedRecord.cs index 9005590410..ef8bf7f8ef 100644 --- a/Wox/Storage/UserSelectedRecord.cs +++ b/Wox/Storage/UserSelectedRecord.cs @@ -12,21 +12,23 @@ namespace Wox.Storage public void Add(Result result) { - if (records.ContainsKey(result.ToString())) + var key = result.ToString(); + if (records.TryGetValue(key, out int value)) { - records[result.ToString()] += 1; + records[key] = value + 1; } else { - records.Add(result.ToString(), 1); + records.Add(key, 1); + } } public int GetSelectedCount(Result result) { - if (records.ContainsKey(result.ToString())) + if (records.TryGetValue(result.ToString(), out int value)) { - return records[result.ToString()]; + return value; } return 0; } diff --git a/Wox/ViewModel/MainViewModel.cs b/Wox/ViewModel/MainViewModel.cs index 4e2ee5818f..a7e17d9a7e 100644 --- a/Wox/ViewModel/MainViewModel.cs +++ b/Wox/ViewModel/MainViewModel.cs @@ -126,6 +126,8 @@ namespace Wox.ViewModel SelectedResults.SelectPrevPage(); }); + SelectFirstResultCommand = new RelayCommand(_ => SelectedResults.SelectFirstResult()); + StartHelpCommand = new RelayCommand(_ => { Process.Start("http://doc.wox.one/"); @@ -268,6 +270,7 @@ namespace Wox.ViewModel public ICommand SelectPrevItemCommand { get; set; } public ICommand SelectNextPageCommand { get; set; } public ICommand SelectPrevPageCommand { get; set; } + public ICommand SelectFirstResultCommand { get; set; } public ICommand StartHelpCommand { get; set; } public ICommand LoadContextMenuCommand { get; set; } public ICommand LoadHistoryCommand { get; set; } @@ -415,11 +418,15 @@ namespace Wox.ViewModel var config = _settings.PluginSettings.Plugins[plugin.Metadata.ID]; if (!config.Disabled) { - var results = PluginManager.QueryForPlugin(plugin, query); UpdateResultView(results, plugin.Metadata, query); } }); + + // 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); } } @@ -628,9 +635,6 @@ namespace Wox.ViewModel /// public void UpdateResultView(List list, PluginMetadata metadata, Query originQuery) { - _queryHasReturn = true; - ProgressBarVisibility = Visibility.Hidden; - foreach (var result in list) { if (_topMostRecord.IsTopMost(result)) diff --git a/Wox/ViewModel/ResultsViewModel.cs b/Wox/ViewModel/ResultsViewModel.cs index 1496647d22..e36ddc94d3 100644 --- a/Wox/ViewModel/ResultsViewModel.cs +++ b/Wox/ViewModel/ResultsViewModel.cs @@ -109,6 +109,11 @@ namespace Wox.ViewModel SelectedIndex = NewIndex(SelectedIndex - MaxResults); } + public void SelectFirstResult() + { + SelectedIndex = NewIndex(0); + } + public void Clear() { Results.Clear(); @@ -157,7 +162,6 @@ namespace Wox.ViewModel // 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))) - .Select(t1 => t1) .ToList(); // remove result of relative complement of B in A