diff --git a/Flow.Launcher.Core/Plugin/QueryBuilder.cs b/Flow.Launcher.Core/Plugin/QueryBuilder.cs
index 3dc7877acc2..2d9acc45b33 100644
--- a/Flow.Launcher.Core/Plugin/QueryBuilder.cs
+++ b/Flow.Launcher.Core/Plugin/QueryBuilder.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections.Generic;
 using Flow.Launcher.Plugin;
 
@@ -6,12 +6,13 @@ namespace Flow.Launcher.Core.Plugin
 {
     public static class QueryBuilder
     {
-        public static Query Build(string text, Dictionary<string, PluginPair> nonGlobalPlugins)
+        public static Query Build(string input, string text, Dictionary<string, PluginPair> nonGlobalPlugins)
         {
             // replace multiple white spaces with one white space
             var terms = text.Split(Query.TermSeparator, StringSplitOptions.RemoveEmptyEntries);
             if (terms.Length == 0)
-            { // nothing was typed
+            {
+                // nothing was typed
                 return null;
             }
 
@@ -21,13 +22,15 @@ public static Query Build(string text, Dictionary<string, PluginPair> nonGlobalP
             string[] searchTerms;
 
             if (nonGlobalPlugins.TryGetValue(possibleActionKeyword, out var pluginPair) && !pluginPair.Metadata.Disabled)
-            { // use non global plugin for query
+            {
+                // use non global plugin for query
                 actionKeyword = possibleActionKeyword;
                 search = terms.Length > 1 ? rawQuery[(actionKeyword.Length + 1)..].TrimStart() : string.Empty;
                 searchTerms = terms[1..];
             }
             else
-            { // non action keyword
+            {
+                // non action keyword
                 actionKeyword = string.Empty;
                 search = rawQuery.TrimStart();
                 searchTerms = terms;
@@ -36,10 +39,11 @@ public static Query Build(string text, Dictionary<string, PluginPair> nonGlobalP
             return new Query ()
             {
                 Search = search,
+                Input = input,
                 RawQuery = rawQuery,
                 SearchTerms = searchTerms,
                 ActionKeyword = actionKeyword
             };
         }
     }
-}
\ No newline at end of file
+}
diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs
index a46932c6af4..059359694b4 100644
--- a/Flow.Launcher.Core/Resource/Theme.cs
+++ b/Flow.Launcher.Core/Resource/Theme.cs
@@ -76,7 +76,7 @@ public Theme(IPublicAPI publicAPI, Settings settings)
             {
                 _api.LogError(ClassName, "Current theme resource not found. Initializing with default theme.");
                 _oldTheme = Constant.DefaultTheme;
-            };
+            }
         }
 
         #endregion
@@ -126,7 +126,7 @@ public void UpdateFonts()
                 // Load a ResourceDictionary for the specified theme.
                 var themeName = _settings.Theme;
                 var dict = GetThemeResourceDictionary(themeName);
-                
+
                 // Apply font settings to the theme resource.
                 ApplyFontSettings(dict);
                 UpdateResourceDictionary(dict);
@@ -152,11 +152,11 @@ private void ApplyFontSettings(ResourceDictionary dict)
                 var fontStyle = FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.QueryBoxFontStyle);
                 var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.QueryBoxFontWeight);
                 var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.QueryBoxFontStretch);
-                
+
                 SetFontProperties(queryBoxStyle, fontFamily, fontStyle, fontWeight, fontStretch, true);
                 SetFontProperties(querySuggestionBoxStyle, fontFamily, fontStyle, fontWeight, fontStretch, false);
             }
-            
+
             if (dict["ItemTitleStyle"] is Style resultItemStyle &&
                 dict["ItemTitleSelectedStyle"] is Style resultItemSelectedStyle &&
                 dict["ItemHotkeyStyle"] is Style resultHotkeyItemStyle &&
@@ -172,7 +172,7 @@ private void ApplyFontSettings(ResourceDictionary dict)
                 SetFontProperties(resultHotkeyItemStyle, fontFamily, fontStyle, fontWeight, fontStretch, false);
                 SetFontProperties(resultHotkeyItemSelectedStyle, fontFamily, fontStyle, fontWeight, fontStretch, false);
             }
