Skip to content

Fix copy to clipboard STA thread issue & Support retry for copy & Async build-in shortcut model & Refactor query update logic & Fix build shortcuts text replace issue #3314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 73 commits into
base: dev
Choose a base branch
from

Conversation

Jack251970
Copy link
Contributor

@Jack251970 Jack251970 commented Mar 6, 2025

Important

All test codes Infrastructure.Logger.Log.Debug in MainViewModel should be removed.

Fix copy to clipboard STA thread issue

Fix CopyToClipboard function in JsonRPCAPI for JsonRPCV2 plugins: we should use STA thread to execute clipboard options.

Close #3071.

Add retry for clipboard copy

Sometimes clipboard is locked, we need to wait and retry.

Async buildin shortcut model

Since new clipboard function need async, we should add support for asynchronous build-in shortcut model.

Refactor query update logic

Fix build shortcuts text replace issue

Fix build shortcuts text replace issue from jjw24 and remove global query delay

image

Fix possible ui thread issue

Check dispatcher access before invoking dispatch to fix possible ui thread issue

Fix query resetting & updating issue

Add new slim & query for fixing query resetting & update issue.

Test

  • Copy to clipboard (text, file) on csharp plugins
  • Copy to clipboard (text, file) on JsonRPCV2 plugins
  • Get text from clipboard
  • Get text from clipboard asynchronously
  • Builtin shortcuts are replaced when we query them
  • Add additional 1000ms for Web Searches plugin and query results display very well when changing query fast

@prlabeler prlabeler bot added the bug Something isn't working label Mar 6, 2025

This comment has been minimized.

Copy link

gitstream-cm bot commented Mar 6, 2025

Be a legend 🏆 by adding a before and after screenshot of the changes you made, especially if they are around UI/UX.

Copy link
Contributor

coderabbitai bot commented Mar 6, 2025

📝 Walkthrough

Walkthrough

This set of changes introduces robust support for executing clipboard and other Windows UI operations on single-threaded apartment (STA) threads, addressing thread affinity issues. The PublicAPIInstance.CopyToClipboard method is refactored to run clipboard operations asynchronously on STA threads with retry logic, preventing errors when called from non-STA threads (such as JSON-RPC v2 plugins). Supporting changes include new helper methods in Win32Helper, new and refactored shortcut model classes for async expansion, updates to settings and main view model logic to accommodate asynchronous shortcut expansion, and minor improvements to error reporting, localization, and application startup.

Changes

File(s) Change Summary
Flow.Launcher.Infrastructure/NativeMethods.txt Added OleInitialize and OleUninitialize Windows API declarations for STA thread initialization and cleanup; restored newline for LOCALE_TRANSIENT_KEYBOARD4.
Flow.Launcher.Infrastructure/Win32Helper.cs Introduced StartSTATaskAsync methods to run actions and functions asynchronously on STA threads, with proper OLE initialization and cleanup.
Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs Refactored shortcut model hierarchy: introduced abstract base classes for custom and builtin shortcuts, added AsyncBuiltinShortcutModel for async expansion, and updated constructors and properties accordingly.
Flow.Launcher.Infrastructure/UserSettings/Settings.cs Changed BuiltinShortcuts property type to ObservableCollection, updated clipboard shortcut to use AsyncBuiltinShortcutModel with async clipboard access.
Flow.Launcher/PublicAPIInstance.cs Refactored CopyToClipboard to run clipboard operations asynchronously on STA threads with retry logic; added private RetryActionOnSTAThreadAsync method; improved error handling and notifications.
Flow.Launcher/ViewModel/MainViewModel.cs Refactored query state tracking and concurrency control; updated ConstructQuery and shortcut expansion logic to support async expansion for built-in shortcuts; added debug logging; centralized UI dispatcher logic for Show/Hide methods; replaced boolean query running flag with Query object and SemaphoreSlim.
Flow.Launcher/App.xaml.cs Added static JoinableTaskFactory for async operations; updated error reporting event handler registration; improved comments and conditional compilation for update checks.
Flow.Launcher/Helper/ErrorReporting.cs Updated error reporting to use caller member name for logger selection; renamed UnhandledExceptionHandle to UnhandledException; improved UI thread handling for unobserved task exceptions; added CallerMemberName attribute support.
Flow.Launcher/Languages/en.xaml Added new string resource "failedToCopy" for clipboard operation failure notifications.
Flow.Launcher.Core/Resource/Theme.cs Fixed syntax error and removed trailing whitespace lines in multiple methods for cleaner formatting without changing logic.

Sequence Diagram(s)

