From c3d0e31fa63e5cdc2b2a4df545906fd3ce2c0859 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sat, 17 Dec 2022 23:39:32 -0600 Subject: [PATCH 01/10] Global Delay Option --- .../UserSettings/Settings.cs | 2 + Flow.Launcher.Plugin/PluginMetadata.cs | 52 +++++++++++++++++++ Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 09fad990bdd..080c26aef3e 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -46,6 +46,8 @@ public string Language public string TimeFormat { get; set; } = "hh:mm tt"; public string DateFormat { get; set; } = "MM'/'dd ddd"; public bool FirstLaunch { get; set; } = true; + + public int SearchDelay { get; set; } = 50; public double SettingWindowWidth { get; set; } = 1000; public double SettingWindowHeight { get; set; } = 700; diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index e8f5cf74432..5f007a38454 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -8,16 +8,46 @@ namespace Flow.Launcher.Plugin public class PluginMetadata : BaseModel { private string _pluginDirectory; + /// + /// Unique ID of the plugin + /// public string ID { get; set; } + /// + /// Name of the plugin + /// public string Name { get; set; } + /// + /// Author of the plugin + /// public string Author { get; set; } + /// + /// Plugin Version + /// public string Version { get; set; } + /// + /// Programming Language of the plugin + /// public string Language { get; set; } + /// + /// Description of the plugin + /// public string Description { get; set; } + /// + /// Website of the plugin + /// public string Website { get; set; } + /// + /// Whether the plugin is enabled + /// public bool Disabled { get; set; } + /// + /// Executable file path of the plugin + /// public string ExecuteFilePath { get; private set;} + /// + /// Executable file Name of the plugin + /// public string ExecuteFileName { get; set; } public string PluginDirectory @@ -31,17 +61,33 @@ internal set } } + /// + /// Action keyword of the plugin (Obsolete) + /// public string ActionKeyword { get; set; } + /// + /// Action keywords of the plugin + /// public List ActionKeywords { get; set; } + /// + /// Icon path of the plugin + /// public string IcoPath { get; set;} + /// + /// Metadata ToString + /// + /// Full Name of Plugin public override string ToString() { return Name; } + /// + /// Plugin Priority + /// [JsonIgnore] public int Priority { get; set; } @@ -50,8 +96,14 @@ public override string ToString() /// [JsonIgnore] public long InitTime { get; set; } + /// + /// Plugin Average Query Time (Statistics) + /// [JsonIgnore] public long AvgQueryTime { get; set; } + /// + /// Plugin Query Count (Statistics) + /// [JsonIgnore] public int QueryCount { get; set; } } diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index e05e47041d2..ce542782aac 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -675,7 +675,7 @@ private async void QueryResults() { // Wait 45 millisecond for query change in global query // if query changes, return so that it won't be calculated - await Task.Delay(45, currentCancellationToken); + await Task.Delay(Settings.SearchDelay, currentCancellationToken); if (currentCancellationToken.IsCancellationRequested) return; } From 34e2ff6b736638f53ded2cd4c760fa4906e02d4e Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sat, 17 Dec 2022 23:59:50 -0600 Subject: [PATCH 02/10] Plugin Specific Delay --- Flow.Launcher.Plugin/PluginMetadata.cs | 5 +++ Flow.Launcher/ViewModel/MainViewModel.cs | 40 +++++++----------------- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index 5f007a38454..152b44cd39f 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -45,6 +45,11 @@ public class PluginMetadata : BaseModel /// public string ExecuteFilePath { get; private set;} + /// + /// Plugin Specified Search Delay + /// + public int? SearchDelay { get; set; } = null; + /// /// Executable file Name of the plugin /// diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index ce542782aac..fd31d6de5a0 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -670,16 +670,6 @@ private async void QueryResults() SearchIconVisibility = Visibility.Visible; } - - if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) - { - // Wait 45 millisecond for query change in global query - // if query changes, return so that it won't be calculated - await Task.Delay(Settings.SearchDelay, currentCancellationToken); - if (currentCancellationToken.IsCancellationRequested) - return; - } - _ = 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 @@ -689,19 +679,16 @@ private async void QueryResults() } }, currentCancellationToken, TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default); - // plugins is ICollection, meaning LINQ will get the Count and preallocate Array - - var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch - { - false => QueryTask(plugin), - true => Task.CompletedTask - }).ToArray(); - - try { - // Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first - await Task.WhenAll(tasks); + await Parallel.ForEachAsync(plugins, currentCancellationToken, + async (pair, token) => + { + await Task.Delay(pair.Metadata.SearchDelay ?? Settings.SearchDelay, token); + if (pair.Metadata.Disabled) + return; + await QueryTask(pair, token); + }); } catch (OperationCanceledException) { @@ -721,15 +708,12 @@ private async void QueryResults() } // Local function - async Task QueryTask(PluginPair plugin) + async ValueTask QueryTask(PluginPair plugin, CancellationToken token = default) { - // Since it is wrapped within a ThreadPool Thread, the synchronous context is null - // Task.Yield will force it to run in ThreadPool - await Task.Yield(); - - IReadOnlyList results = await PluginManager.QueryForPluginAsync(plugin, query, currentCancellationToken); + IReadOnlyList results = await PluginManager.QueryForPluginAsync(plugin, query, token); - currentCancellationToken.ThrowIfCancellationRequested(); + if (token.IsCancellationRequested) + return; results ??= _emptyResult; From 8629d607c2cf4260dadfea45082773373590ed39 Mon Sep 17 00:00:00 2001 From: DB p Date: Mon, 19 Dec 2022 00:21:04 +0900 Subject: [PATCH 03/10] Add search delay item in settingwindow --- Flow.Launcher/Languages/en.xaml | 7 ++- Flow.Launcher/SettingWindow.xaml | 32 ++++++++++++++ .../ViewModel/SettingWindowViewModel.cs | 43 +++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index d0bdfee9b2c..ccef82f0a93 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -61,12 +61,17 @@ Hide Flow Launcher on startup Hide tray icon When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. + Search Delay + Sets the speed at which search results appear when typing is stopped. The lower it is, the more the search results are updated during typing. Some plug-in may have its own delay value, in which case the plug-in's delay is applied. + Fast + Normal + Slow Query Search Precision Changes minimum match score required for results. Search with Pinyin Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow starts. Press F1 to toggle preview. Shadow effect is not allowed while current theme has blur effect enabled diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index 5c3c127103b..314403e25cb 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -803,7 +803,39 @@ Padding="0" CornerRadius="5" Style="{DynamicResource SettingGroupBox}"> + + + + + + + + + +  + + + + SearchDelays + { + get + { + List delay = new List(); + + SearchDelay Fast = new SearchDelay(); + Fast.Display = _translater.GetTranslation("SearchDelayFast"); + Fast.Value = 50; + SearchDelay Normal = new SearchDelay(); + Normal.Display = _translater.GetTranslation("SearchDelayNormal"); + Normal.Value = 100; + SearchDelay Slow = new SearchDelay(); + Slow.Display = _translater.GetTranslation("SearchDelaySlow"); + Slow.Value = 150; + delay.Add(Fast); + delay.Add(Normal); + delay.Add(Slow); + + /* + foreach (var e in enums) + { + var key = $"SearchDelay{e}"; + var display = _translater.GetTranslation(key); + var m = new SearchDelay + { + Display = display, + Value = e, + }; + delay.Add(m); + } + */ + return delay; + } + } + + public string Language { get From 84a55f0fa9bbd146e438e19db8521730551c97b0 Mon Sep 17 00:00:00 2001 From: DB p Date: Mon, 19 Dec 2022 00:53:00 +0900 Subject: [PATCH 04/10] Adjust Key --- Flow.Launcher/Languages/en.xaml | 6 ++-- Flow.Launcher/Languages/ko.xaml | 14 ++++++++-- .../ViewModel/SettingWindowViewModel.cs | 28 ++++++++----------- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index ccef82f0a93..fb2fa8917ce 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -63,9 +63,9 @@ When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. Search Delay Sets the speed at which search results appear when typing is stopped. The lower it is, the more the search results are updated during typing. Some plug-in may have its own delay value, in which case the plug-in's delay is applied. - Fast - Normal - Slow + Fast + Normal + Slow Query Search Precision Changes minimum match score required for results. Search with Pinyin diff --git a/Flow.Launcher/Languages/ko.xaml b/Flow.Launcher/Languages/ko.xaml index f89a2845481..60632e6c253 100644 --- a/Flow.Launcher/Languages/ko.xaml +++ b/Flow.Launcher/Languages/ko.xaml @@ -1,5 +1,8 @@ - - + + 단축키 등록 실패: {0} {0}을 실행할 수 없습니다. @@ -59,12 +62,17 @@ 시작 시 Flow Launcher 숨김 트레이 아이콘 숨기기 트레이에서 아이콘을 숨길 경우, 검색창 우클릭으로 설정창을 열 수 있습니다. + 검색 지연 시간 + 타이핑이 멈췄을 때 검색 결과가 표시되는 속도를 지정합니다.빠를수록 타이핑 도중 결과가 갱신됩니다. 일부 플러그인은 자체적으로 설정된 지연시간을 가지고 있으며, 이 경우 플러그인에서 지정한 지연시간으로 동작합니다. + 빠름 + 보통 + 느림 쿼리 검색 정밀도 검색 결과에 필요한 최소 매치 점수를 변경합니다. 항상 Pinyin 사용 Pinyin을 사용하여 검색할 수 있습니다. Pinyin (병음) 은 로마자 중국어 입력 방식입니다. 항상 미리보기 - 항상 미리보기 패널이 열린 상태로 Flow를 시작합니다. F1키로 미리보기를 on/off 합니다. + 항상 미리보기 패널이 열린 상태로 Flow를 시작합니다. F1키로 미리보기를 on/off 합니다. 반투명 흐림 효과를 사용하는 경우, 그림자 효과를 쓸 수 없습니다. diff --git a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs index 0f9659af643..2aedc9c3ae8 100644 --- a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs +++ b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs @@ -203,35 +203,28 @@ public List SearchDelays List delay = new List(); SearchDelay Fast = new SearchDelay(); - Fast.Display = _translater.GetTranslation("SearchDelayFast"); + Fast.Display = _translater.GetTranslation("SearchDelay50"); Fast.Value = 50; SearchDelay Normal = new SearchDelay(); - Normal.Display = _translater.GetTranslation("SearchDelayNormal"); + Normal.Display = _translater.GetTranslation("SearchDelay100"); Normal.Value = 100; SearchDelay Slow = new SearchDelay(); - Slow.Display = _translater.GetTranslation("SearchDelaySlow"); + Slow.Display = _translater.GetTranslation("SearchDelay150"); Slow.Value = 150; delay.Add(Fast); delay.Add(Normal); delay.Add(Slow); - - /* - foreach (var e in enums) - { - var key = $"SearchDelay{e}"; - var display = _translater.GetTranslation(key); - var m = new SearchDelay - { - Display = display, - Value = e, - }; - delay.Add(m); - } - */ return delay; } } + private void UpdateSearchDelayDisplay() + { + foreach (var item in SearchDelays) + { + item.Display = _translater.GetTranslation($"SearchDelay{item.Value}"); + } + } public string Language { @@ -247,6 +240,7 @@ public string Language ShouldUsePinyin = true; UpdateLastQueryModeDisplay(); + UpdateSearchDelayDisplay(); } } From eafd1ca6f8c413c45310efc344a8ffa9ab853d54 Mon Sep 17 00:00:00 2001 From: DB p Date: Mon, 19 Dec 2022 11:00:17 +0900 Subject: [PATCH 05/10] Adjust desc --- Flow.Launcher/Languages/en.xaml | 2 +- Flow.Launcher/Languages/ko.xaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index fb2fa8917ce..a1c89c12626 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -62,7 +62,7 @@ Hide tray icon When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. Search Delay - Sets the speed at which search results appear when typing is stopped. The lower it is, the more the search results are updated during typing. Some plug-in may have its own delay value, in which case the plug-in's delay is applied. + Sets the speed at which search results appear when typing is stopped. Fast Normal Slow diff --git a/Flow.Launcher/Languages/ko.xaml b/Flow.Launcher/Languages/ko.xaml index 60632e6c253..2f54bad61c5 100644 --- a/Flow.Launcher/Languages/ko.xaml +++ b/Flow.Launcher/Languages/ko.xaml @@ -63,7 +63,7 @@ 트레이 아이콘 숨기기 트레이에서 아이콘을 숨길 경우, 검색창 우클릭으로 설정창을 열 수 있습니다. 검색 지연 시간 - 타이핑이 멈췄을 때 검색 결과가 표시되는 속도를 지정합니다.빠를수록 타이핑 도중 결과가 갱신됩니다. 일부 플러그인은 자체적으로 설정된 지연시간을 가지고 있으며, 이 경우 플러그인에서 지정한 지연시간으로 동작합니다. + 타이핑이 멈췄을 때 검색 결과가 표시되는 속도를 지정합니다.빠를수록 타이핑 도중 결과가 갱신됩니다. 빠름 보통 느림 From e82ca3105e448abf5888aacdbaf0efa1d6c813dc Mon Sep 17 00:00:00 2001 From: DB p Date: Mon, 19 Dec 2022 11:46:29 +0900 Subject: [PATCH 06/10] Adjust Item Texts --- Flow.Launcher/Languages/en.xaml | 5 +---- Flow.Launcher/Languages/ko.xaml | 5 +---- Flow.Launcher/SettingWindow.xaml | 3 ++- .../ViewModel/SettingWindowViewModel.cs | 16 +++------------- 4 files changed, 7 insertions(+), 22 deletions(-) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index a1c89c12626..b0e46ab6d29 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -62,10 +62,7 @@ Hide tray icon When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. Search Delay - Sets the speed at which search results appear when typing is stopped. - Fast - Normal - Slow + Sets the speed at which search results appear when typing is stopped. Default is 50ms. Query Search Precision Changes minimum match score required for results. Search with Pinyin diff --git a/Flow.Launcher/Languages/ko.xaml b/Flow.Launcher/Languages/ko.xaml index 2f54bad61c5..491a4b24b1b 100644 --- a/Flow.Launcher/Languages/ko.xaml +++ b/Flow.Launcher/Languages/ko.xaml @@ -63,10 +63,7 @@ 트레이 아이콘 숨기기 트레이에서 아이콘을 숨길 경우, 검색창 우클릭으로 설정창을 열 수 있습니다. 검색 지연 시간 - 타이핑이 멈췄을 때 검색 결과가 표시되는 속도를 지정합니다.빠를수록 타이핑 도중 결과가 갱신됩니다. - 빠름 - 보통 - 느림 + 타이핑이 멈췄을 때 검색 결과가 표시되는 속도를 지정합니다. 빠를수록 타이핑 도중 결과가 갱신됩니다. 기본값은 50ms입니다. 쿼리 검색 정밀도 검색 결과에 필요한 최소 매치 점수를 변경합니다. 항상 Pinyin 사용 diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index 314403e25cb..c5567410eae 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -820,8 +820,9 @@ SearchDelays get { List delay = new List(); - SearchDelay Fast = new SearchDelay(); - Fast.Display = _translater.GetTranslation("SearchDelay50"); + Fast.Display = "50ms"; Fast.Value = 50; SearchDelay Normal = new SearchDelay(); - Normal.Display = _translater.GetTranslation("SearchDelay100"); + Normal.Display = "100ms"; Normal.Value = 100; SearchDelay Slow = new SearchDelay(); - Slow.Display = _translater.GetTranslation("SearchDelay150"); + Slow.Display = "150ms"; Slow.Value = 150; delay.Add(Fast); delay.Add(Normal); @@ -218,14 +217,6 @@ public List SearchDelays } } - private void UpdateSearchDelayDisplay() - { - foreach (var item in SearchDelays) - { - item.Display = _translater.GetTranslation($"SearchDelay{item.Value}"); - } - } - public string Language { get @@ -240,7 +231,6 @@ public string Language ShouldUsePinyin = true; UpdateLastQueryModeDisplay(); - UpdateSearchDelayDisplay(); } } From 60f766c42f2b22ca971715702f23ac1ddb64ee58 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Mon, 16 Jan 2023 22:59:25 -0600 Subject: [PATCH 07/10] Make Global Search Delay only works for global query and check whether plugin is disabled before return it as valid plugin --- Flow.Launcher.Core/Plugin/PluginManager.cs | 2 +- .../UserSettings/Settings.cs | 2 +- Flow.Launcher/Languages/en.xaml | 2 +- Flow.Launcher/SettingWindow.xaml | 2 +- Flow.Launcher/ViewModel/MainViewModel.cs | 111 ++++++++++++++---- 5 files changed, 91 insertions(+), 28 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 3b4a6e445fc..0d2aefb229f 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -170,7 +170,7 @@ public static ICollection ValidPluginsForQuery(Query query) return Array.Empty(); if (!NonGlobalPlugins.ContainsKey(query.ActionKeyword)) - return GlobalPlugins; + return GlobalPlugins.Where(x=>!x.Metadata.Disabled).ToList(); var plugin = NonGlobalPlugins[query.ActionKeyword]; diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 2be290bb135..299debedb63 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -60,7 +60,7 @@ public string Theme public string DateFormat { get; set; } = "MM'/'dd ddd"; public bool FirstLaunch { get; set; } = true; - public int SearchDelay { get; set; } = 50; + public int GlobalSearchDelay { get; set; } = 50; public double SettingWindowWidth { get; set; } = 1000; public double SettingWindowHeight { get; set; } = 700; diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index b51cc40d237..733a6efe242 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -68,7 +68,7 @@ Hide Flow Launcher on startup Hide tray icon When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. - Search Delay + Global Search Delay Sets the speed at which search results appear when typing is stopped. Default is 50ms. Query Search Precision Changes minimum match score required for results. diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index dda69a97a8d..56e12d95ad4 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -811,7 +811,7 @@ DisplayMemberPath="Display" FontSize="14" ItemsSource="{Binding SearchDelays}" - SelectedValue="{Binding Settings.SearchDelay}" + SelectedValue="{Binding Settings.GlobalSearchDelay}" SelectedValuePath="Value" />  diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 7e24799b138..04837efc681 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -67,15 +67,19 @@ public MainViewModel(Settings settings) { case nameof(Settings.WindowSize): OnPropertyChanged(nameof(MainWindowWidth)); + break; case nameof(Settings.AlwaysStartEn): OnPropertyChanged(nameof(StartWithEnglishMode)); + break; case nameof(Settings.OpenResultModifiers): OnPropertyChanged(nameof(OpenResultCommandModifiers)); + break; case nameof(Settings.PreviewHotkey): OnPropertyChanged(nameof(PreviewHotkey)); + break; } }; @@ -92,14 +96,17 @@ public MainViewModel(Settings settings) { LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand }; + Results = new ResultsViewModel(Settings) { LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand }; + History = new ResultsViewModel(Settings) { LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand }; + _selectedResults = Results; Results.PropertyChanged += (_, args) => @@ -108,6 +115,7 @@ public MainViewModel(Settings settings) { case nameof(Results.SelectedItem): UpdatePreview(); + break; } }; @@ -133,6 +141,7 @@ async Task updateAction() while (await channelReader.WaitToReadAsync()) { await Task.Delay(20); + while (channelReader.TryRead(out var item)) { if (!item.Token.IsCancellationRequested) @@ -173,6 +182,7 @@ private void RegisterResultsUpdatedEvent() var token = e.Token == default ? _updateToken : e.Token; PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); + if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, token))) { Log.Error("MainViewModel", "Unable to add item to Result Update Queue"); @@ -237,6 +247,7 @@ private void Backspace(object index) private void AutocompleteQuery() { var result = SelectedResults.SelectedItem?.Result; + if (result != null && SelectedIsFromQueryResults()) // SelectedItem returns null if selection is empty. { var autoCompleteText = result.Title; @@ -251,6 +262,7 @@ private void AutocompleteQuery() } var specialKeyState = GlobalHotkey.CheckModifiers(); + if (specialKeyState.ShiftPressed) { autoCompleteText = result.SubTitle; @@ -264,15 +276,19 @@ private void AutocompleteQuery() private async Task OpenResultAsync(string index) { var results = SelectedResults; + if (index is not null) { results.SelectedIndex = int.Parse(index); } + var result = results.SelectedItem?.Result; + if (result == null) { return; } + var hideWindow = await result.ExecuteAsync(new ActionContext { SpecialKeyState = GlobalHotkey.CheckModifiers() @@ -361,18 +377,23 @@ public void ToggleGameMode() #region ViewModel Properties public Settings Settings { get; } + public string ClockText { get; private set; } + public string DateText { get; private set; } + public CultureInfo Culture => CultureInfo.DefaultThreadCurrentCulture; private async Task RegisterClockAndDateUpdateAsync() { var timer = new PeriodicTimer(TimeSpan.FromSeconds(1)); + // ReSharper disable once MethodSupportsCancellation while (await timer.WaitForNextTickAsync().ConfigureAwait(false)) { if (Settings.UseClock) ClockText = DateTime.Now.ToString(Settings.TimeFormat, Culture); + if (Settings.UseDate) DateText = DateTime.Now.ToString(Settings.DateFormat, Culture); } @@ -410,6 +431,7 @@ private void IncreaseWidth() Settings.WindowSize += 100; Settings.WindowLeft -= 50; } + OnPropertyChanged(); } @@ -425,6 +447,7 @@ private void DecreaseWidth() Settings.WindowLeft += 50; Settings.WindowSize -= 100; } + OnPropertyChanged(); } @@ -516,6 +539,7 @@ public void ChangeQueryText(string queryText, bool reQuery = false) { Query(); } + QueryTextCursorMovedToEnd = true; }); } @@ -533,6 +557,7 @@ private ResultsViewModel SelectedResults set { _selectedResults = value; + if (SelectedIsFromQueryResults()) { ContextMenu.Visibility = Visibility.Collapsed; @@ -564,7 +589,9 @@ private ResultsViewModel SelectedResults } public Visibility ProgressBarVisibility { get; set; } + public Visibility MainWindowVisibility { get; set; } + public double MainWindowOpacity { get; set; } = 1; // This is to be used for determining the visibility status of the mainwindow instead of MainWindowVisibility @@ -634,6 +661,7 @@ private void QueryContextMenu() r => { var match = StringMatcher.FuzzySearch(query, r.Title); + if (!match.IsSearchPrecisionScoreMet()) { match = StringMatcher.FuzzySearch(query, r.SubTitle); @@ -642,9 +670,11 @@ private void QueryContextMenu() if (!match.IsSearchPrecisionScoreMet()) return false; r.Score = match.Score; + return true; }).ToList(); + ContextMenu.AddResults(filtered, id); } else @@ -661,6 +691,7 @@ private void QueryHistory() History.Clear(); var results = new List(); + foreach (var h in _history.Items) { var title = _translator.GetTranslation("executeQuery"); @@ -678,9 +709,11 @@ private void QueryHistory() { SelectedResults = Results; ChangeQueryText(h.Query); + return false; } }; + results.Add(result); } @@ -691,6 +724,7 @@ private void QueryHistory() r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() || StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() ).ToList(); + History.AddResults(filtered, id); } else @@ -707,7 +741,9 @@ private async void QueryResults() var query = ConstructQuery(QueryText, Settings.CustomShortcuts, Settings.BuiltinShortcuts); - if (query == null) // shortcut expanded + var plugins = PluginManager.ValidPluginsForQuery(query); + + if (query == null || plugins.Count == 0) // shortcut expanded { Results.Clear(); Results.Visibility = Visibility.Collapsed; @@ -715,9 +751,18 @@ private async void QueryResults() SearchIconVisibility = Visibility.Visible; return; } - + else if (plugins.Count == 1) + { + PluginIconPath = plugins.Single().Metadata.IcoPath; + SearchIconVisibility = Visibility.Hidden; + } + else + { + PluginIconPath = null; + SearchIconVisibility = Visibility.Visible; + } + _updateSource?.Dispose(); - var currentUpdateSource = new CancellationTokenSource(); _updateSource = currentUpdateSource; var currentCancellationToken = _updateSource.Token; @@ -738,36 +783,36 @@ private async void QueryResults() _lastQuery = query; - var plugins = PluginManager.ValidPluginsForQuery(query); - - if (plugins.Count == 1) - { - PluginIconPath = plugins.Single().Metadata.IcoPath; - SearchIconVisibility = Visibility.Hidden; - } - else - { - PluginIconPath = null; - SearchIconVisibility = Visibility.Visible; - } _ = 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 (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) { - ProgressBarVisibility = Visibility.Visible; - } - }, currentCancellationToken, TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default); + // 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 (!currentCancellationToken.IsCancellationRequested && _isQueryRunning) + { + ProgressBarVisibility = Visibility.Visible; + } + }, + currentCancellationToken, + TaskContinuationOptions.NotOnCanceled, + TaskScheduler.Default); try { - await Parallel.ForEachAsync(plugins, currentCancellationToken, + await Parallel.ForEachAsync(plugins, + currentCancellationToken, async (pair, token) => { - await Task.Delay(pair.Metadata.SearchDelay ?? Settings.SearchDelay, token); - if (pair.Metadata.Disabled) + if(token.IsCancellationRequested) + return; + + await Task.Delay(pair.Metadata.SearchDelay + ?? (string.IsNullOrEmpty(query.ActionKeyword) + ? Settings.GlobalSearchDelay + : 0)); + + if (token.IsCancellationRequested) return; + await QueryTask(pair, token); }); } @@ -782,6 +827,7 @@ await Parallel.ForEachAsync(plugins, currentCancellationToken, // this should happen once after all queries are done so progress bar should continue // until the end of all querying _isQueryRunning = false; + if (!currentCancellationToken.IsCancellationRequested) { // update to hidden if this is still the current query @@ -852,6 +898,7 @@ private Query ConstructQuery(string queryText, IEnumerable _queryText = queryBuilderTmp.ToString(); var query = QueryBuilder.Build(queryBuilder.ToString().Trim(), PluginManager.NonGlobalPlugins); + return query; } @@ -866,6 +913,7 @@ private void RemoveOldQueryResults(Query query) private Result ContextMenuTopMost(Result result) { Result menu; + if (_topMostRecord.IsTopMost(result)) { menu = new Result @@ -877,6 +925,7 @@ private Result ContextMenuTopMost(Result result) { _topMostRecord.Remove(result); App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success")); + return false; } }; @@ -893,6 +942,7 @@ private Result ContextMenuTopMost(Result result) { _topMostRecord.AddOrUpdate(result); App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success")); + return false; } }; @@ -923,27 +973,32 @@ private Result ContextMenuPluginInfo(string id) Action = _ => { App.API.OpenUrl(metadata.Website); + return true; } }; + return menu; } internal bool SelectedIsFromQueryResults() { var selected = SelectedResults == Results; + return selected; } private bool ContextMenuSelected() { var selected = SelectedResults == ContextMenu; + return selected; } private bool HistorySelected() { var selected = SelectedResults == History; + return selected; } @@ -985,16 +1040,21 @@ public async void Hide() case LastQueryMode.Empty: ChangeQueryText(string.Empty); await Task.Delay(100); //Time for change to opacity + break; case LastQueryMode.Preserved: if (Settings.UseAnimation) await Task.Delay(100); + LastQuerySelected = true; + break; case LastQueryMode.Selected: if (Settings.UseAnimation) await Task.Delay(100); + LastQuerySelected = false; + break; default: throw new ArgumentException($"wrong LastQueryMode: <{Settings.LastQueryMode}>"); @@ -1030,6 +1090,7 @@ public void UpdateResultView(IEnumerable resultsForUpdates) { if (!resultsForUpdates.Any()) return; + CancellationToken token; try @@ -1080,11 +1141,13 @@ public void ResultCopy(string stringToCopy) if (string.IsNullOrEmpty(stringToCopy)) { var result = Results.SelectedItem?.Result; + if (result != null) { string copyText = result.CopyText; var isFile = File.Exists(copyText); var isFolder = Directory.Exists(copyText); + if (isFile || isFolder) { var paths = new StringCollection From 3ebf087228d32c35960e5f2d49861070003344eb Mon Sep 17 00:00:00 2001 From: Vic <10308169+VictoriousRaptor@users.noreply.github.com> Date: Tue, 17 Jan 2023 16:48:46 +0800 Subject: [PATCH 08/10] Simplify get valid plugin logic --- Flow.Launcher.Core/Plugin/PluginManager.cs | 20 +++++++++++--------- Flow.Launcher/ViewModel/MainViewModel.cs | 4 ++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 0d2aefb229f..6a12fceae50 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -164,20 +164,22 @@ public static async Task InitializePluginsAsync(IPublicAPI api) } } - public static ICollection ValidPluginsForQuery(Query query) + public static PluginPair[] ValidPluginsForQuery(Query query) { if (query is null) + { return Array.Empty(); + } - if (!NonGlobalPlugins.ContainsKey(query.ActionKeyword)) - return GlobalPlugins.Where(x=>!x.Metadata.Disabled).ToList(); - - - var plugin = NonGlobalPlugins[query.ActionKeyword]; - return new List + string actionKeyword = query.ActionKeyword; + if (NonGlobalPlugins.TryGetValue(actionKeyword, out var plugin)) { - plugin - }; + return plugin.Metadata.Disabled ? Array.Empty() : new[] { plugin }; + } + else + { + return GlobalPlugins.Where(x => !x.Metadata.Disabled).ToArray(); + } } public static async Task> QueryForPluginAsync(PluginPair pair, Query query, CancellationToken token) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 04837efc681..53cf0790bc1 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -743,7 +743,7 @@ private async void QueryResults() var plugins = PluginManager.ValidPluginsForQuery(query); - if (query == null || plugins.Count == 0) // shortcut expanded + if (query == null || plugins.Length == 0) // shortcut expanded { Results.Clear(); Results.Visibility = Visibility.Collapsed; @@ -751,7 +751,7 @@ private async void QueryResults() SearchIconVisibility = Visibility.Visible; return; } - else if (plugins.Count == 1) + else if (plugins.Length == 1) { PluginIconPath = plugins.Single().Metadata.IcoPath; SearchIconVisibility = Visibility.Hidden; From fcd0e4a80e86c2445f7b7b36f2f6b2507e7775c3 Mon Sep 17 00:00:00 2001 From: Vic <10308169+VictoriousRaptor@users.noreply.github.com> Date: Tue, 17 Jan 2023 17:10:30 +0800 Subject: [PATCH 09/10] Update wording --- Flow.Launcher/Languages/en.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 733a6efe242..158ead811a3 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -69,7 +69,7 @@ Hide tray icon When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. Global Search Delay - Sets the speed at which search results appear when typing is stopped. Default is 50ms. + Sets the search delay when you stops typing. Query Search Precision Changes minimum match score required for results. Search with Pinyin From cc7ddee2a2c9dd52e4d72968b1fa60ea708136a6 Mon Sep 17 00:00:00 2001 From: Vic <10308169+VictoriousRaptor@users.noreply.github.com> Date: Tue, 17 Jan 2023 17:31:55 +0800 Subject: [PATCH 10/10] Refactor search delay combobox --- .../ViewModel/SettingWindowViewModel.cs | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs index 040229bc7df..017f808ed4e 100644 --- a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs +++ b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs @@ -220,30 +220,23 @@ private void UpdateLastQueryModeDisplay() public class SearchDelay { - public string Display { get; set; } - public int Value { get; set; } - } - public List SearchDelays - { - get + public string Display { get; init; } + public int Value { get; init; } + + public SearchDelay(int value) { - List delay = new List(); - SearchDelay Fast = new SearchDelay(); - Fast.Display = "50ms"; - Fast.Value = 50; - SearchDelay Normal = new SearchDelay(); - Normal.Display = "100ms"; - Normal.Value = 100; - SearchDelay Slow = new SearchDelay(); - Slow.Display = "150ms"; - Slow.Value = 150; - delay.Add(Fast); - delay.Add(Normal); - delay.Add(Slow); - return delay; + Value = value; + Display = value.ToString() + " ms"; } } + public List SearchDelays { get; init; } = new() + { + new SearchDelay(50), + new SearchDelay(100), + new SearchDelay(150), + }; + public string Language { get