-            
+
             if (dict["ItemSubTitleStyle"] is Style resultSubItemStyle &&
                 dict["ItemSubTitleSelectedStyle"] is Style resultSubItemSelectedStyle)
             {
@@ -197,7 +197,7 @@ private static void SetFontProperties(Style style, FontFamily fontFamily, FontSt
                 //  First, find the setters to remove and store them in a list  
                 var settersToRemove = style.Setters
                     .OfType<Setter>()
-                    .Where(setter => 
+                    .Where(setter =>
                         setter.Property == Control.FontFamilyProperty ||
                         setter.Property == Control.FontStyleProperty ||
                         setter.Property == Control.FontWeightProperty ||
@@ -227,18 +227,18 @@ private static void SetFontProperties(Style style, FontFamily fontFamily, FontSt
             {
                 var settersToRemove = style.Setters
                     .OfType<Setter>()
-                    .Where(setter => 
+                    .Where(setter =>
                         setter.Property == TextBlock.FontFamilyProperty ||
                         setter.Property == TextBlock.FontStyleProperty ||
                         setter.Property == TextBlock.FontWeightProperty ||
                         setter.Property == TextBlock.FontStretchProperty)
                     .ToList();
-                
+
                 foreach (var setter in settersToRemove)
                 {
                     style.Setters.Remove(setter);
                 }
-                
+
                 style.Setters.Add(new Setter(TextBlock.FontFamilyProperty, fontFamily));
                 style.Setters.Add(new Setter(TextBlock.FontStyleProperty, fontStyle));
                 style.Setters.Add(new Setter(TextBlock.FontWeightProperty, fontWeight));
@@ -421,7 +421,7 @@ public bool ChangeTheme(string theme = null)
 
                 // Retrieve theme resource – always use the resource with font settings applied.
                 var resourceDict = GetResourceDictionary(theme);
-                
+
                 UpdateResourceDictionary(resourceDict);
 
                 _settings.Theme = theme;
diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt
index 18b20602213..0e50420b0e0 100644
--- a/Flow.Launcher.Infrastructure/NativeMethods.txt
+++ b/Flow.Launcher.Infrastructure/NativeMethods.txt
@@ -43,6 +43,9 @@ MONITORINFOEXW
 WM_ENTERSIZEMOVE
 WM_EXITSIZEMOVE
 
+OleInitialize
+OleUninitialize
+
 GetKeyboardLayout
 GetWindowThreadProcessId
 ActivateKeyboardLayout
@@ -53,4 +56,4 @@ INPUTLANGCHANGE_FORWARD
 LOCALE_TRANSIENT_KEYBOARD1
 LOCALE_TRANSIENT_KEYBOARD2
 LOCALE_TRANSIENT_KEYBOARD3
-LOCALE_TRANSIENT_KEYBOARD4
\ No newline at end of file
+LOCALE_TRANSIENT_KEYBOARD4
diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs
index 71020369a60..2d15b54c5be 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs
@@ -1,15 +1,15 @@
 using System;
 using System.Text.Json.Serialization;
+using System.Threading.Tasks;
 
 namespace Flow.Launcher.Infrastructure.UserSettings
 {
+    #region Base
+
     public abstract class ShortcutBaseModel
     {
         public string Key { get; set; }
 
-        [JsonIgnore]
-        public Func<string> Expand { get; set; } = () => { return ""; };
-
         public override bool Equals(object obj)
         {
             return obj is ShortcutBaseModel other &&
@@ -22,16 +22,14 @@ public override int GetHashCode()
         }
     }
 
-    public class CustomShortcutModel : ShortcutBaseModel
+    public class BaseCustomShortcutModel : ShortcutBaseModel
     {
         public string Value { get; set; }
 
-        [JsonConstructorAttribute]
-        public CustomShortcutModel(string key, string value)
+        public BaseCustomShortcutModel(string key, string value)
         {
             Key = key;
             Value = value;
-            Expand = () => { return Value; };
         }
 
         public void Deconstruct(out string key, out string value)
@@ -40,26 +38,69 @@ public void Deconstruct(out string key, out string value)
             value = Value;
         }
 
-        public static implicit operator (string Key, string Value)(CustomShortcutModel shortcut)
+        public static implicit operator (string Key, string Value)(BaseCustomShortcutModel shortcut)
         {
             return (shortcut.Key, shortcut.Value);
         }
 
-        public static implicit operator CustomShortcutModel((string Key, string Value) shortcut)
+        public static implicit operator BaseCustomShortcutModel((string Key, string Value) shortcut)
         {
-            return new CustomShortcutModel(shortcut.Key, shortcut.Value);
+            return new BaseCustomShortcutModel(shortcut.Key, shortcut.Value);
         }
     }
 
-    public class BuiltinShortcutModel : ShortcutBaseModel
+    public class BaseBuiltinShortcutModel : ShortcutBaseModel
     {
         public string Description { get; set; }
 
-        public BuiltinShortcutModel(string key, string description, Func<string> expand)
+        public BaseBuiltinShortcutModel(string key, string description)
         {
             Key = key;
             Description = description;
-            Expand = expand ?? (() => { return ""; });
         }
     }
+
+    #endregion
+
+    #region Custom Shortcut
+
+    public class CustomShortcutModel : BaseCustomShortcutModel
+    {
+        [JsonIgnore]
+        public Func<string> Expand { get; set; } = () => { return string.Empty; };
+
+        [JsonConstructor]
+        public CustomShortcutModel(string key, string value) : base(key, value)
+        {
+            Expand = () => { return Value; };
+        }
+    }
+
+    #endregion
+
+    #region Builtin Shortcut
+
+    public class BuiltinShortcutModel : BaseBuiltinShortcutModel
+    {
+        [JsonIgnore]
+        public Func<string> Expand { get; set; } = () => { return string.Empty; };
+
+        public BuiltinShortcutModel(string key, string description, Func<string> expand) : base(key, description)
+        {
+            Expand = expand ?? (() => { return string.Empty; });
+        }
+    }
+
+    public class AsyncBuiltinShortcutModel : BaseBuiltinShortcutModel
+    {
+        [JsonIgnore]
+        public Func<Task<string>> ExpandAsync { get; set; } = () => { return Task.FromResult(string.Empty); };
+
+        public AsyncBuiltinShortcutModel(string key, string description, Func<Task<string>> expandAsync) : base(key, description)
+        {
+            ExpandAsync = expandAsync ?? (() => { return Task.FromResult(string.Empty); });
+        }
+    }
+
+    #endregion
 }
diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
index 8500e7aa444..fdc3333b020 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -295,9 +295,9 @@ public bool KeepMaxResults
         public ObservableCollection<CustomShortcutModel> CustomShortcuts { get; set; } = new ObservableCollection<CustomShortcutModel>();
 
         [JsonIgnore]
-        public ObservableCollection<BuiltinShortcutModel> BuiltinShortcuts { get; set; } = new()
+        public ObservableCollection<BaseBuiltinShortcutModel> BuiltinShortcuts { get; set; } = new()
         {
-            new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText),
+            new AsyncBuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", () => Win32Helper.StartSTATaskAsync(Clipboard.GetText)),
             new BuiltinShortcutModel("{active_explorer_path}", "shortcut_active_explorer_path", FileExplorerHelper.GetActiveExplorerPath)
         };
 
diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs
index 6a5af41df28..0ece7d63a30 100644
--- a/Flow.Launcher.Infrastructure/Win32Helper.cs
+++ b/Flow.Launcher.Infrastructure/Win32Helper.cs
@@ -5,6 +5,8 @@
 using System.Globalization;
 using System.Linq;
 using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Interop;
 using System.Windows.Markup;
@@ -337,6 +339,78 @@ internal static HWND GetWindowHandle(Window window, bool ensure = false)
 
         #endregion
 
+        #region STA Thread
+
+        /*
+        Inspired by https://github.com/files-community/Files code on STA Thread handling.
+        */
+
+        public static Task StartSTATaskAsync(Action action)
+        {
+            var taskCompletionSource = new TaskCompletionSource();
+            Thread thread = new(() =>
+            {
+                PInvoke.OleInitialize();
+
+                try
+                {
+                    action();
+                    taskCompletionSource.SetResult();
+                }
+                catch (System.Exception ex)
+                {
+                    taskCompletionSource.SetException(ex);
+                }
+                finally
+                {
+                    PInvoke.OleUninitialize();
+                }
+            })
+            {
+                IsBackground = true,
+                Priority = ThreadPriority.Normal
+            };
+
+            thread.SetApartmentState(ApartmentState.STA);
+            thread.Start();
+
+            return taskCompletionSource.Task;
+        }
+
+        public static Task<T> StartSTATaskAsync<T>(Func<T> func)
+        {
+            var taskCompletionSource = new TaskCompletionSource<T>();
+
+            Thread thread = new(() =>
+            {
+                PInvoke.OleInitialize();
+
+                try
+                {
+                    taskCompletionSource.SetResult(func());
+                }
+                catch (System.Exception ex)
+                {
+                    taskCompletionSource.SetException(ex);
+                }
+                finally
+                {
+                    PInvoke.OleUninitialize();
+                }
+            })
+            {
+                IsBackground = true,
+                Priority = ThreadPriority.Normal
+            };
+
+            thread.SetApartmentState(ApartmentState.STA);
+            thread.Start();
+
+            return taskCompletionSource.Task;
+        }
+
+        #endregion
+
         #region Keyboard Layout
 
         private const string UserProfileRegistryPath = @"Control Panel\International\User Profile";
diff --git a/Flow.Launcher.Plugin/Query.cs b/Flow.Launcher.Plugin/Query.cs
index 913dc31ae65..24f4b597c02 100644
--- a/Flow.Launcher.Plugin/Query.cs
+++ b/Flow.Launcher.Plugin/Query.cs
@@ -8,7 +8,14 @@ namespace Flow.Launcher.Plugin
     public class Query
     {
         /// <summary>
-        /// Raw query, this includes action keyword if it has
+        /// Input text in query box.
+        /// We didn't recommend use this property directly. You should always use Search property.
+        /// </summary>
+        public string Input { get; internal init; }
+
+        /// <summary>
+        /// Raw query, this includes action keyword if it has.
+        /// It has handled buildin custom query shortkeys and build-in shortcuts, and it trims the whitespace.
         /// We didn't recommend use this property directly. You should always use Search property.
         /// </summary>
         public string RawQuery { get; internal init; }
diff --git a/Flow.Launcher.Test/QueryBuilderTest.cs b/Flow.Launcher.Test/QueryBuilderTest.cs
index c8ac17748da..3912f26a7d3 100644
--- a/Flow.Launcher.Test/QueryBuilderTest.cs
+++ b/Flow.Launcher.Test/QueryBuilderTest.cs
@@ -16,7 +16,7 @@ public void ExclusivePluginQueryTest()
                 {">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List<string> {">"}}}}
             };
 
-            Query q = QueryBuilder.Build(">   ping    google.com   -n 20  -6", nonGlobalPlugins);
+            Query q = QueryBuilder.Build(">   ping    google.com   -n 20  -6", ">   ping    google.com   -n 20  -6", nonGlobalPlugins);
 
             ClassicAssert.AreEqual(">   ping    google.com   -n 20  -6", q.RawQuery);
             ClassicAssert.AreEqual("ping    google.com   -n 20  -6", q.Search, "Search should not start with the ActionKeyword.");
@@ -39,7 +39,7 @@ public void ExclusivePluginQueryIgnoreDisabledTest()
                 {">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List<string> {">"}, Disabled = true}}}
             };
 
-            Query q = QueryBuilder.Build(">   ping    google.com   -n 20  -6", nonGlobalPlugins);
+            Query q = QueryBuilder.Build(">   ping    google.com   -n 20  -6", ">   ping    google.com   -n 20  -6", nonGlobalPlugins);
 
             ClassicAssert.AreEqual(">   ping    google.com   -n 20  -6", q.Search);
             ClassicAssert.AreEqual(q.Search, q.RawQuery, "RawQuery should be equal to Search.");
@@ -51,7 +51,7 @@ public void ExclusivePluginQueryIgnoreDisabledTest()
         [Test]
         public void GenericPluginQueryTest()
         {
-            Query q = QueryBuilder.Build("file.txt file2 file3", new Dictionary<string, PluginPair>());
+            Query q = QueryBuilder.Build("file.txt file2 file3", "file.txt file2 file3", new Dictionary<string, PluginPair>());
 
             ClassicAssert.AreEqual("file.txt file2 file3", q.Search);
             ClassicAssert.AreEqual("", q.ActionKeyword);
diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs
index 87698a54571..cb15c9d1cb3 100644
--- a/Flow.Launcher/App.xaml.cs
+++ b/Flow.Launcher/App.xaml.cs
@@ -21,6 +21,7 @@
 using Flow.Launcher.ViewModel;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
+using Microsoft.VisualStudio.Threading;
 
 namespace Flow.Launcher
 {
@@ -29,6 +30,8 @@ public partial class App : IDisposable, ISingleInstanceApp
         #region Public Properties
 
         public static IPublicAPI API { get; private set; }
+        public static JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());
+        public static bool Exitting => _mainWindow.CanClose;
 
         #endregion
 
@@ -37,7 +40,7 @@ public partial class App : IDisposable, ISingleInstanceApp
         private static readonly string ClassName = nameof(App);
 
         private static bool _disposed;
-        private MainWindow _mainWindow;
+        private static MainWindow _mainWindow;
         private readonly MainViewModel _mainVM;
         private readonly Settings _settings;
 
@@ -149,7 +152,7 @@ await API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>
                 Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
 
                 API.LogInfo(ClassName, "Begin Flow Launcher startup ----------------------------------------------------");
-                API.LogInfo(ClassName, "Runtime info:{ErrorReporting.RuntimeInfo()}");
+                API.LogInfo(ClassName, $"Runtime info:{ErrorReporting.RuntimeInfo()}");
 
                 RegisterAppDomainExceptions();
                 RegisterDispatcherUnhandledException();
@@ -176,11 +179,12 @@ await API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>
 
                 _mainWindow = new MainWindow();
 
-                API.LogInfo(ClassName, "Dependencies Info:{ErrorReporting.DependenciesInfo()}");
-
                 Current.MainWindow = _mainWindow;
                 Current.MainWindow.Title = Constant.FlowLauncher;
 
+                // Initialize hotkey mapper instantly after main window is created because it will steal focus from main window
+                HotKeyMapper.Initialize();
+
                 // main windows needs initialized before theme change because of blur settings
                 Ioc.Default.GetRequiredService<Theme>().ChangeTheme();
 
@@ -218,6 +222,7 @@ private void AutoStartup()
             }
         }
 
+        [Conditional("RELEASE")]
         private void AutoUpdates()
         {
             _ = Task.Run(async () =>
@@ -275,13 +280,12 @@ private void RegisterDispatcherUnhandledException()
         [Conditional("RELEASE")]
         private static void RegisterAppDomainExceptions()
         {
-            AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
+            AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledException;
         }
 
         /// <summary>
-        /// let exception throw as normal is better for Debug
+        /// let exception throw as normal for Debug and Release
         /// </summary>
-        [Conditional("RELEASE")]
         private static void RegisterTaskSchedulerUnhandledException()
         {
             TaskScheduler.UnobservedTaskException += ErrorReporting.TaskSchedulerUnobservedTaskException;
diff --git a/Flow.Launcher/Helper/ErrorReporting.cs b/Flow.Launcher/Helper/ErrorReporting.cs
index b1ddba7179a..aa810ba651a 100644
--- a/Flow.Launcher/Helper/ErrorReporting.cs
+++ b/Flow.Launcher/Helper/ErrorReporting.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Runtime.CompilerServices;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Threading;
@@ -10,33 +11,34 @@ namespace Flow.Launcher.Helper;
 
 public static class ErrorReporting
 {
-    private static void Report(Exception e)
+    private static void Report(Exception e, [CallerMemberName] string methodName = "UnHandledException")
     {
-        var logger = LogManager.GetLogger("UnHandledException");
+        var logger = LogManager.GetLogger(methodName);
         logger.Fatal(ExceptionFormatter.FormatExcpetion(e));
         var reportWindow = new ReportWindow(e);
         reportWindow.Show();
     }
 
-    public static void UnhandledExceptionHandle(object sender, UnhandledExceptionEventArgs e)
+    public static void UnhandledException(object sender, UnhandledExceptionEventArgs e)
     {
-        //handle non-ui thread exceptions
+        // handle non-ui thread exceptions
         Report((Exception)e.ExceptionObject);
     }
 
     public static void DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
     {
-        //handle ui thread exceptions
+        // handle ui thread exceptions
         Report(e.Exception);
-        //prevent application exist, so the user can copy prompted error info
+        // prevent application exist, so the user can copy prompted error info
         e.Handled = true;
     }
 
     public static void TaskSchedulerUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
     {
-        //handle unobserved task exceptions
+        // handle unobserved task exceptions on UI thread
         Application.Current.Dispatcher.Invoke(() => Report(e.Exception));
-        //prevent application exit, so the user can copy the prompted error info
+        // prevent application exit, so the user can copy the prompted error info
+        e.SetObserved();
     }
 
     public static string RuntimeInfo()
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index f1b41eb3c9b..889679d3c4c 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -392,6 +392,7 @@
     <system:String x:Key="newActionKeywordsSameAsOld">This new Action Keyword is the same as old, please choose a different one</system:String>
     <system:String x:Key="success">Success</system:String>
     <system:String x:Key="completedSuccessfully">Completed successfully</system:String>
+    <system:String x:Key="failedToCopy">Failed to copy</system:String>
     <system:String x:Key="actionkeyword_tips">Enter the action keywords you like to use to start the plugin and use whitespace to divide them. Use * if you don't want to specify any, and the plugin will be triggered without any action keywords.</system:String>
 
     <!--  Search Delay Settings Dialog  -->
diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs
index bf7a45b1d25..1c9b73a5475 100644
--- a/Flow.Launcher/MainWindow.xaml.cs
+++ b/Flow.Launcher/MainWindow.xaml.cs
@@ -16,7 +16,6 @@
 using CommunityToolkit.Mvvm.DependencyInjection;
 using Flow.Launcher.Core.Plugin;
 using Flow.Launcher.Core.Resource;
-using Flow.Launcher.Helper;
 using Flow.Launcher.Infrastructure;
 using Flow.Launcher.Infrastructure.Hotkey;
 using Flow.Launcher.Infrastructure.Image;
@@ -174,9 +173,6 @@ private async void OnLoaded(object sender, RoutedEventArgs _)
             // Set the initial state of the QueryTextBoxCursorMovedToEnd property
             // Without this part, when shown for the first time, switching the context menu does not move the cursor to the end.
             _viewModel.QueryTextCursorMovedToEnd = false;
-            
-            // Initialize hotkey mapper after window is loaded
-            HotKeyMapper.Initialize();
 
             // View model property changed event
             _viewModel.PropertyChanged += (o, e) =>
@@ -401,7 +397,7 @@ private void OnKeyDown(object sender, KeyEventArgs e)
                             && QueryTextBox.CaretIndex == QueryTextBox.Text.Length)
                         {
                             var queryWithoutActionKeyword =
-                                QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins)?.Search;
+                                QueryBuilder.Build(QueryTextBox.Text, QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins)?.Search;
 
                             if (FilesFolders.IsLocationPathString(queryWithoutActionKeyword))
                             {
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 3abc57b8a81..d58e9cf5320 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -69,8 +69,7 @@ public void ChangeQuery(string query, bool requery = false)
             _mainVM.ChangeQueryText(query, requery);
         }
 
-#pragma warning disable VSTHRD100 // Avoid async void methods
-
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
         public async void RestartApp()
         {
             _mainVM.Hide();
@@ -89,8 +88,6 @@ public async void RestartApp()
             UpdateManager.RestartApp(Constant.ApplicationFileName);
         }
 
-#pragma warning restore VSTHRD100 // Avoid async void methods
-
         public void ShowMainWindow() => _mainVM.Show();
 
         public void HideMainWindow() => _mainVM.Hide();
@@ -145,35 +142,90 @@ public void ShellRun(string cmd, string filename = "cmd.exe")
             ShellCommand.Execute(startInfo);
         }
 
-        public void CopyToClipboard(string stringToCopy, bool directCopy = false, bool showDefaultNotification = true)
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
+        public async void CopyToClipboard(string stringToCopy, bool directCopy = false, bool showDefaultNotification = true)
         {
             if (string.IsNullOrEmpty(stringToCopy))
+            {
                 return;
+            }
 
             var isFile = File.Exists(stringToCopy);
             if (directCopy && (isFile || Directory.Exists(stringToCopy)))
             {
-                var paths = new StringCollection
+                // Sometimes the clipboard is locked and cannot be accessed,
+                // we need to retry a few times before giving up
+                var exception = await RetryActionOnSTAThreadAsync(() =>
+                {
+                    var paths = new StringCollection
                     {
                         stringToCopy
                     };
 
-                Clipboard.SetFileDropList(paths);
-
-                if (showDefaultNotification)
-                    ShowMsg(
-                        $"{GetTranslation("copy")} {(isFile ? GetTranslation("fileTitle") : GetTranslation("folderTitle"))}",
-                        GetTranslation("completedSuccessfully"));
+                    Clipboard.SetFileDropList(paths);
+                });
+                
+                if (exception == null)
+                {
+                    if (showDefaultNotification)
+                    {
+                        ShowMsg(
+                            $"{GetTranslation("copy")} {(isFile ? GetTranslation("fileTitle") : GetTranslation("folderTitle"))}",
+                            GetTranslation("completedSuccessfully"));
+                    }
+                }
+                else
+                {
+                    LogException(nameof(PublicAPIInstance), "Failed to copy file/folder to clipboard", exception);
+                    ShowMsgError(GetTranslation("failedToCopy"));
+                }
             }
             else
             {
-                Clipboard.SetDataObject(stringToCopy);
+                // Sometimes the clipboard is locked and cannot be accessed,
+                // we need to retry a few times before giving up
+                var exception = await RetryActionOnSTAThreadAsync(() =>
+                {
+                    // We should use SetText instead of SetDataObject to avoid the clipboard being locked by other applications
+                    Clipboard.SetText(stringToCopy);
+                });
+
+                if (exception == null)
+                {
+                    if (showDefaultNotification)
+                    {
+                        ShowMsg(
+                            $"{GetTranslation("copy")} {GetTranslation("textTitle")}",
+                            GetTranslation("completedSuccessfully"));
+                    }
+                }
+                else
+                {
+                    LogException(nameof(PublicAPIInstance), "Failed to copy text to clipboard", exception);
+                    ShowMsgError(GetTranslation("failedToCopy"));
+                }  
+            }
+        }
 
-                if (showDefaultNotification)
-                    ShowMsg(
-                        $"{GetTranslation("copy")} {GetTranslation("textTitle")}",
-                        GetTranslation("completedSuccessfully"));
+        private static async Task<Exception> RetryActionOnSTAThreadAsync(Action action, int retryCount = 6, int retryDelay = 150)
+        {
+            for (var i = 0; i < retryCount; i++)
+            {
+                try
+                {
+                    await Win32Helper.StartSTATaskAsync(action);
+                    break;
+                }
+                catch (Exception e)
+                {
+                    if (i == retryCount - 1)
+                    {
+                        return e;
+                    }
+                    await Task.Delay(retryDelay);
+                }
             }
+            return null;
         }
 
         public void StartLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Visible;
diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs
index 00675149b41..cfeec0b99de 100644
--- a/Flow.Launcher/ViewModel/MainViewModel.cs
+++ b/Flow.Launcher/ViewModel/MainViewModel.cs
@@ -31,8 +31,8 @@ public partial class MainViewModel : BaseModel, ISavable, IDisposable
 
         private static readonly string ClassName = nameof(MainViewModel);
 
-        private bool _isQueryRunning;
         private Query _lastQuery;
+        private Query _runningQuery;
         private string _queryTextBeforeLeaveResults;
 
         private readonly FlowLauncherJsonStorage<History> _historyItemsStorage;
@@ -43,8 +43,8 @@ public partial class MainViewModel : BaseModel, ISavable, IDisposable
         private readonly UserSelectedRecord _userSelectedRecord;
         private readonly TopMostRecord _topMostRecord;
 
-        private CancellationTokenSource _updateSource;
-        private CancellationToken _updateToken;
+        private CancellationTokenSource _updateSource; // Used to cancel old query flows
+        private readonly SemaphoreSlim _updateLock = new(1, 1); // Used to ensure one updating flow
 
         private ChannelWriter<ResultsForUpdate> _resultsUpdateChannelWriter;
         private Task _resultsViewUpdateTask;
@@ -236,12 +236,15 @@ public void RegisterResultsUpdatedEvent()
                 var plugin = (IResultUpdated)pair.Plugin;
                 plugin.ResultsUpdated += (s, e) =>
                 {
-                    if (e.Query.RawQuery != QueryText || e.Token.IsCancellationRequested)
+                    Infrastructure.Logger.Log.Debug(ClassName, $"Call IResultsUpdated for QueryText: {e.Query.RawQuery}");
+
+                    if (_runningQuery == null || e.Query.RawQuery != _runningQuery.RawQuery || e.Token.IsCancellationRequested)
                     {
+                        Infrastructure.Logger.Log.Debug(ClassName, $"Cancel for QueryText 6: {e.Query.RawQuery}");
                         return;
                     }
 
-                    var token = e.Token == default ? _updateToken : e.Token;
+                    var token = e.Token == default ? _updateSource.Token : e.Token;
 
                     // 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);
@@ -255,11 +258,22 @@ public void RegisterResultsUpdatedEvent()
                     }
 
                     PluginManager.UpdatePluginMetadata(resultsCopy, pair.Metadata, e.Query);
-                    if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query,
-                            token)))
+
+                    if (_runningQuery == null || e.Query.RawQuery != _runningQuery.RawQuery || token.IsCancellationRequested)
+                    {
+                        Infrastructure.Logger.Log.Debug(ClassName, $"Cancel for QueryText 7: {e.Query.RawQuery}");
+                        return;
+                    }
+
+                    if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query, 
+                        token)))
                     {
                         App.API.LogError(ClassName, "Unable to add item to Result Update Queue");
                     }
+                    else
+                    {
+                        Infrastructure.Logger.Log.Debug(ClassName, $"Write updates for QueryText 1: {e.Query.RawQuery}");
+                    }
                 };
             }
         }