sequenceDiagram
    participant Plugin
    participant PublicAPIInstance
    participant Win32Helper
    participant STAThread
    participant Clipboard

    Plugin->>PublicAPIInstance: CopyToClipboard(stringToCopy)
    alt stringToCopy is file/directory or text
        PublicAPIInstance->>Win32Helper: StartSTATaskAsync(Action)
        Win32Helper->>STAThread: Start STA Thread
        STAThread->>Clipboard: SetFileDropList/SetText
        Clipboard-->>STAThread: (Operation result)
        STAThread-->>Win32Helper: Complete Task
        Win32Helper-->>PublicAPIInstance: Task result
        PublicAPIInstance->>Plugin: Show notification (success/failure)
    end
Loading
sequenceDiagram
    participant MainViewModel
    participant Settings
    participant BuiltinShortcutModel/AsyncBuiltinShortcutModel
    participant Win32Helper
    participant Clipboard

    MainViewModel->>Settings: Get BuiltinShortcuts
    loop For each shortcut in BuiltinShortcuts
        alt BuiltinShortcutModel
            MainViewModel->>BuiltinShortcutModel: Expand()
        else AsyncBuiltinShortcutModel
            MainViewModel->>AsyncBuiltinShortcutModel: ExpandAsync()
            AsyncBuiltinShortcutModel->>Win32Helper: StartSTATaskAsync(Func<string>)
            Win32Helper->>STAThread: Start STA Thread
            STAThread->>Clipboard: GetText()
            Clipboard-->>STAThread: Clipboard text
            STAThread-->>Win32Helper: Result
            Win32Helper-->>AsyncBuiltinShortcutModel: Clipboard text
        end
        MainViewModel->>MainViewModel: Replace shortcut key in query
    end
Loading

Assessment against linked issues

