2024-11-21 11:18:05 +00:00
using System ;
2016-02-18 11:30:36 +00:00
using System.Collections.Generic ;
2025-03-17 01:44:02 +00:00
using System.ComponentModel ;
using System.Globalization ;
2016-06-22 23:22:41 +00:00
using System.Linq ;
2025-03-17 01:44:02 +00:00
using System.Text ;
2016-03-28 00:09:40 +00:00
using System.Threading ;
2025-03-17 01:44:02 +00:00
using System.Threading.Channels ;
2016-02-18 11:30:36 +00:00
using System.Threading.Tasks ;
using System.Windows ;
2025-03-27 05:22:23 +00:00
using System.Windows.Input ;
2025-03-17 01:44:02 +00:00
using System.Windows.Media ;
2025-03-28 08:13:02 +00:00
using System.Windows.Threading ;
2025-03-17 01:44:02 +00:00
using CommunityToolkit.Mvvm.DependencyInjection ;
using CommunityToolkit.Mvvm.Input ;
2020-04-21 09:12:17 +00:00
using Flow.Launcher.Core.Plugin ;
using Flow.Launcher.Infrastructure ;
using Flow.Launcher.Infrastructure.Hotkey ;
using Flow.Launcher.Infrastructure.Storage ;
using Flow.Launcher.Infrastructure.UserSettings ;
using Flow.Launcher.Plugin ;
2020-04-23 10:10:20 +00:00
using Flow.Launcher.Plugin.SharedCommands ;
2020-04-21 09:12:17 +00:00
using Flow.Launcher.Storage ;
2021-06-11 04:40:07 +00:00
using Microsoft.VisualStudio.Threading ;
2021-10-14 04:34:46 +00:00
2020-04-21 09:12:17 +00:00
namespace Flow.Launcher.ViewModel
2016-02-18 11:30:36 +00:00
{
2025-03-21 12:55:57 +00:00
public partial class MainViewModel : BaseModel , ISavable , IDisposable
2016-02-18 11:30:36 +00:00
{
#region Private Fields
2025-04-13 09:50:44 +00:00
private static readonly string ClassName = nameof ( MainViewModel ) ;
2019-12-13 22:17:05 +00:00
private bool _isQueryRunning ;
2016-03-26 01:20:42 +00:00
private Query _lastQuery ;
2025-05-10 12:56:48 +00:00
private bool _previousIsHomeQuery ;
2016-06-23 21:17:47 +00:00
private string _queryTextBeforeLeaveResults ;
2025-05-06 05:58:28 +00:00
private string _ignoredQueryText ; // Used to ignore query text change when switching between context menu and query results
2016-02-18 11:30:36 +00:00
2020-04-21 12:16:10 +00:00
private readonly FlowLauncherJsonStorage < History > _historyItemsStorage ;
private readonly FlowLauncherJsonStorage < UserSelectedRecord > _userSelectedRecordStorage ;
private readonly FlowLauncherJsonStorage < TopMostRecord > _topMostRecordStorage ;
2016-06-23 21:17:47 +00:00
private readonly History _history ;
2024-05-19 15:16:56 +00:00
private int lastHistoryIndex = 1 ;
2016-04-21 00:53:21 +00:00
private readonly UserSelectedRecord _userSelectedRecord ;
private readonly TopMostRecord _topMostRecord ;
2016-03-28 00:09:40 +00:00
2025-05-02 04:26:14 +00:00
private CancellationTokenSource _updateSource ; // Used to cancel old query flows
2025-05-06 06:02:18 +00:00
private CancellationToken _updateToken ; // Used to avoid ObjectDisposedException of _updateSource.Token
2016-04-25 01:33:55 +00:00
2021-02-15 18:12:28 +00:00
private ChannelWriter < ResultsForUpdate > _resultsUpdateChannelWriter ;
2020-12-04 13:53:34 +00:00
private Task _resultsViewUpdateTask ;
2020-11-10 09:13:45 +00:00
2025-03-26 02:30:47 +00:00
private readonly IReadOnlyList < Result > _emptyResult = new List < Result > ( ) ;
2025-05-03 14:09:25 +00:00
private readonly PluginMetadata _historyMetadata = new ( )
{
2025-05-05 00:44:08 +00:00
ID = "298303A65D128A845D28A7B83B3968C2" , // ID is for identifying the update plugin in UpdateActionAsync
2025-05-05 00:35:20 +00:00
Priority = 0 // Priority is for calculating scores in UpdateResultView
2025-05-03 14:09:25 +00:00
} ;
2016-02-18 11:30:36 +00:00
#endregion
#region Constructor
2025-01-12 11:45:36 +00:00
public MainViewModel ( )
2016-02-18 11:30:36 +00:00
{
2016-06-23 21:17:47 +00:00
_queryTextBeforeLeaveResults = "" ;
2016-03-26 01:20:42 +00:00
_queryText = "" ;
_lastQuery = new Query ( ) ;
2025-05-06 05:58:28 +00:00
_ignoredQueryText = null ; // null as invalid value
2016-04-21 00:53:21 +00:00
2025-01-12 11:45:36 +00:00
Settings = Ioc . Default . GetRequiredService < Settings > ( ) ;
2022-10-12 04:01:50 +00:00
Settings . PropertyChanged + = ( _ , args ) = >
2021-10-22 04:31:51 +00:00
{
2022-12-11 17:00:48 +00:00
switch ( args . PropertyName )
2021-10-22 04:31:51 +00:00
{
2022-11-24 07:29:05 +00:00
case nameof ( Settings . WindowSize ) :
OnPropertyChanged ( nameof ( MainWindowWidth ) ) ;
break ;
2024-05-14 07:54:15 +00:00
case nameof ( Settings . WindowHeightSize ) :
OnPropertyChanged ( nameof ( MainWindowHeight ) ) ;
break ;
case nameof ( Settings . QueryBoxFontSize ) :
OnPropertyChanged ( nameof ( QueryBoxFontSize ) ) ;
break ;
case nameof ( Settings . ItemHeightSize ) :
OnPropertyChanged ( nameof ( ItemHeightSize ) ) ;
break ;
case nameof ( Settings . ResultItemFontSize ) :
OnPropertyChanged ( nameof ( ResultItemFontSize ) ) ;
break ;
case nameof ( Settings . ResultSubItemFontSize ) :
OnPropertyChanged ( nameof ( ResultSubItemFontSize ) ) ;
break ;
2022-12-20 14:35:07 +00:00
case nameof ( Settings . AlwaysStartEn ) :
OnPropertyChanged ( nameof ( StartWithEnglishMode ) ) ;
break ;
2022-12-22 17:08:06 +00:00
case nameof ( Settings . OpenResultModifiers ) :
OnPropertyChanged ( nameof ( OpenResultCommandModifiers ) ) ;
break ;
2022-12-22 16:58:26 +00:00
case nameof ( Settings . PreviewHotkey ) :
2022-12-27 17:09:32 +00:00
OnPropertyChanged ( nameof ( PreviewHotkey ) ) ;
2022-12-22 16:58:26 +00:00
break ;
2024-04-13 09:47:37 +00:00
case nameof ( Settings . AutoCompleteHotkey ) :
OnPropertyChanged ( nameof ( AutoCompleteHotkey ) ) ;
break ;
2024-05-15 16:11:27 +00:00
case nameof ( Settings . CycleHistoryUpHotkey ) :
OnPropertyChanged ( nameof ( CycleHistoryUpHotkey ) ) ;
break ;
case nameof ( Settings . CycleHistoryDownHotkey ) :
OnPropertyChanged ( nameof ( CycleHistoryDownHotkey ) ) ;
break ;
2024-04-18 12:01:01 +00:00
case nameof ( Settings . AutoCompleteHotkey2 ) :
OnPropertyChanged ( nameof ( AutoCompleteHotkey2 ) ) ;
break ;
2024-04-15 12:52:15 +00:00
case nameof ( Settings . SelectNextItemHotkey ) :
OnPropertyChanged ( nameof ( SelectNextItemHotkey ) ) ;
break ;
2024-04-18 12:01:01 +00:00
case nameof ( Settings . SelectNextItemHotkey2 ) :
OnPropertyChanged ( nameof ( SelectNextItemHotkey2 ) ) ;
break ;
2024-04-15 12:52:15 +00:00
case nameof ( Settings . SelectPrevItemHotkey ) :
OnPropertyChanged ( nameof ( SelectPrevItemHotkey ) ) ;
break ;
2024-04-18 12:01:01 +00:00
case nameof ( Settings . SelectPrevItemHotkey2 ) :
OnPropertyChanged ( nameof ( SelectPrevItemHotkey2 ) ) ;
break ;
case nameof ( Settings . SelectNextPageHotkey ) :
OnPropertyChanged ( nameof ( SelectNextPageHotkey ) ) ;
break ;
case nameof ( Settings . SelectPrevPageHotkey ) :
OnPropertyChanged ( nameof ( SelectPrevPageHotkey ) ) ;
break ;
2024-04-18 07:35:05 +00:00
case nameof ( Settings . OpenContextMenuHotkey ) :
OnPropertyChanged ( nameof ( OpenContextMenuHotkey ) ) ;
break ;
2024-04-18 07:01:26 +00:00
case nameof ( Settings . SettingWindowHotkey ) :
OnPropertyChanged ( nameof ( SettingWindowHotkey ) ) ;
break ;
2021-10-22 04:31:51 +00:00
}
} ;
2016-04-21 00:53:21 +00:00
2020-04-21 12:16:10 +00:00
_historyItemsStorage = new FlowLauncherJsonStorage < History > ( ) ;
_userSelectedRecordStorage = new FlowLauncherJsonStorage < UserSelectedRecord > ( ) ;
_topMostRecordStorage = new FlowLauncherJsonStorage < TopMostRecord > ( ) ;
2016-06-23 21:17:47 +00:00
_history = _historyItemsStorage . Load ( ) ;
2016-04-21 00:53:21 +00:00
_userSelectedRecord = _userSelectedRecordStorage . Load ( ) ;
_topMostRecord = _topMostRecordStorage . Load ( ) ;
2016-03-26 01:20:42 +00:00
2022-11-19 05:15:02 +00:00
ContextMenu = new ResultsViewModel ( Settings )
{
2024-05-30 14:49:36 +00:00
LeftClickResultCommand = OpenResultCommand ,
RightClickResultCommand = LoadContextMenuCommand ,
IsPreviewOn = Settings . AlwaysPreview
2022-11-19 05:15:02 +00:00
} ;
Results = new ResultsViewModel ( Settings )
{
2024-05-30 14:49:36 +00:00
LeftClickResultCommand = OpenResultCommand ,
RightClickResultCommand = LoadContextMenuCommand ,
IsPreviewOn = Settings . AlwaysPreview
2022-11-19 05:15:02 +00:00
} ;
History = new ResultsViewModel ( Settings )
{
2024-05-30 14:49:36 +00:00
LeftClickResultCommand = OpenResultCommand ,
RightClickResultCommand = LoadContextMenuCommand ,
IsPreviewOn = Settings . AlwaysPreview
2022-11-19 05:15:02 +00:00
} ;
2016-06-22 23:26:57 +00:00
_selectedResults = Results ;
2025-03-30 10:03:43 +00:00
Results . PropertyChanged + = ( o , args ) = >
2022-12-22 16:32:29 +00:00
{
switch ( args . PropertyName )
{
case nameof ( Results . SelectedItem ) :
2025-03-22 15:33:04 +00:00
_selectedItemFromQueryResults = true ;
PreviewSelectedItem = Results . SelectedItem ;
2025-03-26 02:07:35 +00:00
_ = UpdatePreviewAsync ( ) ;
2025-03-22 15:33:04 +00:00
break ;
}
} ;
2025-03-30 10:03:43 +00:00
History . PropertyChanged + = ( o , args ) = >
2025-03-22 15:33:04 +00:00
{
switch ( args . PropertyName )
{
case nameof ( History . SelectedItem ) :
_selectedItemFromQueryResults = false ;
PreviewSelectedItem = History . SelectedItem ;
2025-03-26 02:07:35 +00:00
_ = UpdatePreviewAsync ( ) ;
2022-12-22 16:32:29 +00:00
break ;
}
} ;
2022-09-19 20:32:17 +00:00
2021-01-09 04:17:02 +00:00
RegisterViewUpdate ( ) ;
2022-12-23 03:47:06 +00:00
_ = RegisterClockAndDateUpdateAsync ( ) ;
2016-03-28 02:09:57 +00:00
}
2020-12-04 13:53:34 +00:00
private void RegisterViewUpdate ( )
2020-11-10 09:13:45 +00:00
{
2021-02-15 18:12:28 +00:00
var resultUpdateChannel = Channel . CreateUnbounded < ResultsForUpdate > ( ) ;
_resultsUpdateChannelWriter = resultUpdateChannel . Writer ;
2021-01-09 04:17:02 +00:00
_resultsViewUpdateTask =
2025-03-17 02:07:40 +00:00
Task . Run ( UpdateActionAsync ) . ContinueWith ( continueAction , CancellationToken . None , TaskContinuationOptions . OnlyOnFaulted , TaskScheduler . Default ) ;
2020-11-10 09:13:45 +00:00
2025-03-17 02:07:40 +00:00
async Task UpdateActionAsync ( )
2020-11-10 09:13:45 +00:00
{
2021-01-14 11:54:37 +00:00
var queue = new Dictionary < string , ResultsForUpdate > ( ) ;
2021-02-15 18:12:28 +00:00
var channelReader = resultUpdateChannel . Reader ;
2021-02-15 09:58:15 +00:00
// it is not supposed to be false because it won't be complete
2021-02-15 18:12:28 +00:00
while ( await channelReader . WaitToReadAsync ( ) )
2020-11-10 09:13:45 +00:00
{
2020-11-21 04:01:57 +00:00
await Task . Delay ( 20 ) ;
2021-02-15 18:12:28 +00:00
while ( channelReader . TryRead ( out var item ) )
2021-01-14 11:54:37 +00:00
{
if ( ! item . Token . IsCancellationRequested )
queue [ item . ID ] = item ;
}
UpdateResultView ( queue . Values ) ;
2021-02-15 10:19:30 +00:00
queue . Clear ( ) ;
2020-11-10 09:13:45 +00:00
}
2021-02-15 09:58:15 +00:00
2025-03-28 06:45:52 +00:00
if ( ! _disposed )
2025-04-13 09:50:44 +00:00
App . API . LogError ( ClassName , "Unexpected ResultViewUpdate ends" ) ;
2021-10-20 03:10:37 +00:00
}
2020-12-04 13:53:34 +00:00
void continueAction ( Task t )
{
#if DEBUG
throw t . Exception ;
#else
2025-04-13 10:13:55 +00:00
App . API . LogError ( ClassName , $"Error happen in task dealing with viewupdate for results. {t.Exception}" ) ;
2021-06-11 04:40:07 +00:00
_resultsViewUpdateTask =
2025-03-17 02:25:02 +00:00
Task . Run ( UpdateActionAsync ) . ContinueWith ( continueAction , CancellationToken . None , TaskContinuationOptions . OnlyOnFaulted , TaskScheduler . Default ) ;
2020-12-04 13:53:34 +00:00
#endif
}
2020-11-10 09:13:45 +00:00
}
2025-03-21 06:46:39 +00:00
public void RegisterResultsUpdatedEvent ( )
2016-02-18 11:30:36 +00:00
{
2016-05-05 15:08:44 +00:00
foreach ( var pair in PluginManager . GetPluginsForInterface < IResultUpdated > ( ) )
2016-02-18 11:30:36 +00:00
{
2021-01-12 23:35:48 +00:00
var plugin = ( IResultUpdated ) pair . Plugin ;
2016-05-05 15:08:44 +00:00
plugin . ResultsUpdated + = ( s , e ) = >
2016-03-26 01:20:42 +00:00
{
2021-07-07 17:38:57 +00:00
if ( e . Query . RawQuery ! = QueryText | | e . Token . IsCancellationRequested )
2016-05-05 20:15:13 +00:00
{
2021-07-07 17:38:57 +00:00
return ;
}
2025-05-06 06:02:18 +00:00
var token = e . Token = = default ? _updateToken : e . Token ;
2021-07-07 17:38:57 +00:00
2025-02-20 10:06:15 +00:00
// make a clone to avoid possible issue that plugin will also change the list and items when updating view model
var resultsCopy = DeepCloneResults ( e . Results , token ) ;
2024-12-08 04:02:04 +00:00
2025-04-11 07:49:39 +00:00
foreach ( var result in resultsCopy )
{
if ( string . IsNullOrEmpty ( result . BadgeIcoPath ) )
{
result . BadgeIcoPath = pair . Metadata . IcoPath ;
}
}
2024-12-08 04:02:04 +00:00
PluginManager . UpdatePluginMetadata ( resultsCopy , pair . Metadata , e . Query ) ;
2025-05-02 04:26:14 +00:00
if ( token . IsCancellationRequested ) return ;
if ( ! _resultsUpdateChannelWriter . TryWrite ( new ResultsForUpdate ( resultsCopy , pair . Metadata , e . Query ,
token ) ) )
2021-07-07 17:38:57 +00:00
{
2025-04-13 09:50:44 +00:00
App . API . LogError ( ClassName , "Unable to add item to Result Update Queue" ) ;
2021-01-12 23:35:48 +00:00
}
2016-05-05 15:08:44 +00:00
} ;
2016-02-18 11:30:36 +00:00
}
}
2021-11-27 02:38:47 +00:00
2025-03-19 10:22:43 +00:00
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 , CultureInfo . CurrentCulture ) ;
if ( Settings . UseDate )
DateText = DateTime . Now . ToString ( Settings . DateFormat , CultureInfo . CurrentCulture ) ;
}
}
2022-11-19 18:08:32 +00:00
[RelayCommand]
private async Task ReloadPluginDataAsync ( )
2016-02-18 11:30:36 +00:00
{
2022-11-19 18:08:32 +00:00
Hide ( ) ;
2021-10-14 00:00:58 +00:00
2022-11-19 18:08:32 +00:00
await PluginManager . ReloadDataAsync ( ) . ConfigureAwait ( false ) ;
2025-04-09 13:16:33 +00:00
App . API . ShowMsg ( App . API . GetTranslation ( "success" ) ,
2025-03-17 02:07:40 +00:00
App . API . GetTranslation ( "completedSuccessfully" ) ) ;
2022-11-19 18:08:32 +00:00
}
2021-10-14 00:00:58 +00:00
2022-11-19 18:08:32 +00:00
[RelayCommand]
private void LoadHistory ( )
2016-02-18 11:30:36 +00:00
{
2025-03-26 02:07:35 +00:00
if ( QueryResultsSelected ( ) )
2016-02-12 09:20:46 +00:00
{
2022-11-19 18:08:32 +00:00
SelectedResults = History ;
History . SelectedIndex = _history . Items . Count - 1 ;
}
else
2021-10-14 00:00:58 +00:00
{
2022-11-19 18:08:32 +00:00
SelectedResults = Results ;
}
}
2016-02-12 06:21:12 +00:00
2023-06-06 10:06:39 +00:00
[RelayCommand]
2024-02-23 21:25:29 +00:00
public void ReQuery ( )
2023-06-06 10:06:39 +00:00
{
2025-03-26 02:07:35 +00:00
if ( QueryResultsSelected ( ) )
2023-06-06 10:06:39 +00:00
{
2025-03-31 05:04:41 +00:00
// When we are re-querying, we should not delay the query
2025-03-21 05:34:52 +00:00
_ = QueryResultsAsync ( false , isReQuery : true ) ;
2023-06-06 10:06:39 +00:00
}
}
2024-02-25 02:05:54 +00:00
public void ReQuery ( bool reselect )
{
2025-02-22 11:32:08 +00:00
BackToQueryResults ( ) ;
2025-03-31 05:04:41 +00:00
// When we are re-querying, we should not delay the query
2025-03-21 05:34:52 +00:00
_ = QueryResultsAsync ( false , isReQuery : true , reSelect : reselect ) ;
2024-02-25 02:05:54 +00:00
}
2024-05-15 16:11:27 +00:00
[RelayCommand]
public void ReverseHistory ( )
{
if ( _history . Items . Count > 0 )
{
2025-03-30 10:03:43 +00:00
ChangeQueryText ( _history . Items [ ^ lastHistoryIndex ] . Query ) ;
2024-05-19 15:16:56 +00:00
if ( lastHistoryIndex < _history . Items . Count )
2024-05-15 16:11:27 +00:00
{
2024-05-19 15:16:56 +00:00
lastHistoryIndex + + ;
2024-05-15 16:11:27 +00:00
}
}
}
[RelayCommand]
public void ForwardHistory ( )
{
if ( _history . Items . Count > 0 )
{
2025-03-30 10:03:43 +00:00
ChangeQueryText ( _history . Items [ ^ lastHistoryIndex ] . Query ) ;
2024-05-19 15:16:56 +00:00
if ( lastHistoryIndex > 1 )
2024-05-15 16:11:27 +00:00
{
2024-05-19 15:16:56 +00:00
lastHistoryIndex - - ;
2024-05-15 16:11:27 +00:00
}
}
}
2022-11-19 18:08:32 +00:00
[RelayCommand]
private void LoadContextMenu ( )
{
2025-03-26 02:07:35 +00:00
if ( QueryResultsSelected ( ) )
2022-11-19 18:08:32 +00:00
{
// When switch to ContextMenu from QueryResults, but no item being chosen, should do nothing
// i.e. Shift+Enter/Ctrl+O right after Alt + Space should do nothing
if ( SelectedResults . SelectedItem ! = null )
SelectedResults = ContextMenu ;
}
else
{
SelectedResults = Results ;
}
}
2016-02-12 06:21:12 +00:00
2022-11-19 18:08:32 +00:00
[RelayCommand]
private void Backspace ( object index )
{
var query = QueryBuilder . Build ( QueryText . Trim ( ) , PluginManager . NonGlobalPlugins ) ;
2016-02-12 06:21:12 +00:00
2022-11-19 18:08:32 +00:00
// GetPreviousExistingDirectory does not require trailing '\', otherwise will return empty string
var path = FilesFolders . GetPreviousExistingDirectory ( ( _ ) = > true , query . Search . TrimEnd ( '\\' ) ) ;
2016-02-12 06:21:12 +00:00
2022-11-19 18:08:32 +00:00
var actionKeyword = string . IsNullOrEmpty ( query . ActionKeyword ) ? string . Empty : $"{query.ActionKeyword} " ;
2016-02-12 06:21:12 +00:00
2022-11-19 18:08:32 +00:00
ChangeQueryText ( $"{actionKeyword}{path}" ) ;
}
2019-12-07 14:57:21 +00:00
2022-11-19 18:08:32 +00:00
[RelayCommand]
private void AutocompleteQuery ( )
{
var result = SelectedResults . SelectedItem ? . Result ;
2025-03-26 02:07:35 +00:00
if ( result ! = null & & QueryResultsSelected ( ) ) // SelectedItem returns null if selection is empty.
2016-02-12 09:20:46 +00:00
{
2022-11-19 18:08:32 +00:00
var autoCompleteText = result . Title ;
2016-03-26 01:20:42 +00:00
2022-11-19 18:08:32 +00:00
if ( ! string . IsNullOrEmpty ( result . AutoCompleteText ) )
2016-03-26 01:20:42 +00:00
{
2022-11-19 18:08:32 +00:00
autoCompleteText = result . AutoCompleteText ;
2016-03-26 01:20:42 +00:00
}
2022-11-19 18:08:32 +00:00
else if ( ! string . IsNullOrEmpty ( SelectedResults . SelectedItem ? . QuerySuggestionText ) )
2016-02-12 06:21:12 +00:00
{
2025-03-19 10:22:43 +00:00
//var defaultSuggestion = SelectedResults.SelectedItem.QuerySuggestionText;
//// check if result.actionkeywordassigned is empty
//if (!string.IsNullOrEmpty(result.ActionKeywordAssigned))
//{
// autoCompleteText = $"{result.ActionKeywordAssigned} {defaultSuggestion}";
//}
2024-03-20 05:47:14 +00:00
2022-11-19 18:08:32 +00:00
autoCompleteText = SelectedResults . SelectedItem . QuerySuggestionText ;
2022-04-12 09:30:04 +00:00
}
2016-03-26 01:20:42 +00:00
2022-11-19 18:08:32 +00:00
var specialKeyState = GlobalHotkey . CheckModifiers ( ) ;
if ( specialKeyState . ShiftPressed )
2022-04-12 09:30:04 +00:00
{
2022-11-19 18:08:32 +00:00
autoCompleteText = result . SubTitle ;
2016-04-25 20:25:39 +00:00
}
2016-02-12 06:21:12 +00:00
2022-11-19 18:08:32 +00:00
ChangeQueryText ( autoCompleteText ) ;
}
}
2022-12-23 03:46:12 +00:00
2022-11-19 18:08:32 +00:00
[RelayCommand]
2022-11-22 17:24:35 +00:00
private async Task OpenResultAsync ( string index )
2022-11-19 18:08:32 +00:00
{
var results = SelectedResults ;
2022-11-22 17:24:35 +00:00
if ( index is not null )
{
results . SelectedIndex = int . Parse ( index ) ;
}
2024-03-20 05:47:14 +00:00
2022-11-19 18:08:32 +00:00
var result = results . SelectedItem ? . Result ;
if ( result = = null )
2021-12-03 15:31:59 +00:00
{
2022-11-19 18:08:32 +00:00
return ;
}
2024-03-20 05:47:14 +00:00
2022-11-19 18:08:32 +00:00
var hideWindow = await result . ExecuteAsync ( new ActionContext
2025-03-17 02:07:40 +00:00
{
// not null means pressing modifier key + number, should ignore the modifier key
SpecialKeyState = index is not null ? SpecialKeyState . Default : GlobalHotkey . CheckModifiers ( )
} )
. ConfigureAwait ( false ) ;
2021-12-05 08:15:32 +00:00
2025-03-26 02:07:35 +00:00
if ( QueryResultsSelected ( ) )
2022-11-19 18:08:32 +00:00
{
_userSelectedRecord . Add ( result ) ;
2025-05-11 08:53:44 +00:00
_history . Add ( result . OriginQuery . RawQuery ) ;
2024-05-19 15:17:57 +00:00
lastHistoryIndex = 1 ;
2022-11-19 18:08:32 +00:00
}
2023-04-03 06:09:36 +00:00
2023-04-03 05:15:08 +00:00
if ( hideWindow )
{
Hide ( ) ;
}
2022-11-19 18:08:32 +00:00
}
2021-12-05 08:15:32 +00:00
2025-02-20 10:06:15 +00:00
private static IReadOnlyList < Result > DeepCloneResults ( IReadOnlyList < Result > results , CancellationToken token = default )
{
var resultsCopy = new List < Result > ( ) ;
foreach ( var result in results . ToList ( ) )
{
if ( token . IsCancellationRequested )
{
break ;
}
var resultCopy = result . Clone ( ) ;
resultsCopy . Add ( resultCopy ) ;
}
return resultsCopy ;
}
2023-04-23 11:09:58 +00:00
#endregion
2023-04-22 06:12:12 +00:00
#region BasicCommands
2022-11-19 18:08:32 +00:00
[RelayCommand]
private void OpenSetting ( )
{
App . API . OpenSettingDialog ( ) ;
}
2021-12-03 15:31:59 +00:00
2022-11-19 18:08:32 +00:00
[RelayCommand]
private void SelectHelp ( )
{
2025-02-28 09:03:53 +00:00
App . API . OpenUrl ( "https://www.flowlauncher.com/docs/#/usage-tips" ) ;
2022-11-19 18:08:32 +00:00
}
2022-01-19 20:43:00 +00:00
2022-11-19 18:08:32 +00:00
[RelayCommand]
private void SelectFirstResult ( )
{
SelectedResults . SelectFirstResult ( ) ;
}
2022-01-20 09:04:48 +00:00
2024-04-15 17:21:29 +00:00
[RelayCommand]
private void SelectLastResult ( )
{
SelectedResults . SelectLastResult ( ) ;
}
2022-11-19 18:08:32 +00:00
[RelayCommand]
private void SelectPrevPage ( )
{
SelectedResults . SelectPrevPage ( ) ;
}
2022-01-20 09:04:48 +00:00
2022-11-19 18:08:32 +00:00
[RelayCommand]
private void SelectNextPage ( )
{
SelectedResults . SelectNextPage ( ) ;
}
2021-12-03 16:59:40 +00:00
2022-11-19 18:08:32 +00:00
[RelayCommand]
private void SelectPrevItem ( )
{
2025-05-11 06:32:59 +00:00
if ( QueryResultsSelected ( ) // Results selected
& & string . IsNullOrEmpty ( QueryText ) // No input
2025-05-11 08:16:29 +00:00
& & Results . Visibility ! = Visibility . Visible // No items in result list, e.g. when home page is off and no query text is entered, therefore the view is collapsed.
2025-05-11 06:32:59 +00:00
& & _history . Items . Count > 0 ) // Have history items
2024-05-15 16:11:27 +00:00
{
2024-05-19 15:16:56 +00:00
lastHistoryIndex = 1 ;
2024-05-15 16:11:27 +00:00
ReverseHistory ( ) ;
}
else
{
SelectedResults . SelectPrevResult ( ) ;
}
2022-11-19 18:08:32 +00:00
}
2016-02-12 09:20:46 +00:00
2022-11-19 18:08:32 +00:00
[RelayCommand]
private void SelectNextItem ( )
{
SelectedResults . SelectNextResult ( ) ;
}
2021-02-12 03:45:31 +00:00
2022-11-19 18:08:32 +00:00
[RelayCommand]
private void Esc ( )
{
2025-03-26 02:07:35 +00:00
if ( ! QueryResultsSelected ( ) )
2016-06-23 21:17:47 +00:00
{
2022-11-19 18:08:32 +00:00
SelectedResults = Results ;
}
else
2021-02-12 03:45:31 +00:00
{
2021-10-14 00:00:58 +00:00
Hide ( ) ;
2022-11-19 18:08:32 +00:00
}
2016-02-18 11:30:36 +00:00
}
2021-11-05 17:31:05 +00:00
2024-11-21 11:18:05 +00:00
public void BackToQueryResults ( )
{
2025-03-26 02:07:35 +00:00
if ( ! QueryResultsSelected ( ) )
2024-11-21 11:18:05 +00:00
{
SelectedResults = Results ;
}
}
2022-12-23 06:14:53 +00:00
[RelayCommand]
public void ToggleGameMode ( )
{
GameModeStatus = ! GameModeStatus ;
2016-02-18 11:30:36 +00:00
}
2024-05-27 07:09:22 +00:00
2024-03-18 10:11:25 +00:00
[RelayCommand]
public void CopyAlternative ( )
{
var result = Results . SelectedItem ? . Result ? . CopyText ;
if ( result ! = null )
{
App . API . CopyToClipboard ( result , directCopy : false ) ;
}
}
2016-02-18 11:30:36 +00:00
2016-05-05 15:08:44 +00:00
#endregion
#region ViewModel Properties
2022-10-12 04:01:50 +00:00
public Settings Settings { get ; }
2022-11-19 09:54:34 +00:00
public string ClockText { get ; private set ; }
2022-10-12 04:01:50 +00:00
public string DateText { get ; private set ; }
2016-05-05 15:08:44 +00:00
public ResultsViewModel Results { get ; private set ; }
2022-03-01 19:29:21 +00:00
2016-05-05 15:08:44 +00:00
public ResultsViewModel ContextMenu { get ; private set ; }
2022-03-01 19:29:21 +00:00
2016-06-23 21:17:47 +00:00
public ResultsViewModel History { get ; private set ; }
2016-05-05 15:08:44 +00:00
2022-12-23 06:14:53 +00:00
public bool GameModeStatus { get ; set ; } = false ;
2021-11-19 06:43:47 +00:00
2016-06-23 21:17:47 +00:00
private string _queryText ;
2016-05-05 15:08:44 +00:00
public string QueryText
{
2021-01-17 10:05:31 +00:00
get = > _queryText ;
2016-05-05 15:08:44 +00:00
set
{
_queryText = value ;
2022-12-11 17:00:48 +00:00
OnPropertyChanged ( ) ;
2016-05-05 15:08:44 +00:00
}
}
2020-05-04 22:41:24 +00:00
2022-09-19 20:32:17 +00:00
[RelayCommand]
private void IncreaseWidth ( )
{
2025-03-29 08:03:41 +00:00
MainWindowWidth + = 100 ;
2024-06-30 09:03:25 +00:00
Settings . WindowLeft - = 50 ;
2024-05-28 08:46:28 +00:00
OnPropertyChanged ( nameof ( MainWindowWidth ) ) ;
2022-09-19 20:32:17 +00:00
}
[RelayCommand]
private void DecreaseWidth ( )
{
2025-03-29 08:03:41 +00:00
if ( MainWindowWidth - 100 < 400 | | MainWindowWidth = = 400 )
2022-10-13 04:57:42 +00:00
{
2025-03-29 08:03:41 +00:00
MainWindowWidth = 400 ;
2022-10-13 04:57:42 +00:00
}
else
2022-10-25 12:45:06 +00:00
{
2025-03-29 08:03:41 +00:00
MainWindowWidth - = 100 ;
2024-06-30 09:03:25 +00:00
Settings . WindowLeft + = 50 ;
2022-10-13 04:57:42 +00:00
}
2024-03-20 05:47:14 +00:00
2024-05-28 08:46:28 +00:00
OnPropertyChanged ( nameof ( MainWindowWidth ) ) ;
2022-09-19 20:32:17 +00:00
}
[RelayCommand]
private void IncreaseMaxResult ( )
{
2022-10-25 12:45:06 +00:00
if ( Settings . MaxResultsToShow = = 17 )
2022-10-14 09:46:53 +00:00
return ;
2022-10-25 12:45:06 +00:00
Settings . MaxResultsToShow + = 1 ;
2022-09-19 20:32:17 +00:00
}
[RelayCommand]
private void DecreaseMaxResult ( )
{
2022-10-25 12:45:06 +00:00
if ( Settings . MaxResultsToShow = = 2 )
2022-10-14 09:46:53 +00:00
return ;
2022-10-25 12:45:06 +00:00
Settings . MaxResultsToShow - = 1 ;
2022-09-19 20:32:17 +00:00
}
2016-07-20 22:37:06 +00:00
/// <summary>
/// we need move cursor to end when we manually changed query
/// but we don't want to move cursor to end when query is updated from TextBox
/// </summary>
/// <param name="queryText"></param>
2023-06-11 13:19:41 +00:00
/// <param name="isReQuery">Force query even when Query Text doesn't change</param>
public void ChangeQueryText ( string queryText , bool isReQuery = false )
2016-07-20 22:37:06 +00:00
{
2025-05-02 04:26:14 +00:00
// Must check access so that we will not block the UI thread which causes window visibility issue
if ( ! Application . Current . Dispatcher . CheckAccess ( ) )
{
Application . Current . Dispatcher . Invoke ( ( ) = > ChangeQueryText ( queryText , isReQuery ) ) ;
return ;
}
if ( QueryText ! = queryText )
{
// Change query text first
QueryText = queryText ;
// When we are changing query from codes, we should not delay the query
Query ( false , isReQuery : false ) ;
// set to false so the subsequent set true triggers
// PropertyChanged and MoveQueryTextToEnd is called
QueryTextCursorMovedToEnd = false ;
}
else if ( isReQuery )
{
// When we are re-querying, we should not delay the query
Query ( false , isReQuery : true ) ;
}
QueryTextCursorMovedToEnd = true ;
2025-03-27 05:22:23 +00:00
}
/// <summary>
/// Async version of <see cref="ChangeQueryText"/>
/// </summary>
private async Task ChangeQueryTextAsync ( string queryText , bool isReQuery = false )
{
2025-05-02 04:26:14 +00:00
// Must check access so that we will not block the UI thread which causes window visibility issue
2025-03-28 03:56:44 +00:00
if ( ! Application . Current . Dispatcher . CheckAccess ( ) )
2021-09-25 01:05:55 +00:00
{
2025-05-02 04:26:14 +00:00
await Application . Current . Dispatcher . InvokeAsync ( ( ) = > ChangeQueryTextAsync ( queryText , isReQuery ) ) ;
2025-03-28 03:56:44 +00:00
return ;
}
2025-03-28 04:00:23 +00:00
if ( QueryText ! = queryText )
{
2025-03-30 04:39:18 +00:00
// Change query text first
2025-03-28 04:00:23 +00:00
QueryText = queryText ;
2025-03-30 04:32:32 +00:00
// When we are changing query from codes, we should not delay the query
await QueryAsync ( false , isReQuery : false ) ;
2025-03-30 04:39:18 +00:00
2025-03-28 04:00:23 +00:00
// set to false so the subsequent set true triggers
// PropertyChanged and MoveQueryTextToEnd is called
QueryTextCursorMovedToEnd = false ;
}
else if ( isReQuery )
{
2025-03-31 05:04:41 +00:00
// When we are re-querying, we should not delay the query
2025-03-30 04:32:32 +00:00
await QueryAsync ( false , isReQuery : true ) ;
2025-03-28 04:00:23 +00:00
}
2024-03-20 05:47:14 +00:00
2025-03-28 04:00:23 +00:00
QueryTextCursorMovedToEnd = true ;
2016-07-20 22:37:06 +00:00
}
2021-01-09 04:01:08 +00:00
2017-02-12 22:34:12 +00:00
public bool LastQuerySelected { get ; set ; }
2021-10-05 08:37:58 +00:00
// This is not a reliable indicator of the cursor's position, it is manually set for a specific purpose.
2016-07-20 22:37:06 +00:00
public bool QueryTextCursorMovedToEnd { get ; set ; }
2016-05-05 15:08:44 +00:00
2016-06-22 23:26:57 +00:00
private ResultsViewModel _selectedResults ;
2021-01-09 04:01:08 +00:00
2016-06-22 23:26:57 +00:00
private ResultsViewModel SelectedResults
2016-05-05 15:08:44 +00:00
{
2025-03-22 15:33:04 +00:00
get = > _selectedResults ;
2016-05-05 15:08:44 +00:00
set
{
2025-03-26 02:07:35 +00:00
var isReturningFromQueryResults = QueryResultsSelected ( ) ;
2024-06-03 11:59:14 +00:00
var isReturningFromContextMenu = ContextMenuSelected ( ) ;
2025-03-22 15:33:04 +00:00
var isReturningFromHistory = HistorySelected ( ) ;
2016-06-22 23:26:57 +00:00
_selectedResults = value ;
2025-03-26 02:07:35 +00:00
if ( QueryResultsSelected ( ) )
2016-05-05 15:08:44 +00:00
{
2025-03-25 11:36:39 +00:00
Results . Visibility = Visibility . Visible ;
2023-01-09 15:56:48 +00:00
ContextMenu . Visibility = Visibility . Collapsed ;
History . Visibility = Visibility . Collapsed ;
2024-06-03 11:59:14 +00:00
// QueryText setter (used in ChangeQueryText) runs the query again, resetting the selected
// result from the one that was selected before going into the context menu to the first result.
// The code below correctly restores QueryText and puts the text caret at the end without
// running the query again when returning from the context menu.
if ( isReturningFromContextMenu )
{
_queryText = _queryTextBeforeLeaveResults ;
2025-05-02 06:40:18 +00:00
// When executing OnPropertyChanged, QueryTextBox_TextChanged1 and Query will be called
// So we need to ignore it so that we will not call Query again
_ignoredQueryText = _queryText ;
2024-06-03 11:59:14 +00:00
OnPropertyChanged ( nameof ( QueryText ) ) ;
QueryTextCursorMovedToEnd = true ;
}
else
{
ChangeQueryText ( _queryTextBeforeLeaveResults ) ;
}
2025-03-22 15:33:04 +00:00
// If we are returning from history and we have not set select item yet,
// we need to clear the preview selected item
if ( isReturningFromHistory & & _selectedItemFromQueryResults . HasValue & & ( ! _selectedItemFromQueryResults . Value ) )
{
PreviewSelectedItem = null ;
}
2016-05-05 15:08:44 +00:00
}
else
{
2023-01-09 15:56:48 +00:00
Results . Visibility = Visibility . Collapsed ;
2025-03-25 11:36:39 +00:00
if ( HistorySelected ( ) )
{
ContextMenu . Visibility = Visibility . Collapsed ;
History . Visibility = Visibility . Visible ;
}
else
{
ContextMenu . Visibility = Visibility . Visible ;
History . Visibility = Visibility . Collapsed ;
}
2016-06-23 21:17:47 +00:00
_queryTextBeforeLeaveResults = QueryText ;
// Because of Fody's optimization
// setter won't be called when property value is not changed.
// so we need manually call Query()
// http://stackoverflow.com/posts/25895769/revisions
2025-03-21 05:34:52 +00:00
QueryText = string . Empty ;
2025-03-30 04:39:18 +00:00
// When we are changing query because selected results are changed to history or context menu,
// we should not delay the query
2025-03-21 05:34:52 +00:00
Query ( false ) ;
2025-03-22 15:42:14 +00:00
if ( HistorySelected ( ) )
{
// If we are returning from query results and we have not set select item yet,
// we need to clear the preview selected item
if ( isReturningFromQueryResults & & _selectedItemFromQueryResults . HasValue & & _selectedItemFromQueryResults . Value )
{
PreviewSelectedItem = null ;
}
}
2016-05-05 15:08:44 +00:00
}
}
}
2016-05-23 21:17:38 +00:00
public Visibility ProgressBarVisibility { get ; set ; }
public Visibility MainWindowVisibility { get ; set ; }
2025-03-28 13:39:25 +00:00
2025-03-30 10:03:43 +00:00
// This is to be used for determining the visibility status of the main window instead of MainWindowVisibility
2021-11-27 02:38:47 +00:00
// because it is more accurate and reliable representation than using Visibility as a condition check
public bool MainWindowVisibilityStatus { get ; set ; } = true ;
2023-07-03 21:42:41 +00:00
public event VisibilityChangedEventHandler VisibilityChanged ;
2025-03-28 07:52:31 +00:00
public Visibility ClockPanelVisibility { get ; set ; }
2021-12-17 12:48:29 +00:00
public Visibility SearchIconVisibility { get ; set ; }
2025-03-28 07:52:31 +00:00
public double ClockPanelOpacity { get ; set ; } = 1 ;
public double SearchIconOpacity { get ; set ; } = 1 ;
2021-12-17 12:48:29 +00:00
2025-03-29 07:08:49 +00:00
private string _placeholderText ;
public string PlaceholderText
{
2025-03-29 07:48:52 +00:00
get = > string . IsNullOrEmpty ( _placeholderText ) ? App . API . GetTranslation ( "queryTextBoxPlaceholder" ) : _placeholderText ;
2025-03-29 07:08:49 +00:00
set
{
2025-03-29 07:48:52 +00:00
_placeholderText = value ;
2025-03-29 07:08:49 +00:00
OnPropertyChanged ( ) ;
}
}
2022-09-19 20:32:17 +00:00
public double MainWindowWidth
{
2022-10-25 12:45:06 +00:00
get = > Settings . WindowSize ;
2024-05-28 08:44:28 +00:00
set
{
if ( ! MainWindowVisibilityStatus ) return ;
Settings . WindowSize = value ;
}
2022-09-19 20:32:17 +00:00
}
2021-10-22 04:31:51 +00:00
2024-05-14 07:54:15 +00:00
public double MainWindowHeight
{
get = > Settings . WindowHeightSize ;
set = > Settings . WindowHeightSize = value ;
}
public double QueryBoxFontSize
{
get = > Settings . QueryBoxFontSize ;
set = > Settings . QueryBoxFontSize = value ;
}
public double ItemHeightSize
{
get = > Settings . ItemHeightSize ;
set = > Settings . ItemHeightSize = value ;
}
public double ResultItemFontSize
{
get = > Settings . ResultItemFontSize ;
set = > Settings . ResultItemFontSize = value ;
}
public double ResultSubItemFontSize
{
get = > Settings . ResultSubItemFontSize ;
set = > Settings . ResultSubItemFontSize = value ;
}
2024-11-27 13:57:17 +00:00
public ImageSource PluginIconSource { get ; private set ; } = null ;
2021-12-17 11:23:19 +00:00
public string PluginIconPath { get ; set ; } = null ;
2022-12-22 14:57:15 +00:00
public string OpenResultCommandModifiers = > Settings . OpenResultModifiers ;
2022-01-25 18:14:43 +00:00
2025-03-19 10:22:43 +00:00
private static string VerifyOrSetDefaultHotkey ( string hotkey , string defaultHotkey )
2024-03-20 05:47:14 +00:00
{
2024-04-13 17:49:57 +00:00
try
2023-02-25 07:00:19 +00:00
{
2024-04-13 17:49:57 +00:00
var converter = new KeyGestureConverter ( ) ;
var key = ( KeyGesture ) converter . ConvertFromString ( hotkey ) ;
2023-02-25 07:00:19 +00:00
}
2024-04-13 17:49:57 +00:00
catch ( Exception e ) when ( e is NotSupportedException | | e is InvalidEnumArgumentException )
2024-04-13 09:47:37 +00:00
{
2024-04-13 17:49:57 +00:00
return defaultHotkey ;
2024-04-13 09:47:37 +00:00
}
2024-04-13 17:49:57 +00:00
return hotkey ;
2024-04-13 09:47:37 +00:00
}
2024-05-27 07:09:22 +00:00
2024-04-13 17:49:57 +00:00
public string PreviewHotkey = > VerifyOrSetDefaultHotkey ( Settings . PreviewHotkey , "F1" ) ;
2024-04-15 13:28:07 +00:00
public string AutoCompleteHotkey = > VerifyOrSetDefaultHotkey ( Settings . AutoCompleteHotkey , "Ctrl+Tab" ) ;
2024-04-18 12:01:01 +00:00
public string AutoCompleteHotkey2 = > VerifyOrSetDefaultHotkey ( Settings . AutoCompleteHotkey2 , "" ) ;
2024-04-15 12:52:15 +00:00
public string SelectNextItemHotkey = > VerifyOrSetDefaultHotkey ( Settings . SelectNextItemHotkey , "Tab" ) ;
2024-04-18 12:01:01 +00:00
public string SelectNextItemHotkey2 = > VerifyOrSetDefaultHotkey ( Settings . SelectNextItemHotkey2 , "" ) ;
2024-04-15 13:28:07 +00:00
public string SelectPrevItemHotkey = > VerifyOrSetDefaultHotkey ( Settings . SelectPrevItemHotkey , "Shift+Tab" ) ;
2024-04-18 12:01:01 +00:00
public string SelectPrevItemHotkey2 = > VerifyOrSetDefaultHotkey ( Settings . SelectPrevItemHotkey2 , "" ) ;
public string SelectNextPageHotkey = > VerifyOrSetDefaultHotkey ( Settings . SelectNextPageHotkey , "" ) ;
public string SelectPrevPageHotkey = > VerifyOrSetDefaultHotkey ( Settings . SelectPrevPageHotkey , "" ) ;
2024-04-18 07:35:05 +00:00
public string OpenContextMenuHotkey = > VerifyOrSetDefaultHotkey ( Settings . OpenContextMenuHotkey , "Ctrl+O" ) ;
2024-04-18 07:01:26 +00:00
public string SettingWindowHotkey = > VerifyOrSetDefaultHotkey ( Settings . SettingWindowHotkey , "Ctrl+I" ) ;
2024-05-15 16:11:27 +00:00
public string CycleHistoryUpHotkey = > VerifyOrSetDefaultHotkey ( Settings . CycleHistoryUpHotkey , "Alt+Up" ) ;
public string CycleHistoryDownHotkey = > VerifyOrSetDefaultHotkey ( Settings . CycleHistoryDownHotkey , "Alt+Down" ) ;
2024-04-13 09:47:37 +00:00
2022-12-20 14:35:07 +00:00
public bool StartWithEnglishMode = > Settings . AlwaysStartEn ;
2024-12-08 20:33:19 +00:00
2016-05-05 15:08:44 +00:00
#endregion
2023-04-22 06:12:12 +00:00
#region Preview
2025-03-26 02:30:47 +00:00
private static readonly int ResultAreaColumnPreviewShown = 1 ;
private static readonly int ResultAreaColumnPreviewHidden = 3 ;
2025-03-22 15:33:04 +00:00
2025-03-26 02:30:47 +00:00
private bool? _selectedItemFromQueryResults ;
2025-03-22 15:08:47 +00:00
2025-03-26 02:30:47 +00:00
private ResultViewModel _previewSelectedItem ;
2025-03-22 15:33:04 +00:00
public ResultViewModel PreviewSelectedItem
2025-03-22 15:08:47 +00:00
{
2025-03-26 02:30:47 +00:00
get = > _previewSelectedItem ;
2025-03-22 15:08:47 +00:00
set
{
2025-03-26 02:30:47 +00:00
_previewSelectedItem = value ;
2025-03-22 15:08:47 +00:00
OnPropertyChanged ( ) ;
}
}
2024-05-28 11:38:02 +00:00
public bool InternalPreviewVisible
2023-04-22 06:12:12 +00:00
{
2024-05-28 11:38:02 +00:00
get
2023-04-22 06:17:05 +00:00
{
2024-05-28 11:38:02 +00:00
if ( ResultAreaColumn = = ResultAreaColumnPreviewShown )
return true ;
if ( ResultAreaColumn = = ResultAreaColumnPreviewHidden )
return false ;
#if DEBUG
throw new NotImplementedException ( "ResultAreaColumn should match ResultAreaColumnPreviewShown/ResultAreaColumnPreviewHidden value" ) ;
#else
2025-04-13 10:13:55 +00:00
App . API . LogError ( ClassName , "ResultAreaColumnPreviewHidden/ResultAreaColumnPreviewShown int value not implemented" , "InternalPreviewVisible" ) ;
2024-05-28 11:38:02 +00:00
return false ;
2025-03-17 02:07:40 +00:00
#endif
2024-05-28 11:38:02 +00:00
}
}
public int ResultAreaColumn { get ; set ; } = ResultAreaColumnPreviewShown ;
2024-05-28 11:41:59 +00:00
// This is not a reliable indicator of whether external preview is visible due to the
2024-05-30 11:49:44 +00:00
// ability of manually closing/exiting the external preview program which, does not inform flow that
2024-05-28 11:41:59 +00:00
// preview is no longer available.
2025-03-26 02:07:35 +00:00
public bool ExternalPreviewVisible { get ; private set ; }
2024-05-28 11:41:59 +00:00
2025-03-26 02:07:35 +00:00
private async Task ShowPreviewAsync ( )
2024-05-28 11:41:59 +00:00
{
var useExternalPreview = PluginManager . UseExternalPreview ( ) ;
2024-06-06 02:36:26 +00:00
switch ( useExternalPreview )
2023-05-20 16:29:56 +00:00
{
2024-06-06 02:36:26 +00:00
case true
when CanExternalPreviewSelectedResult ( out var path ) :
// Internal preview may still be on when user switches to external
if ( InternalPreviewVisible )
HideInternalPreview ( ) ;
2025-03-26 02:07:35 +00:00
_ = OpenExternalPreviewAsync ( path ) ;
2024-06-06 02:36:26 +00:00
break ;
2024-05-28 11:41:59 +00:00
2024-06-06 02:36:26 +00:00
case true
when ! CanExternalPreviewSelectedResult ( out var _ ) :
if ( ExternalPreviewVisible )
2025-03-26 02:07:35 +00:00
await CloseExternalPreviewAsync ( ) ;
2024-06-06 02:36:26 +00:00
ShowInternalPreview ( ) ;
break ;
case false :
ShowInternalPreview ( ) ;
break ;
}
2024-05-28 11:41:59 +00:00
}
2016-05-09 22:35:20 +00:00
2024-05-28 11:41:59 +00:00
private void HidePreview ( )
2024-12-08 20:33:19 +00:00
{
2024-05-28 11:41:59 +00:00
if ( PluginManager . UseExternalPreview ( ) )
2025-03-26 02:07:35 +00:00
_ = CloseExternalPreviewAsync ( ) ;
2024-05-28 11:41:59 +00:00
if ( InternalPreviewVisible )
HideInternalPreview ( ) ;
}
[RelayCommand]
private void TogglePreview ( )
{
2024-05-31 07:02:54 +00:00
if ( InternalPreviewVisible | | ExternalPreviewVisible )
2023-04-22 09:06:38 +00:00
{
2024-05-31 07:02:54 +00:00
HidePreview ( ) ;
}
else
{
2025-03-26 02:07:35 +00:00
_ = ShowPreviewAsync ( ) ;
2023-04-22 09:06:38 +00:00
}
}
2025-03-26 02:07:35 +00:00
private async Task OpenExternalPreviewAsync ( string path , bool sendFailToast = true )
2023-04-22 09:06:38 +00:00
{
2025-03-26 02:07:35 +00:00
await PluginManager . OpenExternalPreviewAsync ( path , sendFailToast ) . ConfigureAwait ( false ) ;
2024-05-28 11:31:29 +00:00
ExternalPreviewVisible = true ;
2023-05-13 17:40:45 +00:00
}
2025-03-26 02:07:35 +00:00
private async Task CloseExternalPreviewAsync ( )
2023-05-13 17:40:45 +00:00
{
2025-03-26 02:07:35 +00:00
await PluginManager . CloseExternalPreviewAsync ( ) . ConfigureAwait ( false ) ;
2024-05-28 11:31:29 +00:00
ExternalPreviewVisible = false ;
2023-05-13 17:40:45 +00:00
}
2023-07-11 12:08:22 +00:00
2025-03-26 02:07:35 +00:00
private static async Task SwitchExternalPreviewAsync ( string path , bool sendFailToast = true )
2023-05-13 17:40:45 +00:00
{
2025-03-26 02:07:35 +00:00
await PluginManager . SwitchExternalPreviewAsync ( path , sendFailToast ) . ConfigureAwait ( false ) ;
2023-04-22 09:06:38 +00:00
}
2020-05-03 17:17:16 +00:00
2023-04-22 08:55:27 +00:00
private void ShowInternalPreview ( )
2023-04-22 06:12:12 +00:00
{
2024-05-28 11:41:59 +00:00
ResultAreaColumn = ResultAreaColumnPreviewShown ;
2025-03-22 15:33:04 +00:00
PreviewSelectedItem ? . LoadPreviewImage ( ) ;
2023-04-22 06:12:12 +00:00
}
2023-04-22 08:55:27 +00:00
private void HideInternalPreview ( )
2023-04-22 06:12:12 +00:00
{
2024-05-28 11:41:59 +00:00
ResultAreaColumn = ResultAreaColumnPreviewHidden ;
2023-04-22 06:12:12 +00:00
}
public void ResetPreview ( )
{
2024-06-12 04:14:25 +00:00
switch ( Settings . AlwaysPreview )
2023-04-22 06:12:12 +00:00
{
2024-06-12 04:14:25 +00:00
case true
when PluginManager . AllowAlwaysPreview ( ) & & CanExternalPreviewSelectedResult ( out var path ) :
2025-03-26 02:07:35 +00:00
_ = OpenExternalPreviewAsync ( path ) ;
2024-06-12 04:14:25 +00:00
break ;
case true :
ShowInternalPreview ( ) ;
break ;
case false :
HidePreview ( ) ;
break ;
2023-04-22 06:12:12 +00:00
}
}
2024-12-08 20:33:19 +00:00
2025-03-26 02:07:35 +00:00
private async Task UpdatePreviewAsync ( )
2023-04-22 06:12:12 +00:00
{
2024-05-28 11:41:59 +00:00
switch ( PluginManager . UseExternalPreview ( ) )
2023-04-22 07:53:43 +00:00
{
2024-05-28 11:41:59 +00:00
case true
2024-06-06 02:36:26 +00:00
when CanExternalPreviewSelectedResult ( out var path ) :
if ( ExternalPreviewVisible )
{
2025-03-26 02:07:35 +00:00
_ = SwitchExternalPreviewAsync ( path , false ) ;
2024-06-06 02:36:26 +00:00
}
else if ( InternalPreviewVisible )
{
HideInternalPreview ( ) ;
2025-03-26 02:07:35 +00:00
_ = OpenExternalPreviewAsync ( path ) ;
2024-06-06 02:36:26 +00:00
}
2024-05-28 11:41:59 +00:00
break ;
case true
2024-06-06 02:36:26 +00:00
when ! CanExternalPreviewSelectedResult ( out var _ ) :
if ( ExternalPreviewVisible )
{
2025-03-26 02:07:35 +00:00
await CloseExternalPreviewAsync ( ) ;
2024-06-06 02:36:26 +00:00
ShowInternalPreview ( ) ;
}
2024-05-28 11:41:59 +00:00
break ;
2024-06-06 02:36:26 +00:00
case false
when InternalPreviewVisible :
2025-03-22 15:33:04 +00:00
PreviewSelectedItem ? . LoadPreviewImage ( ) ;
2024-05-28 11:41:59 +00:00
break ;
2023-04-22 07:53:43 +00:00
}
2023-04-22 06:17:05 +00:00
}
2023-04-22 07:53:43 +00:00
private bool CanExternalPreviewSelectedResult ( out string path )
{
2025-03-26 02:07:35 +00:00
path = QueryResultsPreviewed ( ) ? Results . SelectedItem ? . Result ? . Preview . FilePath : string . Empty ;
2023-04-22 09:06:38 +00:00
return ! string . IsNullOrEmpty ( path ) ;
2023-04-22 07:53:43 +00:00
}
2025-03-26 02:07:35 +00:00
private bool QueryResultsPreviewed ( )
{
var previewed = PreviewSelectedItem = = Results . SelectedItem ;
return previewed ;
}
2020-05-04 22:41:24 +00:00
2016-05-05 15:08:44 +00:00
#endregion
2022-12-23 03:46:12 +00:00
#region Query
2025-05-03 14:54:46 +00:00
public void QueryResults ( )
2025-05-03 09:02:39 +00:00
{
2025-05-03 14:54:46 +00:00
_ = QueryResultsAsync ( false ) ;
2025-05-03 09:02:39 +00:00
}
2025-03-26 08:57:37 +00:00
public void Query ( bool searchDelay , bool isReQuery = false )
2025-03-27 05:22:23 +00:00
{
2025-05-02 06:40:18 +00:00
if ( _ignoredQueryText ! = null )
{
if ( _ignoredQueryText = = QueryText )
{
_ignoredQueryText = null ;
return ;
}
else
{
// If _ignoredQueryText does not match current QueryText, we should still execute Query
_ignoredQueryText = null ;
}
}
2025-05-02 04:26:14 +00:00
if ( QueryResultsSelected ( ) )
{
_ = QueryResultsAsync ( searchDelay , isReQuery ) ;
}
else if ( ContextMenuSelected ( ) )
{
QueryContextMenu ( ) ;
}
else if ( HistorySelected ( ) )
{
QueryHistory ( ) ;
}
2025-03-27 05:22:23 +00:00
}
2025-03-30 04:07:04 +00:00
private async Task QueryAsync ( bool searchDelay , bool isReQuery = false )
2016-06-23 21:17:47 +00:00
{
2025-03-26 02:07:35 +00:00
if ( QueryResultsSelected ( ) )
2016-06-23 21:17:47 +00:00
{
2025-03-30 01:38:59 +00:00
await QueryResultsAsync ( searchDelay , isReQuery ) ;
2016-06-23 21:17:47 +00:00
}
else if ( ContextMenuSelected ( ) )
{
QueryContextMenu ( ) ;
}
else if ( HistorySelected ( ) )
{
QueryHistory ( ) ;
}
}
2016-02-18 11:30:36 +00:00
2016-02-21 14:40:10 +00:00
private void QueryContextMenu ( )
2016-02-18 11:30:36 +00:00
{
2016-06-23 21:17:47 +00:00
const string id = "Context Menu ID" ;
2016-06-22 23:26:57 +00:00
var query = QueryText . ToLower ( ) . Trim ( ) ;
ContextMenu . Clear ( ) ;
var selected = Results . SelectedItem ? . Result ;
if ( selected ! = null ) // SelectedItem returns null if selection is empty.
2016-02-18 11:30:36 +00:00
{
2025-05-04 01:14:57 +00:00
List < Result > results ;
if ( selected . PluginID = = null ) // SelectedItem from history in home page.
{
results = new ( )
{
ContextMenuTopMost ( selected )
} ;
}
else
{
results = PluginManager . GetContextMenusForPlugin ( selected ) ;
results . Add ( ContextMenuTopMost ( selected ) ) ;
2025-05-11 08:53:44 +00:00
results . Add ( ContextMenuPluginInfo ( selected ) ) ;
2025-05-04 01:14:57 +00:00
}
2016-03-26 01:20:42 +00:00
2016-06-22 23:26:57 +00:00
if ( ! string . IsNullOrEmpty ( query ) )
2016-02-18 11:30:36 +00:00
{
2024-02-04 16:41:42 +00:00
var filtered = results . Select ( x = > x . Clone ( ) ) . Where
2016-06-22 23:26:57 +00:00
(
2021-01-17 10:10:36 +00:00
r = >
{
2025-03-27 05:22:23 +00:00
var match = App . API . FuzzySearch ( query , r . Title ) ;
2021-01-17 10:10:36 +00:00
if ( ! match . IsSearchPrecisionScoreMet ( ) )
{
2025-03-27 05:22:23 +00:00
match = App . API . FuzzySearch ( query , r . SubTitle ) ;
2021-01-17 10:10:36 +00:00
}
if ( ! match . IsSearchPrecisionScoreMet ( ) ) return false ;
2021-02-03 09:50:21 +00:00
2021-01-17 10:10:36 +00:00
r . Score = match . Score ;
return true ;
} ) . ToList ( ) ;
2016-06-23 21:17:47 +00:00
ContextMenu . AddResults ( filtered , id ) ;
2016-02-18 11:30:36 +00:00
}
2016-06-22 23:26:57 +00:00
else
2016-05-05 20:15:13 +00:00
{
2016-06-23 21:17:47 +00:00
ContextMenu . AddResults ( results , id ) ;
2016-06-22 23:26:57 +00:00
}
}
}
2016-06-23 21:17:47 +00:00
private void QueryHistory ( )
2016-06-22 23:26:57 +00:00
{
2016-06-23 21:17:47 +00:00
const string id = "Query History ID" ;
var query = QueryText . ToLower ( ) . Trim ( ) ;
History . Clear ( ) ;
2025-05-04 08:37:15 +00:00
var results = GetHistoryItems ( _history . Items ) ;
if ( ! string . IsNullOrEmpty ( query ) )
{
var filtered = results . Where
(
r = > App . API . FuzzySearch ( query , r . Title ) . IsSearchPrecisionScoreMet ( ) | |
App . API . FuzzySearch ( query , r . SubTitle ) . IsSearchPrecisionScoreMet ( )
) . ToList ( ) ;
History . AddResults ( filtered , id ) ;
}
else
{
History . AddResults ( results , id ) ;
}
}
private static List < Result > GetHistoryItems ( IEnumerable < HistoryItem > historyItems )
{
2016-06-23 21:17:47 +00:00
var results = new List < Result > ( ) ;
2025-05-04 08:37:15 +00:00
foreach ( var h in historyItems )
2016-06-22 23:26:57 +00:00
{
2025-03-17 02:07:40 +00:00
var title = App . API . GetTranslation ( "executeQuery" ) ;
var time = App . API . GetTranslation ( "lastExecuteTime" ) ;
2016-06-23 21:17:47 +00:00
var result = new Result
{
Title = string . Format ( title , h . Query ) ,
SubTitle = string . Format ( time , h . ExecutedDateTime ) ,
2025-05-05 10:53:40 +00:00
IcoPath = Constant . HistoryIcon ,
2024-03-20 05:47:14 +00:00
OriginQuery = new Query { RawQuery = h . Query } ,
2016-06-23 21:17:47 +00:00
Action = _ = >
{
2025-05-02 06:37:10 +00:00
App . API . BackToQueryResults ( ) ;
2025-03-27 05:22:23 +00:00
App . API . ChangeQuery ( h . Query ) ;
2016-06-23 21:17:47 +00:00
return false ;
2025-05-05 10:53:40 +00:00
} ,
Glyph = new GlyphInfo ( FontFamily : "/Resources/#Segoe Fluent Icons" , Glyph : "\uE81C" )
2016-06-23 21:17:47 +00:00
} ;
results . Add ( result ) ;
}
2025-05-04 08:37:15 +00:00
return results ;
2016-02-18 11:30:36 +00:00
}
2025-03-21 05:34:52 +00:00
private async Task QueryResultsAsync ( bool searchDelay , bool isReQuery = false , bool reSelect = true )
2016-02-18 11:30:36 +00:00
{
2025-05-06 06:14:10 +00:00
_updateSource ? . Cancel ( ) ;
2016-02-18 11:30:36 +00:00
2025-05-04 14:05:57 +00:00
App . API . LogDebug ( ClassName , $"Start query with text: <{QueryText}>" ) ;
2025-05-04 14:04:01 +00:00
2025-05-02 04:26:14 +00:00
var query = await ConstructQueryAsync ( QueryText , Settings . CustomShortcuts , Settings . BuiltinShortcuts ) ;
2025-03-21 01:57:15 +00:00
2025-05-02 04:26:14 +00:00
if ( query = = null ) // shortcut expanded
2016-02-18 11:30:36 +00:00
{
2025-05-04 14:04:01 +00:00
App . API . LogDebug ( ClassName , $"Clear query results" ) ;
2025-05-04 01:38:35 +00:00
// Hide and clear results again because running query may show and add some results
Results . Visibility = Visibility . Collapsed ;
Results . Clear ( ) ;
2025-05-02 04:26:14 +00:00
2025-05-04 01:38:35 +00:00
// Reset plugin icon
PluginIconPath = null ;
PluginIconSource = null ;
SearchIconVisibility = Visibility . Visible ;
2025-05-02 04:26:14 +00:00
2025-05-04 01:38:35 +00:00
// Hide progress bar again because running query may set this to visible
ProgressBarVisibility = Visibility . Hidden ;
return ;
2021-01-13 01:25:46 +00:00
}
2020-11-20 15:21:04 +00:00
2025-05-04 14:04:01 +00:00
App . API . LogDebug ( ClassName , $"Start query with ActionKeyword <{query.ActionKeyword}> and RawQuery <{query.RawQuery}>" ) ;
2025-05-10 12:56:48 +00:00
var currentIsHomeQuery = query . RawQuery = = string . Empty ;
2025-05-04 13:43:48 +00:00
2025-05-06 11:35:40 +00:00
_updateSource ? . Dispose ( ) ;
var currentUpdateSource = new CancellationTokenSource ( ) ;
_updateSource = currentUpdateSource ;
var currentCancellationToken = _updateSource . Token ;
_updateToken = currentCancellationToken ;
2020-11-21 08:42:15 +00:00
2021-01-13 01:25:46 +00:00
ProgressBarVisibility = Visibility . Hidden ;
_isQueryRunning = true ;
2021-01-02 07:52:41 +00:00
2021-06-25 06:43:47 +00:00
// Switch to ThreadPool thread
await TaskScheduler . Default ;
2025-05-06 11:35:40 +00:00
if ( currentCancellationToken . IsCancellationRequested ) return ;
2021-06-26 07:42:27 +00:00
2023-06-07 20:19:47 +00:00
// Update the query's IsReQuery property to true if this is a re-query
2025-05-04 01:38:35 +00:00
query . IsReQuery = isReQuery ;
2021-01-09 04:17:02 +00:00
2025-05-10 12:56:48 +00:00
2021-01-13 01:25:46 +00:00
2025-05-04 01:38:35 +00:00
ICollection < PluginPair > plugins = Array . Empty < PluginPair > ( ) ;
2025-05-10 12:56:48 +00:00
if ( currentIsHomeQuery )
2025-05-02 04:26:14 +00:00
{
2025-05-04 01:38:35 +00:00
if ( Settings . ShowHomePage )
{
plugins = PluginManager . ValidPluginsForHomeQuery ( ) ;
}
2025-05-02 04:26:14 +00:00
PluginIconPath = null ;
PluginIconSource = null ;
SearchIconVisibility = Visibility . Visible ;
}
2025-05-03 08:50:50 +00:00
else
{
plugins = PluginManager . ValidPluginsForQuery ( query ) ;
if ( plugins . Count = = 1 )
{
PluginIconPath = plugins . Single ( ) . Metadata . IcoPath ;
PluginIconSource = await App . API . LoadImageAsync ( PluginIconPath ) ;
SearchIconVisibility = Visibility . Hidden ;
}
else
{
PluginIconPath = null ;
PluginIconSource = null ;
SearchIconVisibility = Visibility . Visible ;
}
}
2025-05-02 04:26:14 +00:00
2025-05-05 00:27:51 +00:00
var validPluginNames = plugins . Select ( x = > $"<{x.Metadata.Name}>" ) ;
App . API . LogDebug ( ClassName , $"Valid <{plugins.Count}> plugins: {string.Join(" ", validPluginNames)}" ) ;
2025-05-02 04:26:14 +00:00
// Do not wait for performance improvement
/ * if ( string . IsNullOrEmpty ( query . ActionKeyword ) )
2021-06-11 04:40:07 +00:00
{
2025-04-11 08:13:55 +00:00
// Wait 15 millisecond for query change in global query
2021-06-11 04:40:07 +00:00
// if query changes, return so that it won't be calculated
2025-05-06 11:35:40 +00:00
await Task . Delay ( 15 , currentCancellationToken ) ;
if ( currentCancellationToken . IsCancellationRequested ) return ;
2025-05-02 04:26:14 +00:00
} * /
2021-06-11 04:40:07 +00:00
2025-05-06 11:35:40 +00:00
_ = Task . Delay ( 200 , currentCancellationToken ) . ContinueWith ( _ = >
2021-01-17 09:51:21 +00:00
{
2025-03-21 05:34:52 +00:00
// start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet
2025-05-02 04:26:14 +00:00
if ( _isQueryRunning )
2025-03-21 05:34:52 +00:00
{
ProgressBarVisibility = Visibility . Visible ;
}
2025-03-21 02:05:48 +00:00
} ,
2025-05-06 11:35:40 +00:00
currentCancellationToken ,
2025-03-21 02:05:48 +00:00
TaskContinuationOptions . NotOnCanceled ,
TaskScheduler . Default ) ;
2019-12-03 22:15:46 +00:00
2025-03-30 10:03:43 +00:00
// plugins are ICollection, meaning LINQ will get the Count and preallocate Array
2021-01-02 07:52:41 +00:00
2025-05-03 14:09:25 +00:00
Task [ ] tasks ;
2025-05-10 12:56:48 +00:00
if ( currentIsHomeQuery )
2021-06-11 04:40:07 +00:00
{
2025-05-04 01:38:35 +00:00
tasks = plugins . Select ( plugin = > plugin . Metadata . HomeDisabled switch
2025-05-03 14:09:25 +00:00
{
2025-05-06 11:35:40 +00:00
false = > QueryTaskAsync ( plugin , currentCancellationToken ) ,
2025-05-03 14:09:25 +00:00
true = > Task . CompletedTask
2025-05-04 01:38:35 +00:00
} ) . ToArray ( ) ;
2025-05-03 14:09:25 +00:00
2025-05-04 01:38:35 +00:00
// Query history results for home page firstly so it will be put on top of the results
2025-05-03 14:09:25 +00:00
if ( Settings . ShowHistoryResultsForHomePage )
{
2025-05-06 11:35:40 +00:00
QueryHistoryTask ( currentCancellationToken ) ;
2025-05-03 14:09:25 +00:00
}
}
else
{
tasks = plugins . Select ( plugin = > plugin . Metadata . Disabled switch
{
2025-05-06 11:35:40 +00:00
false = > QueryTaskAsync ( plugin , currentCancellationToken ) ,
2025-05-03 14:09:25 +00:00
true = > Task . CompletedTask
} ) . ToArray ( ) ;
}
2021-05-28 09:06:58 +00:00
2021-06-11 04:40:07 +00:00
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 ) ;
}
catch ( OperationCanceledException )
{
// nothing to do here
}
2021-01-10 03:13:46 +00:00
2025-05-06 11:35:40 +00:00
if ( currentCancellationToken . IsCancellationRequested ) return ;
2021-01-17 10:05:31 +00:00
2021-06-11 04:40:07 +00:00
// this should happen once after all queries are done so progress bar should continue
// until the end of all querying
_isQueryRunning = false ;
2025-05-02 04:26:14 +00:00
2025-05-06 11:35:40 +00:00
if ( ! currentCancellationToken . IsCancellationRequested )
2021-06-11 04:40:07 +00:00
{
// update to hidden if this is still the current query
ProgressBarVisibility = Visibility . Hidden ;
}
2021-01-17 09:51:21 +00:00
2021-06-11 04:40:07 +00:00
// Local function
2025-03-30 10:03:43 +00:00
async Task QueryTaskAsync ( PluginPair plugin , CancellationToken token )
2021-06-11 04:40:07 +00:00
{
2025-05-04 14:04:01 +00:00
App . API . LogDebug ( ClassName , $"Wait for querying plugin <{plugin.Metadata.Name}>" ) ;
2025-05-10 12:56:48 +00:00
if ( searchDelay & & ! currentIsHomeQuery ) // Do not delay for home query
2025-03-21 05:34:52 +00:00
{
2025-04-07 06:16:17 +00:00
var searchDelayTime = plugin . Metadata . SearchDelayTime ? ? Settings . SearchDelayTime ;
2025-03-30 14:40:46 +00:00
await Task . Delay ( searchDelayTime , token ) ;
2025-03-21 05:34:52 +00:00
2025-05-02 04:26:14 +00:00
if ( token . IsCancellationRequested ) return ;
2025-03-21 05:34:52 +00:00
}
2021-06-25 06:43:47 +00:00
// Since it is wrapped within a ThreadPool Thread, the synchronous context is null
2021-06-11 04:40:07 +00:00
// Task.Yield will force it to run in ThreadPool
await Task . Yield ( ) ;
2021-01-17 09:51:21 +00:00
2025-05-10 12:56:48 +00:00
var results = currentIsHomeQuery ?
2025-05-04 02:28:20 +00:00
await PluginManager . QueryHomeForPluginAsync ( plugin , query , token ) :
2025-05-03 08:50:50 +00:00
await PluginManager . QueryForPluginAsync ( plugin , query , token ) ;
2021-07-07 17:38:57 +00:00
2025-05-02 04:26:14 +00:00
if ( token . IsCancellationRequested ) return ;
2021-04-19 05:06:03 +00:00
2025-02-20 10:17:29 +00:00
IReadOnlyList < Result > resultsCopy ;
if ( results = = null )
{
resultsCopy = _emptyResult ;
}
else
{
// make a copy of results to avoid possible issue that FL changes some properties of the records, like score, etc.
2025-03-21 02:29:45 +00:00
resultsCopy = DeepCloneResults ( results , token ) ;
2025-02-20 10:17:29 +00:00
}
2021-06-11 04:40:07 +00:00
2025-04-11 07:48:49 +00:00
foreach ( var result in resultsCopy )
2025-04-11 07:21:11 +00:00
{
2025-04-11 07:38:00 +00:00
if ( string . IsNullOrEmpty ( result . BadgeIcoPath ) )
2025-04-11 07:21:11 +00:00
{
2025-04-11 07:38:00 +00:00
result . BadgeIcoPath = plugin . Metadata . IcoPath ;
2025-04-11 07:21:11 +00:00
}
}
2025-05-02 04:26:14 +00:00
if ( token . IsCancellationRequested ) return ;
2025-05-04 14:04:01 +00:00
App . API . LogDebug ( ClassName , $"Update results for plugin <{plugin.Metadata.Name}>" ) ;
2025-05-10 12:56:48 +00:00
// Indicate if to clear existing results so to show only ones from plugins with action keywords
var shouldClearExistingResults = ShouldClearExistingResults ( query , currentIsHomeQuery ) ;
_lastQuery = query ;
_previousIsHomeQuery = currentIsHomeQuery ;
2025-02-20 10:17:29 +00:00
if ( ! _resultsUpdateChannelWriter . TryWrite ( new ResultsForUpdate ( resultsCopy , plugin . Metadata , query ,
2025-05-10 12:56:48 +00:00
token , reSelect , shouldClearExistingResults ) ) )
2021-06-11 04:40:07 +00:00
{
2025-04-13 09:50:44 +00:00
App . API . LogError ( ClassName , "Unable to add item to Result Update Queue" ) ;
2021-06-11 04:40:07 +00:00
}
}
2025-05-03 14:09:25 +00:00
2025-05-06 11:35:40 +00:00
void QueryHistoryTask ( CancellationToken token )
2025-05-03 14:09:25 +00:00
{
// Select last history results and revert its order to make sure last history results are on top
2025-05-04 08:37:15 +00:00
var historyItems = _history . Items . TakeLast ( Settings . MaxHistoryResultsToShowForHomePage ) . Reverse ( ) ;
2025-05-03 14:09:25 +00:00
2025-05-04 08:37:15 +00:00
var results = GetHistoryItems ( historyItems ) ;
2025-05-03 14:09:25 +00:00
2025-05-06 11:35:40 +00:00
if ( token . IsCancellationRequested ) return ;
2025-05-03 14:09:25 +00:00
2025-05-05 00:27:51 +00:00
App . API . LogDebug ( ClassName , $"Update results for history" ) ;
2025-05-04 03:05:23 +00:00
if ( ! _resultsUpdateChannelWriter . TryWrite ( new ResultsForUpdate ( results , _historyMetadata , query ,
2025-05-06 11:35:40 +00:00
token ) ) )
2025-05-03 14:09:25 +00:00
{
App . API . LogError ( ClassName , "Unable to add item to Result Update Queue" ) ;
}
}
2016-02-12 06:21:12 +00:00
}
2016-06-23 21:17:47 +00:00
2025-05-02 04:26:14 +00:00
private async Task < Query > ConstructQueryAsync ( string queryText , IEnumerable < CustomShortcutModel > customShortcuts ,
IEnumerable < BaseBuiltinShortcutModel > builtInShortcuts )
2022-06-04 05:30:33 +00:00
{
2022-11-08 04:35:28 +00:00
if ( string . IsNullOrWhiteSpace ( queryText ) )
{
2025-05-04 01:38:35 +00:00
return QueryBuilder . Build ( string . Empty , PluginManager . NonGlobalPlugins ) ;
2022-11-08 04:35:28 +00:00
}
2025-05-02 04:26:14 +00:00
var queryBuilder = new StringBuilder ( queryText ) ;
var queryBuilderTmp = new StringBuilder ( queryText ) ;
2022-06-04 05:30:33 +00:00
2024-03-27 02:56:10 +00:00
// Sorting order is important here, the reason is for matching longest shortcut by default
2024-02-24 18:10:25 +00:00
foreach ( var shortcut in customShortcuts . OrderByDescending ( x = > x . Key . Length ) )
2022-06-04 05:30:33 +00:00
{
2022-10-13 11:49:44 +00:00
if ( queryBuilder . Equals ( shortcut . Key ) )
2022-06-04 05:30:33 +00:00
{
2022-10-13 11:49:44 +00:00
queryBuilder . Replace ( shortcut . Key , shortcut . Expand ( ) ) ;
2022-06-04 05:30:33 +00:00
}
2022-10-13 11:49:44 +00:00
queryBuilder . Replace ( '@' + shortcut . Key , shortcut . Expand ( ) ) ;
}
2022-12-11 17:00:48 +00:00
2025-05-02 04:26:14 +00:00
// Applying builtin shortcuts
await BuildQueryAsync ( builtInShortcuts , queryBuilder , queryBuilderTmp ) ;
return QueryBuilder . Build ( queryBuilder . ToString ( ) . Trim ( ) , PluginManager . NonGlobalPlugins ) ;
}
private async Task BuildQueryAsync ( IEnumerable < BaseBuiltinShortcutModel > builtInShortcuts ,
StringBuilder queryBuilder , StringBuilder queryBuilderTmp )
{
var customExpanded = queryBuilder . ToString ( ) ;
2023-01-12 09:06:23 +00:00
2025-05-02 04:26:14 +00:00
var queryChanged = false ;
foreach ( var shortcut in builtInShortcuts )
2022-10-13 11:49:44 +00:00
{
2025-05-02 04:26:14 +00:00
try
2022-12-08 03:13:07 +00:00
{
2025-05-02 04:26:14 +00:00
if ( customExpanded . Contains ( shortcut . Key ) )
2022-12-22 13:22:56 +00:00
{
2025-05-02 04:26:14 +00:00
string expansion ;
if ( shortcut is BuiltinShortcutModel syncShortcut )
2023-01-12 09:06:23 +00:00
{
2025-05-02 04:26:14 +00:00
expansion = syncShortcut . Expand ( ) ;
2023-01-12 09:06:23 +00:00
}
2025-05-02 04:26:14 +00:00
else if ( shortcut is AsyncBuiltinShortcutModel asyncShortcut )
{
expansion = await asyncShortcut . ExpandAsync ( ) ;
}
else
{
continue ;
}
queryBuilder . Replace ( shortcut . Key , expansion ) ;
queryBuilderTmp . Replace ( shortcut . Key , expansion ) ;
queryChanged = true ;
2022-12-22 13:22:56 +00:00
}
2022-12-08 03:13:07 +00:00
}
2025-05-02 04:26:14 +00:00
catch ( Exception e )
{
App . API . LogException ( ClassName , $"Error when expanding shortcut {shortcut.Key}" , e ) ;
}
}
2022-11-07 18:00:34 +00:00
2025-05-02 07:06:20 +00:00
// Show expanded builtin shortcuts
2025-05-02 04:26:14 +00:00
if ( queryChanged )
{
2025-05-02 07:06:20 +00:00
// Use private field to avoid infinite recursion
2025-05-02 04:26:14 +00:00
_queryText = queryBuilderTmp . ToString ( ) ;
2025-05-02 07:21:17 +00:00
// When executing OnPropertyChanged, QueryTextBox_TextChanged1 and Query will be called
// So we need to ignore it so that we will not call Query again
_ignoredQueryText = _queryText ;
2025-05-02 04:26:14 +00:00
OnPropertyChanged ( nameof ( QueryText ) ) ;
}
2022-06-04 05:30:33 +00:00
}
2025-05-10 12:56:48 +00:00
/// <summary>
/// Determines whether the existing search results should be cleared based on the current query and the previous query type.
/// This is needed because of the design that treats plugins with action keywords and global action keywords separately. Results are gathered
/// either from plugins with matching action keywords or global action keyword, but not both. So when the current results are from plugins
/// with a matching action keyword and a new result set comes from a new query with the global action keyword, the existing results need to be cleared,
/// and vice versa. The same applies to home page query results.
///
/// There is no need to clear results from global action keyword if a new set of results comes along that is also from global action keywords.
/// This is because the removal of obsolete results is handled in ResultsViewModel.NewResults(ICollection<ResultsForUpdate>).
/// </summary>
/// <param name="query">The current query.</param>
/// <param name="currentIsHomeQuery">A flag indicating if the current query is a home query.</param>
/// <returns>True if the existing results should be cleared, false otherwise.</returns>
private bool ShouldClearExistingResults ( Query query , bool currentIsHomeQuery )
2019-12-13 22:17:05 +00:00
{
2025-05-10 12:56:48 +00:00
// If previous or current results are from home query, we need to clear them
if ( _previousIsHomeQuery | | currentIsHomeQuery )
2025-05-05 09:32:43 +00:00
{
2025-05-10 12:56:48 +00:00
App . API . LogDebug ( ClassName , $"Cleared old results" ) ;
return true ;
2025-05-05 09:32:43 +00:00
}
2025-05-10 12:56:48 +00:00
// If the last and current query are not home query type, we need to check the action keyword
if ( _lastQuery ? . ActionKeyword ! = query ? . ActionKeyword )
2019-12-13 22:17:05 +00:00
{
2025-05-10 12:56:48 +00:00
App . API . LogDebug ( ClassName , $"Cleared old results" ) ;
return true ;
2019-12-13 22:17:05 +00:00
}
2025-05-10 12:56:48 +00:00
return false ;
2019-12-13 22:17:05 +00:00
}
2016-04-25 20:25:39 +00:00
private Result ContextMenuTopMost ( Result result )
2016-03-26 01:20:42 +00:00
{
2016-04-25 20:25:39 +00:00
Result menu ;
2016-03-28 00:09:40 +00:00
if ( _topMostRecord . IsTopMost ( result ) )
2016-03-26 01:20:42 +00:00
{
2016-04-27 01:15:53 +00:00
menu = new Result
2016-03-26 01:20:42 +00:00
{
2025-03-17 02:07:40 +00:00
Title = App . API . GetTranslation ( "cancelTopMostInThisQuery" ) ,
2016-04-27 01:15:53 +00:00
IcoPath = "Images\\down.png" ,
2016-06-22 23:26:57 +00:00
PluginDirectory = Constant . ProgramDirectory ,
2016-03-26 01:20:42 +00:00
Action = _ = >
{
2016-03-28 00:09:40 +00:00
_topMostRecord . Remove ( result ) ;
2025-03-17 02:07:40 +00:00
App . API . ShowMsg ( App . API . GetTranslation ( "success" ) ) ;
2025-01-26 12:07:40 +00:00
App . API . ReQuery ( ) ;
2016-03-26 01:20:42 +00:00
return false ;
2025-05-05 10:53:40 +00:00
} ,
2025-05-11 08:53:44 +00:00
Glyph = new GlyphInfo ( FontFamily : "/Resources/#Segoe Fluent Icons" , Glyph : "\uE74B" ) ,
OriginQuery = result . OriginQuery
2016-03-26 01:20:42 +00:00
} ;
}
else
{
2016-04-27 01:15:53 +00:00
menu = new Result
2016-03-26 01:20:42 +00:00
{
2025-03-17 02:07:40 +00:00
Title = App . API . GetTranslation ( "setAsTopMostInThisQuery" ) ,
2016-04-27 01:15:53 +00:00
IcoPath = "Images\\up.png" ,
2016-06-22 23:26:57 +00:00
PluginDirectory = Constant . ProgramDirectory ,
2016-03-26 01:20:42 +00:00
Action = _ = >
{
2016-03-28 00:09:40 +00:00
_topMostRecord . AddOrUpdate ( result ) ;
2025-03-17 02:07:40 +00:00
App . API . ShowMsg ( App . API . GetTranslation ( "success" ) ) ;
2025-01-26 12:07:40 +00:00
App . API . ReQuery ( ) ;
2016-03-26 01:20:42 +00:00
return false ;
2025-05-05 10:53:40 +00:00
} ,
2025-05-11 08:53:44 +00:00
Glyph = new GlyphInfo ( FontFamily : "/Resources/#Segoe Fluent Icons" , Glyph : "\uE74A" ) ,
OriginQuery = result . OriginQuery
2016-03-26 01:20:42 +00:00
} ;
}
2021-01-09 04:01:08 +00:00
2016-04-25 20:25:39 +00:00
return menu ;
}
2025-05-11 08:53:44 +00:00
private static Result ContextMenuPluginInfo ( Result result )
2016-04-25 20:25:39 +00:00
{
2025-05-11 08:53:44 +00:00
var id = result . PluginID ;
2016-04-25 20:25:39 +00:00
var metadata = PluginManager . GetPluginForId ( id ) . Metadata ;
2025-03-17 02:07:40 +00:00
var translator = App . API ;
2016-04-25 20:25:39 +00:00
var author = translator . GetTranslation ( "author" ) ;
var website = translator . GetTranslation ( "website" ) ;
var version = translator . GetTranslation ( "version" ) ;
var plugin = translator . GetTranslation ( "plugin" ) ;
var title = $"{plugin}: {metadata.Name}" ;
var icon = metadata . IcoPath ;
2021-12-06 10:10:34 +00:00
var subtitle = $"{author} {metadata.Author}" ;
2016-04-25 20:25:39 +00:00
2016-04-27 01:15:53 +00:00
var menu = new Result
2016-04-25 20:25:39 +00:00
{
2016-04-27 01:15:53 +00:00
Title = title ,
IcoPath = icon ,
SubTitle = subtitle ,
2016-04-25 20:25:39 +00:00
PluginDirectory = metadata . PluginDirectory ,
2022-01-23 21:16:17 +00:00
Action = _ = >
{
App . API . OpenUrl ( metadata . Website ) ;
return true ;
2025-05-11 08:53:44 +00:00
} ,
OriginQuery = result . OriginQuery
2016-04-25 20:25:39 +00:00
} ;
return menu ;
2016-03-26 01:20:42 +00:00
}
2016-05-09 22:35:20 +00:00
2025-03-26 02:07:35 +00:00
internal bool QueryResultsSelected ( )
2016-06-22 23:26:57 +00:00
{
var selected = SelectedResults = = Results ;
return selected ;
}
2016-06-23 21:17:47 +00:00
private bool ContextMenuSelected ( )
{
var selected = SelectedResults = = ContextMenu ;
return selected ;
}
private bool HistorySelected ( )
{
var selected = SelectedResults = = History ;
return selected ;
}
2021-01-09 04:01:08 +00:00
2022-12-23 03:46:12 +00:00
#endregion
2016-05-09 22:35:20 +00:00
#region Hotkey
2020-05-03 17:17:16 +00:00
2021-11-05 17:31:05 +00:00
public void ToggleFlowLauncher ( )
2016-05-09 22:35:20 +00:00
{
2021-11-27 02:38:47 +00:00
if ( ! MainWindowVisibilityStatus )
2016-05-09 22:35:20 +00:00
{
2021-11-14 08:45:56 +00:00
Show ( ) ;
2016-05-09 22:35:20 +00:00
}
else
{
2021-10-31 19:35:58 +00:00
Hide ( ) ;
2016-05-09 22:35:20 +00:00
}
}
2021-11-27 02:38:47 +00:00
2025-03-26 02:30:47 +00:00
/// <summary>
/// Checks if Flow Launcher should ignore any hotkeys
/// </summary>
public bool ShouldIgnoreHotkeys ( )
{
return Settings . IgnoreHotkeysOnFullscreen & & Win32Helper . IsForegroundWindowFullscreen ( ) | | GameModeStatus ;
}
#endregion
#region Public Methods
2025-03-27 05:22:23 +00:00
#pragma warning disable VSTHRD100 // Avoid async void methods
2025-03-28 04:00:23 +00:00
public void Show ( )
2021-11-14 08:45:56 +00:00
{
2025-05-02 04:26:14 +00:00
// When application is exiting, we should not show the main window
if ( App . Exiting ) return ;
2025-03-30 07:01:41 +00:00
// When application is exiting, the Application.Current will be null
2025-03-30 06:59:30 +00:00
Application . Current ? . Dispatcher . Invoke ( ( ) = >
2025-03-28 08:13:02 +00:00
{
2025-03-30 07:01:41 +00:00
// When application is exiting, the Application.Current will be null
2025-03-28 13:39:25 +00:00
if ( Application . Current ? . MainWindow is MainWindow mainWindow )
2025-03-28 08:13:02 +00:00
{
// 📌 Remove DWM Cloak (Make the window visible normally)
Win32Helper . DWMSetCloakForWindow ( mainWindow , false ) ;
// Set clock and search icon opacity
var opacity = Settings . UseAnimation ? 0.0 : 1.0 ;
ClockPanelOpacity = opacity ;
SearchIconOpacity = opacity ;
// Set clock and search icon visibility
ClockPanelVisibility = string . IsNullOrEmpty ( QueryText ) ? Visibility . Visible : Visibility . Collapsed ;
if ( PluginIconSource ! = null )
{
SearchIconOpacity = 0.0 ;
}
else
{
SearchIconVisibility = Visibility . Visible ;
}
}
} , DispatcherPriority . Render ) ;
2021-11-14 08:45:56 +00:00
2025-03-27 05:22:23 +00:00
// Update WPF properties
MainWindowVisibility = Visibility . Visible ;
MainWindowVisibilityStatus = true ;
VisibilityChanged ? . Invoke ( this , new VisibilityChangedEventArgs { IsVisible = true } ) ;
2025-03-22 10:23:34 +00:00
2025-03-28 13:39:25 +00:00
// Switch keyboard layout
2025-03-27 05:22:23 +00:00
if ( StartWithEnglishMode )
{
Win32Helper . SwitchToEnglishKeyboardLayout ( true ) ;
}
2025-03-19 10:22:43 +00:00
}
2025-03-18 15:09:02 +00:00
2025-03-06 05:57:02 +00:00
public async void Hide ( )
2021-10-14 00:00:58 +00:00
{
2025-03-06 05:14:25 +00:00
lastHistoryIndex = 1 ;
2025-03-06 05:57:02 +00:00
2024-05-28 11:41:59 +00:00
if ( ExternalPreviewVisible )
2025-03-17 02:07:40 +00:00
{
2025-03-26 02:07:35 +00:00
await CloseExternalPreviewAsync ( ) ;
2025-03-17 02:07:40 +00:00
}
2023-04-22 09:16:02 +00:00
2025-05-02 06:37:10 +00:00
BackToQueryResults ( ) ;
2024-03-20 05:47:14 +00:00
2025-03-06 05:57:02 +00:00
switch ( Settings . LastQueryMode )
{
2025-03-26 16:14:13 +00:00
case LastQueryMode . Empty :
2025-03-27 05:22:23 +00:00
await ChangeQueryTextAsync ( string . Empty ) ;
2025-03-26 16:14:13 +00:00
break ;
2025-03-06 05:57:02 +00:00
case LastQueryMode . Preserved :
case LastQueryMode . Selected :
2025-03-27 05:22:23 +00:00
LastQuerySelected = Settings . LastQueryMode = = LastQueryMode . Preserved ;
2025-03-06 05:57:02 +00:00
break ;
case LastQueryMode . ActionKeywordPreserved :
case LastQueryMode . ActionKeywordSelected :
2025-05-03 08:50:50 +00:00
var newQuery = _lastQuery ? . ActionKeyword ;
2025-03-27 05:22:23 +00:00
2025-03-06 05:57:02 +00:00
if ( ! string . IsNullOrEmpty ( newQuery ) )
newQuery + = " " ;
2025-03-27 05:22:23 +00:00
await ChangeQueryTextAsync ( newQuery ) ;
2025-03-06 04:33:36 +00:00
2025-03-06 05:57:02 +00:00
if ( Settings . LastQueryMode = = LastQueryMode . ActionKeywordSelected )
2025-03-06 04:33:36 +00:00
LastQuerySelected = false ;
2025-03-06 05:57:02 +00:00
break ;
}
2021-11-27 02:38:47 +00:00
2025-03-30 07:01:41 +00:00
// When application is exiting, the Application.Current will be null
2025-03-30 06:59:30 +00:00
Application . Current ? . Dispatcher . Invoke ( ( ) = >
2025-03-28 08:13:02 +00:00
{
2025-03-30 07:01:41 +00:00
// When application is exiting, the Application.Current will be null
2025-03-28 13:39:25 +00:00
if ( Application . Current ? . MainWindow is MainWindow mainWindow )
2025-03-28 08:13:02 +00:00
{
// Set clock and search icon opacity
var opacity = Settings . UseAnimation ? 0.0 : 1.0 ;
ClockPanelOpacity = opacity ;
SearchIconOpacity = opacity ;
// Set clock and search icon visibility
ClockPanelVisibility = Visibility . Hidden ;
SearchIconVisibility = Visibility . Hidden ;
// Force UI update
mainWindow . ClockPanel . UpdateLayout ( ) ;
mainWindow . SearchIcon . UpdateLayout ( ) ;
// 📌 Apply DWM Cloak (Completely hide the window)
Win32Helper . DWMSetCloakForWindow ( mainWindow , true ) ;
}
} , DispatcherPriority . Render ) ;
2025-03-28 13:39:25 +00:00
// Switch keyboard layout
2025-03-22 10:23:34 +00:00
if ( StartWithEnglishMode )
{
Win32Helper . RestorePreviousKeyboardLayout ( ) ;
}
2025-03-28 08:13:02 +00:00
// Delay for a while to make sure clock will not flicker
await Task . Delay ( 50 ) ;
2025-03-18 15:09:02 +00:00
// Update WPF properties
2025-02-24 06:31:41 +00:00
MainWindowVisibilityStatus = false ;
2025-03-08 07:12:57 +00:00
MainWindowVisibility = Visibility . Collapsed ;
2023-07-03 21:42:41 +00:00
VisibilityChanged ? . Invoke ( this , new VisibilityChangedEventArgs { IsVisible = false } ) ;
2016-05-09 22:35:20 +00:00
}
2025-03-17 02:07:40 +00:00
#pragma warning restore VSTHRD100 // Avoid async void methods
2021-10-14 00:00:58 +00:00
/// <summary>
2025-03-26 02:30:47 +00:00
/// Save history, user selected records and top most records
2021-10-14 00:00:58 +00:00
/// </summary>
2016-05-05 15:08:44 +00:00
public void Save ( )
{
2021-04-19 05:06:03 +00:00
_historyItemsStorage . Save ( ) ;
_userSelectedRecordStorage . Save ( ) ;
_topMostRecordStorage . Save ( ) ;
2016-05-05 15:08:44 +00:00
}
2020-11-11 11:06:05 +00:00
/// <summary>
2025-03-30 10:03:43 +00:00
/// To avoid deadlock, this method should not be called from main thread
2020-11-11 11:06:05 +00:00
/// </summary>
2024-03-20 05:47:14 +00:00
public void UpdateResultView ( ICollection < ResultsForUpdate > resultsForUpdates )
2020-11-11 10:36:01 +00:00
{
2020-11-16 00:32:11 +00:00
if ( ! resultsForUpdates . Any ( ) )
return ;
2020-12-04 13:53:34 +00:00
CancellationToken token ;
try
{
2020-12-05 07:48:36 +00:00
// Don't know why sometimes even resultsForUpdates is empty, the method won't return;
token = resultsForUpdates . Select ( r = > r . Token ) . Distinct ( ) . SingleOrDefault ( ) ;
2020-12-04 13:53:34 +00:00
}
#if DEBUG
catch
{
throw new ArgumentException ( "Unacceptable token" ) ;
}
#else
catch
{
2020-12-04 14:30:14 +00:00
token = default ;
2020-12-04 13:53:34 +00:00
}
#endif
2020-11-16 00:32:11 +00:00
2021-01-13 01:43:15 +00:00
foreach ( var metaResults in resultsForUpdates )
2020-11-11 10:36:01 +00:00
{
2021-01-13 01:43:15 +00:00
foreach ( var result in metaResults . Results )
2020-11-11 10:36:01 +00:00
{
2021-01-13 01:43:15 +00:00
if ( _topMostRecord . IsTopMost ( result ) )
{
2025-02-22 07:51:36 +00:00
result . Score = Result . MaxScore ;
2021-01-13 01:43:15 +00:00
}
2025-02-20 10:06:15 +00:00
else
2021-01-13 01:43:15 +00:00
{
2021-01-17 10:05:31 +00:00
var priorityScore = metaResults . Metadata . Priority * 150 ;
2025-02-22 07:59:38 +00:00
if ( result . AddSelectedCount )
{
if ( ( long ) result . Score + _userSelectedRecord . GetSelectedCount ( result ) + priorityScore > Result . MaxScore )
{
result . Score = Result . MaxScore ;
}
else
{
result . Score + = _userSelectedRecord . GetSelectedCount ( result ) + priorityScore ;
}
}
else
{
if ( ( long ) result . Score + priorityScore > Result . MaxScore )
{
result . Score = Result . MaxScore ;
}
else
{
result . Score + = priorityScore ;
}
}
2021-01-13 01:43:15 +00:00
}
2020-11-11 10:36:01 +00:00
}
}
2024-03-20 05:47:14 +00:00
// it should be the same for all results
bool reSelect = resultsForUpdates . First ( ) . ReSelectFirstResult ;
Results . AddResults ( resultsForUpdates , token , reSelect ) ;
2020-11-11 10:36:01 +00:00
}
2016-02-18 11:30:36 +00:00
#endregion
2025-03-21 12:55:57 +00:00
#region IDisposable
private bool _disposed = false ;
protected virtual void Dispose ( bool disposing )
{
if ( ! _disposed )
{
if ( disposing )
{
2025-05-06 06:14:10 +00:00
_updateSource ? . Dispose ( ) ;
2025-03-22 05:34:03 +00:00
_resultsUpdateChannelWriter ? . Complete ( ) ;
2025-03-26 02:39:17 +00:00
if ( _resultsViewUpdateTask ? . IsCompleted = = true )
{
_resultsViewUpdateTask . Dispose ( ) ;
}
2025-03-21 12:55:57 +00:00
_disposed = true ;
}
}
}
public void Dispose ( )
{
2025-03-22 05:26:12 +00:00
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
2025-03-21 12:55:57 +00:00
Dispose ( disposing : true ) ;
GC . SuppressFinalize ( this ) ;
}
#endregion
2016-02-12 09:20:46 +00:00
}
2022-08-08 04:31:38 +00:00
}