@@ -306,6 +320,7 @@ public void ReQuery()
         {
             if (QueryResultsSelected())
             {
+                Infrastructure.Logger.Log.Debug(ClassName, $"Search Delay: {false}, Is Requery: {true}, Reselect: {true}");
                 // When we are re-querying, we should not delay the query
                 _ = QueryResultsAsync(false, isReQuery: true);
             }
@@ -314,6 +329,7 @@ public void ReQuery()
         public void ReQuery(bool reselect)
         {
             BackToQueryResults();
+            Infrastructure.Logger.Log.Debug(ClassName, $"Search Delay: {false}, Is Requery: {true}, Reselect: {reselect}");
             // When we are re-querying, we should not delay the query
             _ = QueryResultsAsync(false, isReQuery: true, reSelect: reselect);
         }
@@ -363,7 +379,7 @@ private void LoadContextMenu()
         [RelayCommand]
         private void Backspace(object index)
         {
-            var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins);
+            var query = QueryBuilder.Build(QueryText, QueryText.Trim(), PluginManager.NonGlobalPlugins);
 
             // GetPreviousExistingDirectory does not require trailing '\', otherwise will return empty string
             var path = FilesFolders.GetPreviousExistingDirectory((_) => true, query.Search.TrimEnd('\\'));
@@ -640,7 +656,31 @@ private void DecreaseMaxResult()
         /// <param name="isReQuery">Force query even when Query Text doesn't change</param>
         public void ChangeQueryText(string queryText, bool isReQuery = false)
         {
-            _ = ChangeQueryTextAsync(queryText, isReQuery);
+            // 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;
         }
 
         /// <summary>
@@ -648,10 +688,10 @@ public void ChangeQueryText(string queryText, bool isReQuery = false)
         /// </summary>
         private async Task ChangeQueryTextAsync(string queryText, bool isReQuery = false)
         {
-            // Must check access so that we will not block the UI thread which cause window visibility issue
+            // Must check access so that we will not block the UI thread which causes window visibility issue
             if (!Application.Current.Dispatcher.CheckAccess())
             {
-                await Application.Current.Dispatcher.InvokeAsync(() => ChangeQueryText(queryText, isReQuery));
+                await Application.Current.Dispatcher.InvokeAsync(() => ChangeQueryTextAsync(queryText, isReQuery));
                 return;
             }
 
@@ -1050,13 +1090,26 @@ private bool QueryResultsPreviewed()
 
         public void Query(bool searchDelay, bool isReQuery = false)
         {
-            _ = QueryAsync(searchDelay, isReQuery);
+            if (QueryResultsSelected())
+            {
+                Infrastructure.Logger.Log.Debug(ClassName, $"Search Delay: {searchDelay}, Is Requery: {isReQuery}, Reselect: {true}");
+                _ = QueryResultsAsync(searchDelay, isReQuery);
+            }
+            else if (ContextMenuSelected())
+            {
+                QueryContextMenu();
+            }
+            else if (HistorySelected())
+            {
+                QueryHistory();
+            }
         }
 
         private async Task QueryAsync(bool searchDelay, bool isReQuery = false)
         {
             if (QueryResultsSelected())
             {
+                Infrastructure.Logger.Log.Debug(ClassName, $"Search Delay: {searchDelay}, Is Requery: {isReQuery}, Reselect: {true}");
                 await QueryResultsAsync(searchDelay, isReQuery);
             }
             else if (ContextMenuSelected())
@@ -1159,105 +1212,176 @@ private void QueryHistory()
         private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true)
         {
             _updateSource?.Cancel();
+            _runningQuery = null;
 
-            var query = ConstructQuery(QueryText, Settings.CustomShortcuts, Settings.BuiltinShortcuts);
+            Infrastructure.Logger.Log.Debug(ClassName, $"Query construct for QueryText: {QueryText}");
 
-            var plugins = PluginManager.ValidPluginsForQuery(query);
+            var query = ConstructQuery(QueryText, Settings.CustomShortcuts, Settings.BuiltinShortcuts);
 
-            if (query == null || plugins.Count == 0) // shortcut expanded
+            if (query == null) // shortcut expanded
             {
-                Results.Clear();
+                Infrastructure.Logger.Log.Debug(ClassName, $"Query null for QueryText");
+
+                // Hide and clear results fast because results are already invalid although query is still running
                 Results.Visibility = Visibility.Collapsed;
-                PluginIconPath = null;
-                PluginIconSource = null;
-                SearchIconVisibility = Visibility.Visible;
-                return;
-            }
-            else 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;
-            }
+                Results.Clear();
 
-            _updateSource?.Dispose();
+                // Hide progress bar because running query is already invalid
+                ProgressBarVisibility = Visibility.Hidden;
 
-            var currentUpdateSource = new CancellationTokenSource();
-            _updateSource = currentUpdateSource;
-            _updateToken = _updateSource.Token;
+                // Wait last query to be canceled and then reset UI elements
+                await _updateLock.WaitAsync(CancellationToken.None);
+                try
+                {
+                    Infrastructure.Logger.Log.Debug(ClassName, $"Clear for QueryText");
 
-            ProgressBarVisibility = Visibility.Hidden;
-            _isQueryRunning = true;
+                    // Hide and clear results again because running query may show and add some results
+                    Results.Visibility = Visibility.Collapsed;
+                    Results.Clear();
 
-            // Switch to ThreadPool thread
-            await TaskScheduler.Default;
+                    // Reset plugin icon
+                    PluginIconPath = null;
+                    PluginIconSource = null;
+                    SearchIconVisibility = Visibility.Visible;
 
-            if (_updateSource.Token.IsCancellationRequested)
+                    // Hide progress bar again because running query may set this to visible
+                    ProgressBarVisibility = Visibility.Hidden;
+                }
+                finally
+                {
+                    _updateLock.Release();
+                }
                 return;
+            }
 
-            // Update the query's IsReQuery property to true if this is a re-query
-            query.IsReQuery = isReQuery;
+            Infrastructure.Logger.Log.Debug(ClassName, $"Wait for QueryText: {query.RawQuery}");
 
-            // handle the exclusiveness of plugin using action keyword
-            RemoveOldQueryResults(query);
+            await _updateLock.WaitAsync(CancellationToken.None);
+            try
+            {
+                // Check if the query has changed because query can be changed so fast that
+                // token of the query between two queries has not been created yet
+                if (query.Input != QueryText && query.RawQuery != QueryText.Trim())
+                {
+                    Infrastructure.Logger.Log.Debug(ClassName, $"Cancel for QueryText 0: {query.RawQuery}");
+                    return;
+                }
 
-            _lastQuery = query;
+                _updateSource = new CancellationTokenSource();
+
+                ProgressBarVisibility = Visibility.Hidden;
+
+                Infrastructure.Logger.Log.Debug(ClassName, $"Start for QueryText: {query.RawQuery}");
+                Infrastructure.Logger.Log.Debug(ClassName, $"ProgressBar: {Visibility.Hidden}");
+
+                _runningQuery = query;
+
+                // Switch to ThreadPool thread
+                await TaskScheduler.Default;
 
-            if (string.IsNullOrEmpty(query.ActionKeyword))
-            {
-                // Wait 15 millisecond for query change in global query
-                // if query changes, return so that it won't be calculated
-                await Task.Delay(15, _updateSource.Token);
                 if (_updateSource.Token.IsCancellationRequested)
+                {
+                    Infrastructure.Logger.Log.Debug(ClassName, $"Cancel for QueryText 1: {query.RawQuery}");
                     return;
-            }
+                }
+
+                // Update the query's IsReQuery property to true if this is a re-query
+                query.IsReQuery = isReQuery;
 
-            _ = Task.Delay(200, _updateSource.Token).ContinueWith(_ =>
+                // handle the exclusiveness of plugin using action keyword
+                RemoveOldQueryResults(query);
+
+                Infrastructure.Logger.Log.Debug(ClassName, $"Remove old for QueryText: {query.RawQuery}");
+
+                _lastQuery = query;
+
+                var plugins = PluginManager.ValidPluginsForQuery(query);
+
+                Infrastructure.Logger.Log.Debug(ClassName, $"Valid {plugins.Count} plugins QueryText: {query.RawQuery}");
+
+                if (plugins.Count == 1)
                 {
-                    // 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 (!_updateSource.Token.IsCancellationRequested && _isQueryRunning)
+                    PluginIconPath = plugins.Single().Metadata.IcoPath;
+                    PluginIconSource = await App.API.LoadImageAsync(PluginIconPath);
+                    SearchIconVisibility = Visibility.Hidden;
+                }
+                else
+                {
+                    PluginIconPath = null;
+                    PluginIconSource = null;
+                    SearchIconVisibility = Visibility.Visible;
+                }
+
+                // Do not wait for performance improvement
+                /*if (string.IsNullOrEmpty(query.ActionKeyword))
+                {
+                    // Wait 15 millisecond for query change in global query
+                    // if query changes, return so that it won't be calculated
+                    await Task.Delay(15, _updateSource.Token);
+                    if (_updateSource.Token.IsCancellationRequested)
+                        return;
+                }*/
+
+                _ = Task.Delay(200, _updateSource.Token).ContinueWith(_ =>
                     {
-                        ProgressBarVisibility = Visibility.Visible;
-                    }
-                },
-                _updateSource.Token,
-                TaskContinuationOptions.NotOnCanceled,
-                TaskScheduler.Default);
+                        Infrastructure.Logger.Log.Debug(ClassName, $"Check ProgressBar for QueryText: running: {_runningQuery?.RawQuery ?? "null"} query: {query.RawQuery}");
 
-            // plugins are ICollection, meaning LINQ will get the Count and preallocate Array
+                        // 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 (_runningQuery != null && _runningQuery == query)
+                        {
+                            ProgressBarVisibility = Visibility.Visible;
 
-            var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch
-            {
-                false => QueryTaskAsync(plugin, _updateSource.Token),
-                true => Task.CompletedTask
-            }).ToArray();
+                            Infrastructure.Logger.Log.Debug(ClassName, $"ProgressBar: {Visibility.Visible}");
+                        }
+                    },
+                    _updateSource.Token,
+                    TaskContinuationOptions.NotOnCanceled,
+                    TaskScheduler.Default);
 