Objective Addressed Explanation
Fix CopyToClipboard method to avoid STA thread errors for JSON-RPC v2 plugins (#3071)

Possibly related PRs

Suggested labels

Code Quality

Suggested reviewers

  • taooceros
  • Yusyuriv

Poem

A bunny hopped with clipboard dreams,
But STA threads broke all his schemes.
Now with helpers new and logic spry,
He copies and pastes—oh my, oh my!
Async and safe, no errors in sight,
This patch makes the bunny’s clipboard just right.
🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between adde491 and 6859232.

📒 Files selected for processing (5)
  • Flow.Launcher.Core/Resource/Theme.cs (7 hunks)
  • Flow.Launcher.Infrastructure/UserSettings/Settings.cs (1 hunks)
  • Flow.Launcher.Infrastructure/Win32Helper.cs (2 hunks)
  • Flow.Launcher/App.xaml.cs (5 hunks)
  • Flow.Launcher/Languages/en.xaml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • Flow.Launcher/Languages/en.xaml
  • Flow.Launcher.Core/Resource/Theme.cs
  • Flow.Launcher.Infrastructure/Win32Helper.cs
  • Flow.Launcher.Infrastructure/UserSettings/Settings.cs
  • Flow.Launcher/App.xaml.cs

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Yusyuriv
Copy link
Member

Yusyuriv commented Mar 6, 2025

I am very against this. This is the core, most used, functionality, and it should be available to everyone. Plugins shouldn't need to reinvent the wheel every time. Especially considering Node and Python don't provide a way to do this. I remember seeing one plugin that packed another binary with itself just so it could copy text, which is absolutely ridiculous.

@Jack251970
Copy link
Contributor Author

Jack251970 commented Mar 6, 2025

I am very against this. This is the core, most used, functionality, and it should be available to everyone. Plugins shouldn't need to reinvent the wheel every time. Especially considering Node and Python don't provide a way to do this. I remember seeing one plugin that packed another binary with itself just so it could copy text, which is absolutely ridiculous.

Well, could you please give me the sample to test?

@Jack251970 Jack251970 changed the title Delete CopyToClipboard function in JsonRPC api [TODO] Fix CopyToClipboard function in JsonRPC api Mar 6, 2025
@Yusyuriv Yusyuriv marked this pull request as draft March 6, 2025 05:22
@Jack251970 Jack251970 changed the title [TODO] Fix CopyToClipboard function in JsonRPC api Fix CopyToClipboard function in JsonRPC api Mar 6, 2025
@Jack251970 Jack251970 changed the title Fix CopyToClipboard function in JsonRPC api Fix CopyToClipboard function in JsonRPCAPI for JsonRPCV2 plugins Mar 6, 2025
@Yusyuriv
Copy link
Member

Yusyuriv commented Mar 6, 2025

demo-plugin.zip

After installing this plugin, type rpc2. It will have one result. Select that result. You'll get a message box saying if the text was copied successfully or not.

@Jack251970
Copy link
Contributor Author

demo-plugin.zip

After installing this plugin, type rpc2. It will have one result. Select that result. You'll get a message box saying if the text was copied successfully or not.

Many thanks! Sorry, I don't see this as a bug unique to the V2 plugin. In that case, the bug could be fixed.

@Jack251970
Copy link
Contributor Author

Jack251970 commented Mar 6, 2025

demo-plugin.zip

After installing this plugin, type rpc2. It will have one result. Select that result. You'll get a message box saying if the text was copied successfully or not.

Sorry I cannot open your demo plugin. I have used 1.19.5 release version of FL (master branch) and downloaded embedded node.js environment from FL but I got this issue:

image

I want to check it by myself, but I really know little about JavaScript. Could you please fix it?

@Yusyuriv
Copy link
Member

Yusyuriv commented Mar 6, 2025

Could you please install a newer version of NodeJS (v22), specify it in settings (General — Node.js Path) and try again? Flow currently uses very old version that has reached end-of-life already.

This comment has been minimized.

@Jack251970 Jack251970 changed the title Fix CopyToClipboard function in JsonRPCAPI for JsonRPCV2 plugins Fix copy to clipboard STA thread issue & Add retry for text copy Mar 6, 2025
@Jack251970
Copy link
Contributor Author

Could you please install a newer version of NodeJS (v22), specify it in settings (General — Node.js Path) and try again? Flow currently uses very old version that has reached end-of-life already.

Thanks, I think I have fixed it. Now could you please help me check if copy to clipboard (directory) with JsonRPCV2 plugins can work?

@Jack251970
Copy link
Contributor Author

Could you please install a newer version of NodeJS (v22), specify it in settings (General — Node.js Path) and try again? Flow currently uses very old version that has reached end-of-life already.

Thanks, I think I have fixed it. Now could you please help me check if copy to clipboard (directory) with JsonRPCV2 plugins can work?

Nvm, I have checked it.

@Jack251970 Jack251970 marked this pull request as ready for review March 6, 2025 08:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
Flow.Launcher.Infrastructure/Win32Helper.cs (2)

122-125: Consider propagating exceptions rather than suppressing them.

The current implementation catches all exceptions but doesn't propagate them to the caller, making it difficult to diagnose issues when they occur.

-                catch (System.Exception)
-                {
-                    taskCompletionSource.SetResult();
-                }
+                catch (System.Exception ex)
+                {
+                    taskCompletionSource.SetException(ex);
+                }

154-157: Consider propagating exceptions rather than returning default values.

Similar to the non-generic method, this implementation catches all exceptions but doesn't propagate them to the caller. Instead, it returns a default value which could mask errors.

-                catch (System.Exception)
-                {
-                    taskCompletionSource.SetResult(default);
-                }
+                catch (System.Exception ex)
+                {
+                    taskCompletionSource.SetException(ex);
+                }
Flow.Launcher/PublicAPIInstance.cs (3)

131-156: Consolidate repetitive clipboard copy logic.

The logic for direct file/folder copying (lines 131–156) and text copying (lines 160–181) mostly duplicates the retry mechanism and message handling. Consider extracting common retry logic into a shared helper or unify this code block to improve maintainability and clarity.

Also applies to: 160-181


164-164: Fix minor typo in comment.

Change "shouold" to "should" for clarity.

- // We shouold use SetText instead of SetDataObject to avoid the clipboard being locked by other applications
+ // We should use SetText instead of SetDataObject to avoid the clipboard being locked by other applications

185-204: Reconsider returning the exception object from RetryActionOnSTAThreadAsync.

Returning an Exception object directly is functional, but throwing an exception or returning a status (e.g., bool) might simplify the calling code. The current design requires the caller to check for null vs. non-null to determine success/failure, which can be error-prone or less conventional.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f8e829 and e5ad777.

📒 Files selected for processing (5)
  • Flow.Launcher.Infrastructure/NativeMethods.txt (1 hunks)
  • Flow.Launcher.Infrastructure/UserSettings/Settings.cs (1 hunks)
  • Flow.Launcher.Infrastructure/Win32Helper.cs (2 hunks)
  • Flow.Launcher/Languages/en.xaml (1 hunks)
  • Flow.Launcher/PublicAPIInstance.cs (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: gitStream.cm
  • GitHub Check: gitStream.cm
  • GitHub Check: gitStream.cm
🔇 Additional comments (4)
Flow.Launcher.Infrastructure/NativeMethods.txt (1)

21-22: Good addition of required OLE function declarations.

These OLE function declarations are necessary for properly initializing and uninitializing the clipboard operations on STA threads, ensuring proper COM interop which is critical for clipboard functionality.

Flow.Launcher/Languages/en.xaml (1)

339-339: Good addition of error message for clipboard operations.

This new string resource will help provide better user feedback when clipboard operations fail, improving the overall user experience.

Flow.Launcher.Infrastructure/UserSettings/Settings.cs (1)

251-251:

✅ Verification successful

Ensure clipboard operations run on STA thread.

Good change to use the new StartSTATaskAsync method for clipboard text retrieval. This ensures proper threading model for COM operations.

Note that using .Result could potentially lead to deadlocks in certain scenarios, but since this is only creating a model with a function reference to be executed later, it should be fine in this context.


🏁 Script executed:

#!/bin/bash
# Check if this pattern is used elsewhere in the codebase where it might cause problems
rg "StartSTATaskAsync.*\.Result" --type cs

Length of output: 248


Verified STA Thread Usage for Clipboard Operations

After reviewing the code and verifying the pattern usage in the codebase, it’s clear that the call to Win32Helper.StartSTATaskAsync(Clipboard.GetText).Result is only present in Flow.Launcher.Infrastructure/UserSettings/Settings.cs. Since this function is used to defer clipboard text retrieval until later execution and is correctly encapsulated within a model creation, the use of .Result here is acceptable and does not pose a deadlock risk in this context.

  • Verified that the snippet is uniquely located in the intended file.
  • Confirmed that clipboard operations are safely executed on an STA thread.
Flow.Launcher.Infrastructure/Win32Helper.cs (1)

104-174: Good implementation of STA thread execution helpers.

The implementation of these helper methods provides a clean way to execute actions on an STA thread, which is necessary for clipboard and other COM operations. Properly initializing and uninitializing OLE ensures resources are managed correctly.

Copy link

gitstream-cm bot commented Mar 6, 2025

🥷 Code experts: onesounds

Jack251970, onesounds have most 👩‍💻 activity in the files.
Jack251970, onesounds have most 🧠 knowledge in the files.

See details

Flow.Launcher.Infrastructure/NativeMethods.txt

Activity based on git-commit:

Jack251970 onesounds
MAR 30 additions & 3 deletions
FEB 4 additions & 1 deletions
JAN 1 additions & 4 deletions
DEC 24 additions & 2 deletions
NOV
OCT

Knowledge based on git-blame:
Jack251970: 100%

Flow.Launcher.Infrastructure/UserSettings/Settings.cs

Activity based on git-commit:

Jack251970 onesounds
MAR 3 additions & 2 deletions 10 additions & 0 deletions
FEB 10 additions & 4 deletions
JAN 17 additions & 4 deletions
DEC 1 additions & 1 deletions
NOV
OCT

Knowledge based on git-blame:
onesounds: 25%
Jack251970: 7%

Flow.Launcher.Infrastructure/Win32Helper.cs

Activity based on git-commit:

Jack251970 onesounds
MAR 360 additions & 159 deletions
FEB 13 additions & 16 deletions 22 additions & 0 deletions
JAN 0 additions & 138 deletions
DEC 243 additions & 4 deletions
NOV
OCT

Knowledge based on git-blame:
Jack251970: 100%

Flow.Launcher/Languages/en.xaml

Activity based on git-commit:

Jack251970 onesounds
MAR 6 additions & 3 deletions 7 additions & 3 deletions
FEB 10 additions & 7 deletions
JAN 1 additions & 0 deletions
DEC
NOV
OCT

Knowledge based on git-blame:
onesounds: 44%
Jack251970: 2%

Flow.Launcher/PublicAPIInstance.cs

Activity based on git-commit:

Jack251970 onesounds
MAR 21 additions & 19 deletions
FEB 24 additions & 3 deletions
JAN 45 additions & 47 deletions
DEC 25 additions & 32 deletions
NOV 16 additions & 0 deletions
OCT

Knowledge based on git-blame:
Jack251970: 22%
onesounds: 1%

To learn more about /:\ gitStream - Visit our Docs

@Jack251970 Jack251970 added the enhancement New feature or request label Mar 6, 2025
@Jack251970 Jack251970 changed the title Fix copy to clipboard STA thread issue & Add retry for text copy Fix copy to clipboard STA thread issue & Add retry for copy Mar 9, 2025
@Jack251970 Jack251970 requested a review from jjw24 March 10, 2025 04:11
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
Flow.Launcher/ViewModel/MainViewModel.cs (2)

658-668: ⚠️ Potential issue

isReQuery flag is lost when query text changes.

In ChangeQueryText when QueryText != queryText, you're calling Query(false, isReQuery: false) regardless of the isReQuery parameter value. This means callers cannot force a re-query when changing the query text.

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);
+    Query(false, isReQuery);

    // set to false so the subsequent set true triggers
    // PropertyChanged and MoveQueryTextToEnd is called
    QueryTextCursorMovedToEnd = false;
}