-            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
-            }
+                // plugins are ICollection, meaning LINQ will get the Count and preallocate Array
 
-            if (_updateSource.Token.IsCancellationRequested)
-                return;
+                var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch
+                {
+                    false => QueryTaskAsync(plugin, _updateSource.Token),
+                    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);
+                }
+                catch (OperationCanceledException)
+                {
+                    // nothing to do here
+                }
+
+                if (_updateSource.Token.IsCancellationRequested)
+                {
+                    Infrastructure.Logger.Log.Debug(ClassName, $"Cancel for QueryText 2: {query.RawQuery}");
+                    return;
+                }
+
+                // this should happen once after all queries are done so progress bar should continue
+                // until the end of all querying
+                _runningQuery = null;
 
-            // this should happen once after all queries are done so progress bar should continue
-            // until the end of all querying
-            _isQueryRunning = false;
-            if (!_updateSource.Token.IsCancellationRequested)
+                if (!_updateSource.Token.IsCancellationRequested)
+                {
+                    // update to hidden if this is still the current query
+                    ProgressBarVisibility = Visibility.Hidden;
+
+                    Infrastructure.Logger.Log.Debug(ClassName, $"ProgressBar: {Visibility.Hidden}");
+                }
+            }
+            finally
             {
-                // update to hidden if this is still the current query
-                ProgressBarVisibility = Visibility.Hidden;
+                Infrastructure.Logger.Log.Debug(ClassName, $"Query return for QueryText: {query.RawQuery}");
+                // this make sures running query is null even if the query is canceled
+                _runningQuery = null;
+
+                // release the lock so that other query can be executed
+                _updateLock.Release();
             }
 
             // Local function
@@ -1270,7 +1394,10 @@ async Task QueryTaskAsync(PluginPair plugin, CancellationToken token)
                     await Task.Delay(searchDelayTime, token);
 
                     if (token.IsCancellationRequested)
+                    {
+                        Infrastructure.Logger.Log.Debug(ClassName, $"Cancel for QueryText 3: {QueryText}");
                         return;
+                    }
                 }
 
                 // Since it is wrapped within a ThreadPool Thread, the synchronous context is null
@@ -1280,7 +1407,10 @@ async Task QueryTaskAsync(PluginPair plugin, CancellationToken token)
                 var results = await PluginManager.QueryForPluginAsync(plugin, query, token);
 
                 if (token.IsCancellationRequested)
+                {
+                    Infrastructure.Logger.Log.Debug(ClassName, $"Cancel for QueryText 4: {query.RawQuery}");
                     return;
+                }
 
                 IReadOnlyList<Result> resultsCopy;
                 if (results == null)
@@ -1301,24 +1431,34 @@ async Task QueryTaskAsync(PluginPair plugin, CancellationToken token)
                     }
                 }
 
+                if (token.IsCancellationRequested)
+                {
+                    Infrastructure.Logger.Log.Debug(ClassName, $"Cancel for QueryText 5: {query.RawQuery}");
+                    return;
+                }
+
                 if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, plugin.Metadata, query,
                     token, reSelect)))
                 {
                     App.API.LogError(ClassName, "Unable to add item to Result Update Queue");
                 }