1203-1233: 🛠️ Refactor suggestion

Shortcut expansion handling could lead to UI freezes.

When a shortcut is expanded (query is null), you appropriately wait for the lock before resetting UI state. However, this waiting happens on the UI thread before switching to the thread pool in line 1235, potentially freezing the UI if the lock is currently held.

if (query == null) // shortcut expanded
{
    App.API.LogDebug(ClassName, $"Query null for QueryText: null");

+   // Switch to ThreadPool thread to keep UI responsive when waiting update lock
+   await TaskScheduler.Default;
    
    // Wait last query to be canceled and then do resetting actions
    await _updateLock.WaitAsync(CancellationToken.None);
    try
    {
        // Reset results
        Results.Clear();
        Results.Visibility = Visibility.Collapsed;

        // Reset plugin icon
        PluginIconPath = null;
        PluginIconSource = null;
        SearchIconVisibility = Visibility.Visible;

        // Reset progress bar
        ProgressBarVisibility = Visibility.Hidden;
    }
    finally
    {
        _updateLock.Release();
    }
    return;
}
🧹 Nitpick comments (3)
Flow.Launcher/ViewModel/MainViewModel.cs (3)

46-48: Good improvement to concurrency control mechanism.

Replacing simple boolean flags with proper concurrency primitives improves thread safety and query flow management:

  • _updateSource allows cancellation of obsolete queries
  • _updateToken provides cancellation support to query tasks
  • _updateLock ensures only one query update executes at a time