+                else
+                {
+                    Infrastructure.Logger.Log.Debug(ClassName, $"Write updates for QueryText: {query.RawQuery}");
+                }
             }
         }
 
         private Query ConstructQuery(string queryText, IEnumerable<CustomShortcutModel> customShortcuts,
-            IEnumerable<BuiltinShortcutModel> builtInShortcuts)
+            IEnumerable<BaseBuiltinShortcutModel> builtInShortcuts)
         {
             if (string.IsNullOrWhiteSpace(queryText))
             {
                 return null;
             }
 
-            StringBuilder queryBuilder = new(queryText);
-            StringBuilder queryBuilderTmp = new(queryText);
+            var queryBuilder = new StringBuilder(queryText);
+            var queryBuilderTmp = new StringBuilder(queryText);
 
             // Sorting order is important here, the reason is for matching longest shortcut by default
             foreach (var shortcut in customShortcuts.OrderByDescending(x => x.Key.Length))
@@ -1331,36 +1471,56 @@ private Query ConstructQuery(string queryText, IEnumerable<CustomShortcutModel>
                 queryBuilder.Replace('@' + shortcut.Key, shortcut.Expand());
             }
 
-            string customExpanded = queryBuilder.ToString();
+            // Applying builtin shortcuts
+            BuildQuery(builtInShortcuts, queryBuilder, queryBuilderTmp);
 
-            Application.Current.Dispatcher.Invoke(() =>
+            return QueryBuilder.Build(queryText, queryBuilder.ToString().Trim(), PluginManager.NonGlobalPlugins);
+        }
+
+        private void BuildQuery(IEnumerable<BaseBuiltinShortcutModel> builtInShortcuts,
+            StringBuilder queryBuilder, StringBuilder queryBuilderTmp)
+        {
+            var customExpanded = queryBuilder.ToString();
+
+            var queryChanged = false;
+
+            foreach (var shortcut in builtInShortcuts)
             {
-                foreach (var shortcut in builtInShortcuts)
+                try
                 {
-                    try
+                    if (customExpanded.Contains(shortcut.Key))
                     {
-                        if (customExpanded.Contains(shortcut.Key))
+                        string expansion;
+                        if (shortcut is BuiltinShortcutModel syncShortcut)
                         {
-                            var expansion = shortcut.Expand();
-                            queryBuilder.Replace(shortcut.Key, expansion);
-                            queryBuilderTmp.Replace(shortcut.Key, expansion);
+                            expansion = syncShortcut.Expand();
                         }
-                    }
-                    catch (Exception e)
-                    {
-                        App.API.LogException(ClassName,
-                            $"Error when expanding shortcut {shortcut.Key}",
-                            e);
+                        else if (shortcut is AsyncBuiltinShortcutModel asyncShortcut)
+                        {
+                            expansion = App.JTF.Run(() => asyncShortcut.ExpandAsync());
+                        }
+                        else
+                        {
+                            continue;
+                        }
+                        queryBuilder.Replace(shortcut.Key, expansion);
+                        queryBuilderTmp.Replace(shortcut.Key, expansion);
+                        queryChanged = true;
                     }
                 }
-            });
-
-            // show expanded builtin shortcuts
-            // use private field to avoid infinite recursion
-            _queryText = queryBuilderTmp.ToString();
+                catch (Exception e)
+                {
+                    App.API.LogException(ClassName, $"Error when expanding shortcut {shortcut.Key}", e);
+                }
+            }
 
-            var query = QueryBuilder.Build(queryBuilder.ToString().Trim(), PluginManager.NonGlobalPlugins);
-            return query;
+            if (queryChanged)
+            {
+                // show expanded builtin shortcuts
+                // use private field to avoid infinite recursion
+                _queryText = queryBuilderTmp.ToString();
+                OnPropertyChanged(nameof(QueryText));
+            }
         }
 
         private void RemoveOldQueryResults(Query query)
@@ -1489,6 +1649,9 @@ public bool ShouldIgnoreHotkeys()
 
         public void Show()
         {
+            // When application is exiting, we should not show the main window
+            if (App.Exitting) return;
+
             // When application is exiting, the Application.Current will be null
             Application.Current?.Dispatcher.Invoke(() =>
             {
@@ -1702,6 +1865,7 @@ protected virtual void Dispose(bool disposing)
                     {
                         _resultsViewUpdateTask.Dispose();
                     }
+                    _updateLock?.Dispose();
                     _disposed = true;
                 }
             }