Consider passing a cancellation token to _updateLock.WaitAsync() to avoid potential deadlocks if a lock is never released.

-await _updateLock.WaitAsync(CancellationToken.None);
+await _updateLock.WaitAsync(_updateSource?.Token ?? CancellationToken.None);

678-682: Use ChangeQueryText instead of recursively calling ChangeQueryTextAsync.

The async method recursively calls itself when dispatcher access is needed, but this creates unnecessary async state machines. Since the synchronous version already handles thread dispatching correctly, prefer calling it directly.

-await Application.Current.Dispatcher.InvokeAsync(() => ChangeQueryTextAsync(queryText, isReQuery));
+await Application.Current.Dispatcher.InvokeAsync(() => ChangeQueryText(queryText, isReQuery));

1289-1298: Good removal of global query delay.

Removing the 15ms delay for global queries improves responsiveness, especially for high-frequency queries. The commented-out code should be fully removed in a cleanup pass.

-/*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;
-}*/
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96bb07d and 56a3551.

📒 Files selected for processing (1)
  • Flow.Launcher/ViewModel/MainViewModel.cs (11 hunks)
🧰 Additional context used
🧠 Learnings (1)
Flow.Launcher/ViewModel/MainViewModel.cs (1)
Learnt from: Yusyuriv
PR: Flow-Launcher/Flow.Launcher#3118
File: Flow.Launcher/ViewModel/MainViewModel.cs:1404-1413
Timestamp: 2024-12-08T21:12:12.060Z
Learning: In the `MainViewModel` class, the `_lastQuery` field is initialized in the constructor and is never null.
🔇 Additional comments (5)
Flow.Launcher/ViewModel/MainViewModel.cs (5)

35-35: Good replacement of boolean flag with more informative object.

Replacing a boolean flag with the actual query object provides more context and enables better tracking of the running query state.


646-652: Good dispatcher thread safety check.

Adding a check for dispatcher access before executing UI thread operations prevents potential deadlocks and UI freezing. This change ensures that UI updates are properly marshaled to the UI thread.


1080-1093: Good separation of query handling by result type.

Splitting the query handling logic based on the selected result type (Results, ContextMenu, or History) improves code organization and maintainability.


1835-1835: Good resource cleanup practice.

Adding disposal of the semaphore in the Dispose method prevents resource leaks and follows proper IDisposable pattern implementation.


1357-1419: Improved QueryTaskAsync local function with better error handling and logging.

The local function QueryTaskAsync has been enhanced with better error handling, cancellation checks, and detailed logging. These improvements help with debugging and make the code more robust.

@Jack251970 Jack251970 requested a review from Copilot April 17, 2025 12:00
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 9 out of 10 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • Flow.Launcher/Languages/en.xaml: Language not supported

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
Flow.Launcher/ViewModel/MainViewModel.cs (1)

658-658: ⚠️ Potential issue

isReQuery flag lost when query text changes.

Inside the "text changed" branch you hard-code isReQuery: false, therefore a caller that wants a re-query (e.g. after settings change) cannot force it when the query string happens to be modified at the same time.

-Query(false, isReQuery: false);
+Query(false, isReQuery);
🧹 Nitpick comments (1)
Flow.Launcher/ViewModel/MainViewModel.cs (1)

1357-1370: Using token from shared field in query task.

This code relies on _updateSource.Token inside the local function, which could lead to race conditions if _updateSource is reassigned during query processing.

Consider passing the token as a parameter to the function:

-async Task QueryTaskAsync(PluginPair plugin)
+async Task QueryTaskAsync(PluginPair plugin, CancellationToken token)
{
    if (searchDelay)
    {
        var searchDelayTime = plugin.Metadata.SearchDelayTime ?? Settings.SearchDelayTime;

-        await Task.Delay(searchDelayTime, _updateSource.Token);
+        await Task.Delay(searchDelayTime, token);

-        if (_updateSource.Token.IsCancellationRequested)
+        if (token.IsCancellationRequested)
        {
            App.API.LogDebug(ClassName, $"Cancel for QueryText 3: {QueryText}");
            return;
        }
    }
    // ...
}

// And when calling:
-var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch
+var capturedToken = _updateToken;
+var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch
{
-    false => QueryTaskAsync(plugin),
+    false => QueryTaskAsync(plugin, capturedToken),
    true => Task.CompletedTask
}).ToArray();
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab95342 and 186cd2c.

📒 Files selected for processing (1)
  • Flow.Launcher/ViewModel/MainViewModel.cs (11 hunks)
🧰 Additional context used
🧠 Learnings (1)
Flow.Launcher/ViewModel/MainViewModel.cs (1)
Learnt from: Yusyuriv
PR: Flow-Launcher/Flow.Launcher#3118
File: Flow.Launcher/ViewModel/MainViewModel.cs:1404-1413
Timestamp: 2024-12-08T21:12:12.060Z
Learning: In the `MainViewModel` class, the `_lastQuery` field is initialized in the constructor and is never null.
🔇 Additional comments (10)
Flow.Launcher/ViewModel/MainViewModel.cs (10)

35-35: Good addition of _runningQuery field for tracking active queries.

This field helps track which query is currently running, enabling better handling of concurrent query scenarios and supporting the proper display of progress indicators.


46-48: Good addition of SemaphoreSlim for query synchronization.

Adding the SemaphoreSlim helps ensure only one query update flow is active at a time, preventing race conditions. The descriptive comments also improve code maintainability.


646-651: Good dispatcher access check to prevent UI thread blocking.

Checking access with CheckAccess() before execution is an important improvement that prevents potential UI freezes that could occur if the method is called from a background thread.


679-682: Good dispatcher access handling in async method.

The async version correctly uses InvokeAsync instead of Invoke which prevents blocking a thread pool thread while waiting for the dispatcher.


1080-1093: Improved query routing with debug logging.

The Query method now properly routes to different query methods based on the selected results view with added debug logging which will help troubleshoot issues in production.


1203-1232: Good handling of null query results from shortcuts.

The code properly handles the case when a query is null after shortcut expansion, releasing resources and providing informative debug logs about the operations.


1236-1254: Good semaphore usage and async flow management.

The code properly acquires the semaphore before starting the query process and correctly handles cancellation states. The switch to the ThreadPool thread with await TaskScheduler.Default is also a good practice.


1298-1313: Improved progress bar visibility logic.

The progress bar is now only shown after a delay if the query is still running, which should reduce flickering for fast queries and provide a better user experience.


1421-1447: Changed parameter type to support base shortcut model hierarchy.

The ConstructQuery method now accepts IEnumerable<BaseBuiltinShortcutModel> instead of a specific type, which allows handling both synchronous and asynchronous shortcut models.


1834-1834: Good resource cleanup for _updateLock in Dispose method.

Adding the semaphore to the Dispose method ensures proper resource cleanup and prevents memory leaks.

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
Flow.Launcher/ViewModel/MainViewModel.cs (2)

660-682: 🛠️ Refactor suggestion

Use asynchronous dispatcher methods to prevent UI thread blocking

Using synchronous Dispatcher.Invoke can lead to deadlocks if the UI thread is busy. The asynchronous counterpart already uses InvokeAsync (line 695), which is preferred.

- Application.Current.Dispatcher.Invoke(() => ChangeQueryText(queryText, isReQuery));
+ await Application.Current.Dispatcher.InvokeAsync(() => ChangeQueryText(queryText, isReQuery));

Additionally, inside the "text changed" branch you hard-code isReQuery: false, therefore a caller that wants a re-query cannot force it when the query string happens to be modified at the same time.

- Query(false, isReQuery: false);
+ Query(false, isReQuery);

1463-1507: ⚠️ Potential issue

Potential deadlock risk in BuildQuery with synchronous execution of async operations

Using App.JTF.Run(() => asyncShortcut.ExpandAsync()) synchronously blocks the calling thread until the async operation completes. If ExpandAsync attempts to access the UI thread (e.g., for clipboard operations), this can cause a deadlock if called from the UI thread.

Consider making ConstructQuery and callers async to properly await async expansions:

private async Task<Query> ConstructQueryAsync(string queryText, IEnumerable<CustomShortcutModel> customShortcuts,
    IEnumerable<BaseBuiltinShortcutModel> builtInShortcuts)
{
    // ... existing code ...
    
    // 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)
{
    // ... existing code ...
    
    foreach (var shortcut in builtInShortcuts)
    {
        try
        {
            if (customExpanded.Contains(shortcut.Key))
            {
                string expansion;
                if (shortcut is BuiltinShortcutModel syncShortcut)
                {
                    expansion = syncShortcut.Expand();
                }
                else if (shortcut is AsyncBuiltinShortcutModel asyncShortcut)
                {
                    expansion = await asyncShortcut.ExpandAsync();
                }
                else
                {
                    continue;
                }
                queryBuilder.Replace(shortcut.Key, expansion);
                queryBuilderTmp.Replace(shortcut.Key, expansion);
                queryChanged = true;
            }
        }
        catch (Exception e)
        {
            App.API.LogException(ClassName, $"Error when expanding shortcut {shortcut.Key}", e);
        }
    }
    
    // ... rest of the method ...
}
🧹 Nitpick comments (1)
Flow.Launcher/ViewModel/MainViewModel.cs (1)

1250-1368: Good threading model improvements, but consider adding a timeout to semaphore wait

The code properly switches to a ThreadPool thread using await TaskScheduler.Default after acquiring the lock, which is good practice. However, consider adding a timeout to _updateLock.WaitAsync() to prevent potential hangs if a lock is never released.

- await _updateLock.WaitAsync(CancellationToken.None);
+ await _updateLock.WaitAsync(_updateSource?.Token ?? CancellationToken.None);

Or with a timeout:

await _updateLock.WaitAsync(TimeSpan.FromSeconds(10));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7961615 and e5b5ec5.

📒 Files selected for processing (1)
  • Flow.Launcher/ViewModel/MainViewModel.cs (14 hunks)
🧰 Additional context used
🧠 Learnings (1)
Flow.Launcher/ViewModel/MainViewModel.cs (1)
Learnt from: Yusyuriv
PR: Flow-Launcher/Flow.Launcher#3118
File: Flow.Launcher/ViewModel/MainViewModel.cs:1404-1413
Timestamp: 2024-12-08T21:12:12.060Z
Learning: In the `MainViewModel` class, the `_lastQuery` field is initialized in the constructor and is never null.
🔇 Additional comments (5)
Flow.Launcher/ViewModel/MainViewModel.cs (5)

35-48: Good refactoring of concurrency controls!

The change from a boolean flag to a more structured approach using _runningQuery, _updateSource, and _updateLock improves thread safety and cancellation handling. This is a good architectural improvement.


1217-1246: Good cancellation and cleanup handling with proper lock usage

The null query handling with appropriate resource cleanup shows good attention to detail. The use of semaphore with try/finally ensures resources are properly released even in error scenarios.


1379-1395: Good cancellation check improvements with detailed logging

The enhanced cancellation checks and logging throughout the query execution path will greatly improve debugging and prevent wasted work on cancelled queries.


1435-1461: Good method refactoring to support new shortcut model

The refactoring to support the BaseBuiltinShortcutModel parameter type provides better flexibility for handling both synchronous and asynchronous shortcuts.


1848-1848: Good resource cleanup in Dispose method

Adding the null check with ? operator for _updateLock?.Dispose() is a good practice to avoid potential null reference exceptions during application shutdown.

This comment has been minimized.

This comment has been minimized.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
Flow.Launcher/ViewModel/MainViewModel.cs (2)

659-685: ⚠️ Potential issue

Fixed potential UI thread issues but lost isReQuery parameter.

The dispatcher check is a good improvement to prevent blocking the UI thread. However, the implementation has a bug at line 672 where isReQuery parameter is ignored when calling Query() - hard-coding false will prevent re-querying when explicitly requested.

Apply this fix to ensure the isReQuery parameter is preserved:

-Query(false, isReQuery: false);
+Query(false, isReQuery: isReQuery);

46-49: 💡 Verification agent

🧩 Analysis chain

Good addition of semaphore for synchronization.

Using a SemaphoreSlim is an appropriate concurrency control mechanism to ensure only one query updating flow runs at a time. Consider passing cancellation tokens to WaitAsync calls to avoid potential deadlocks if the owning task never releases the semaphore.


🏁 Script executed:

#!/bin/bash
# Check if _updateLock.WaitAsync() calls include cancellation tokens throughout the codebase
rg "(_updateLock|updateLock).WaitAsync\(\s*\)" -A 1 --multiline

Length of output: 65


🏁 Script executed:

#!/bin/bash
# Search for all WaitAsync invocations on _updateLock
echo "=== _updateLock.WaitAsync calls ==="
rg "_updateLock\.WaitAsync" -n

# Search for any WaitAsync calls in the repository
echo "=== All WaitAsync( calls ==="
rg "WaitAsync\(" -n

Length of output: 3193


Use the real cancellation token when waiting on the semaphore

Currently the two WaitAsync calls in MainViewModel.cs use CancellationToken.None, which can never cancel and could still deadlock if a token was requested:

• Flow.Launcher/ViewModel/MainViewModel.cs:

  • Line 1233:
  • await _updateLock.WaitAsync(CancellationToken.None);
  • await _updateLock.WaitAsync(_updateToken);
  • Line 1259:
  • await _updateLock.WaitAsync(CancellationToken.None);
  • await _updateLock.WaitAsync(_updateToken);
    
    

By passing _updateToken (from your CancellationTokenSource), any queued waiters will throw if the update is cancelled, avoiding potential deadlocks.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30a840a and b918d00.

📒 Files selected for processing (1)
  • Flow.Launcher/ViewModel/MainViewModel.cs (14 hunks)
🧰 Additional context used
🧠 Learnings (1)
Flow.Launcher/ViewModel/MainViewModel.cs (1)
Learnt from: Yusyuriv
PR: Flow-Launcher/Flow.Launcher#3118
File: Flow.Launcher/ViewModel/MainViewModel.cs:1404-1413
Timestamp: 2024-12-08T21:12:12.060Z
Learning: In the `MainViewModel` class, the `_lastQuery` field is initialized in the constructor and is never null.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: gitStream.cm
🔇 Additional comments (8)
Flow.Launcher/ViewModel/MainViewModel.cs (8)

34-36: Improved state tracking with _runningQuery object.

Replacing a boolean flag with a Query object provides richer state information to track the currently running query, which is a good improvement for maintainability and debuggability.


692-697: Improved async dispatcher invocation pattern.

The recursive invocation pattern is a good approach to ensure the method runs on the UI thread. Using InvokeAsync instead of Invoke avoids blocking thread pool threads, which is particularly important for async operations.


1094-1107: Improved query dispatch with appropriate logging.

The method now correctly routes queries to the appropriate handler based on the selected results view, with helpful debug logging to track execution flow.


1217-1279: Good enhancements to query processing with improved state management.

The changes include:

  1. More robust error handling and cleanup for cancelled or expanded queries
  2. Better query state tracking with the _runningQuery object
  3. Proper semaphore usage for synchronization
  4. Comprehensive debug logging for improved diagnostics

This is a significant improvement to the query execution flow.


1317-1325: Performance improvement by removing unnecessary delay.

Removing the commented-out 15ms delay improves responsiveness, particularly for quick query changes. This is a good optimization.


1345-1350: Simplified plugin query task creation using pattern matching.

The use of pattern matching with switch expression is a clean, modern approach to conditionally create query tasks based on plugin status.


1450-1476: Support for polymorphic shortcut models.

Changing the parameter type to IEnumerable<BaseBuiltinShortcutModel> allows for more flexibility by supporting different types of shortcut models in the same collection. This is a good design enhancement that supports the new asynchronous shortcut expansion feature.


1863-1864: Proper resource cleanup with semaphore disposal.

Adding the semaphore to the Dispose method is crucial to prevent resource leaks. The null-conditional operator ?. is also a good practice to avoid null reference exceptions.

@Jack251970 Jack251970 changed the title Fix copy to clipboard STA thread issue & Support retry for copy & Async build-in shortcut model & Fix build shortcuts text replace issue Fix copy to clipboard STA thread issue & Support retry for copy & Async build-in shortcut model & Refactor query update logic & Fix build shortcuts text replace issue Apr 17, 2025

This comment has been minimized.

Copy link

@check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, or 📝 job summary for details.

❌ Errors Count
❌ forbidden-pattern 22
⚠️ non-alpha-in-dictionary 19

See ❌ Event descriptions for more information.

If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
10 min review bug Something isn't working Code Refactor Dev branch only An issue or fix for the Dev branch build enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BUG: CopyToClipboard method throws an error for JSON-RPC v2 plugins
4 participants