Skip to content

refactor: Introduce specific context menus and operations for extension#77

Merged
qdaxb merged 4 commits into
mainfrom
ContextMenu_20250821
Aug 22, 2025
Merged

refactor: Introduce specific context menus and operations for extension#77
qdaxb merged 4 commits into
mainfrom
ContextMenu_20250821

Conversation

@Micro66

@Micro66 Micro66 commented Aug 21, 2025

Copy link
Copy Markdown
Contributor
  1. Introduce specific context menus and operations for extension
  2. Fix the review issues encountered during the release to the store.

Summary by CodeRabbit

  • New Features
    • Dynamic “Extension Actions” context menu tailored to the active extension and selection.
    • Added Roo Code and Cline providers with extension-specific actions and visibility controls.
  • Improvements
    • Smoother UI updates for buttons and context menus via safer UI-thread scheduling.
    • Extension switching now distinguishes immediate vs next-restart effects.
    • Extensions are resolved from the app’s config directory for improved portability.
  • Removed
    • Legacy static right‑click code-action menu and Extension Resource Status action, replaced by the dynamic “Extension Actions” menu.

@coderabbitai

coderabbitai Bot commented Aug 21, 2025

Copy link
Copy Markdown

Walkthrough

Adds a forceRestart-aware extension switching flow, introduces a dynamic, extension-provided context menu framework, implements RooCode and Cline context menu providers, refactors Roo prompt constants/APIs, updates UI refresh logic for dynamic buttons, adjusts extension installation lookup paths, and removes legacy code-action and status action files. Updates plugin.xml to wire the dynamic context menu group.

Changes

Cohort / File(s) Summary
Extension switching flow
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionManager.kt, .../extensions/core/ExtensionSwitcher.kt
Adds forceRestart parameter to setCurrentProvider and threads it through switcher; initializes provider and config always; UI updates and notifications gated when forceRestart is false; logging updated.
Context menu framework
.../ui/contextmenu/ContextMenuConfiguration.kt, .../ui/contextmenu/ExtensionContextMenuProvider.kt, .../ui/contextmenu/DynamicContextMenuManager.kt, .../ui/contextmenu/DynamicExtensionContextMenuGroup.kt, jetbrains_plugin/src/main/resources/META-INF/plugin.xml
Introduces context menu API (configuration, provider), dynamic manager and action group that populate actions based on active extension and selection; plugin.xml replaces static group with dynamic group and labels.
Extension-specific providers
.../extensions/plugin/roo/RooCodeContextMenuProvider.kt, .../extensions/plugin/cline/ClineContextMenuProvider.kt
Adds RooCode and Cline context menu providers; RooCode includes concrete actions and webview dispatch; Cline provides configuration and empty action list.
Roo prompt API refactor
.../extensions/plugin/roo/RooCodeActionConstants.kt
Removes legacy action/command constants; adds RooCodeSupportPrompt types, configs, and factory methods; updates names and templates for prompt generation.
Dynamic buttons refresh
.../extensions/ui/buttons/DynamicButtonManager.kt
Changes toolbar refresh to schedule on EDT via ApplicationManager.invokeLater; removes dummy event update; adds try/catch logging.
Extension install path resolution
.../core/ExtensionHostManager.kt, .../extensions/core/VsixManager.kt, .../extensions/plugin/cline/ClineExtensionProvider.kt, .../extensions/plugin/roo/RooExtensionProvider.kt
Switches base path from $HOME/.run-vs-agent/plugins to getUserConfigDir()/plugins via VsixManager.getBaseDirectory; updates availability/path checks accordingly.
Removed legacy actions
.../actions/RegisterCodeActions.kt, .../extensions/ui/actions/ExtensionResourceStatusAction.kt
Deletes code-action registration/handling and an extension resource status action; removes related public APIs and classes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant UI as UI (Switcher)
  participant EM as ExtensionManager
  participant Btn as DynamicButtonManager
  participant Ctx as DynamicContextMenuManager
  Note over UI,EM: Extension switch with forceRestart flag
  User->>UI: Choose extension (id, forceRestart)
  UI->>EM: setCurrentProvider(id, forceRestart)
  EM-->>UI: Boolean result
  alt forceRestart == true
    Note over EM: Initialize provider + save config<br/>(no currentProvider/UI swap)
    UI-->>Btn: (no-op)
    UI-->>Ctx: (no-op)
  else forceRestart == false
    Note over EM: Initialize provider + set currentProvider<br/>+ save config
    EM->>Btn: Schedule button refresh
    EM->>Ctx: Schedule context menu refresh
  end
Loading
sequenceDiagram
  autonumber
  participant IDE as IDE Editor
  participant Group as DynamicExtensionContextMenuGroup
  participant Mgr as DynamicContextMenuManager
  participant Prov as ExtensionContextMenuProvider
  Note over Group,Mgr: Context menu population on selection
  IDE->>Group: update(event)
  Group->>IDE: Check editor & selection
  alt has selection
    Group->>Mgr: getInstance(project).initialize()
    Mgr->>Mgr: Resolve current extension id
    Mgr->>Prov: getContextMenuActions(project)
    Prov-->>Mgr: List<AnAction>
    Mgr-->>Group: Actions
    Group->>Group: Replace group children with actions
  else no selection
    Group->>Group: Clear actions
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

In burrows of code I twitch my nose,
New menus bloom where selection grows.
Switch with a hop, restart or not,
Roo and Cline share a cozy spot.
Paths realigned, old twigs pruned—
Buttons refreshed, the UI tuned. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ContextMenu_20250821

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

@Micro66 Micro66 requested a review from qdaxb August 21, 2025 08:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionManager.kt (1)

137-138: Suspicious default provider assignment

The code has a commented-out line preferring "roo-code" as default, but the active line prefers "cline" instead. The log message on line 141 still says "roo-code (preferred)" which is misleading.

Fix the inconsistency between the actual default provider and the log message:

-//             val rooProvider = availableProviders.find { it.getExtensionId() == "roo-code" }
             val rooProvider = availableProviders.find { it.getExtensionId() == "cline" }
             if (rooProvider != null) {
                 currentProvider = rooProvider
-                LOG.info("Set default extension provider: roo-code (preferred)")
+                LOG.info("Set default extension provider: cline (preferred)")
🧹 Nitpick comments (23)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RightClickChatActionGroup.kt (2)

13-16: Good deprecation notice; add cross-reference for discoverability

Consider adding a KDoc @see to explicitly point readers to DynamicExtensionContextMenuGroup for faster navigation.

 /**
  * Right-click menu code action group, similar to VSCode's code action provider.
  * This class manages the dynamic actions that appear in the context menu when text is selected.
  * 
- * @deprecated This class is deprecated in favor of DynamicExtensionContextMenuGroup.
- * Use the new extension-based context menu system for better extensibility.
+ * @deprecated This class is deprecated in favor of DynamicExtensionContextMenuGroup.
+ * Use the new extension-based context menu system for better extensibility.
+ * @see com.sina.weibo.agent.extensions.ui.contextmenu.DynamicExtensionContextMenuGroup
  * 
  * Implements DumbAware to ensure the action works during indexing, and ActionUpdateThreadAware
  * to specify which thread should handle action updates.
  */

20-21: Add ReplaceWith and explicit level to the deprecation for better migration UX

Providing ReplaceWith helps IDE quick-fix users jump to the new API; an explicit level documents intended severity.

-@Deprecated("Use DynamicExtensionContextMenuGroup instead for extension-based context menus")
+@Deprecated(
+  message = "Use DynamicExtensionContextMenuGroup instead for extension-based context menus",
+  replaceWith = ReplaceWith(
+    "com.sina.weibo.agent.extensions.ui.contextmenu.DynamicExtensionContextMenuGroup"
+  ),
+  level = DeprecationLevel.WARNING
+)
 class RightClickChatActionGroup : DefaultActionGroup(), DumbAware, ActionUpdateThreadAware {
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ContextMenuConfiguration.kt (2)

7-21: Provide a default implementation to reduce duplication across providers

Most providers will implement isActionVisible and then mechanically build getVisibleActions. Offer a default implementation that derives the list from isActionVisible.

 interface ContextMenuConfiguration {
-    
-
     /**
      * Check if a specific context menu action should be visible.
      * @param actionType The type of context menu action
      * @return true if the action should be visible, false otherwise
      */
     fun isActionVisible(actionType: ContextMenuActionType): Boolean
-    
+
     /**
      * Get all visible context menu actions.
      * @return List of visible action types
      */
-    fun getVisibleActions(): List<ContextMenuActionType>
+    fun getVisibleActions(): List<ContextMenuActionType> =
+        enumValues<ContextMenuActionType>().filter(::isActionVisible)
 }

27-34: Consider marking this API surface as experimental while it settles

If you anticipate churn, annotate with ApiStatus.Experimental to set expectations for ext authors.

+@org.jetbrains.annotations.ApiStatus.Experimental
 enum class ContextMenuActionType {
     EXPLAIN_CODE,
     FIX_CODE,
     FIX_LOGIC,
     IMPROVE_CODE,
     ADD_TO_CONTEXT,
     NEW_TASK
 }
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ExtensionContextMenuProvider.kt (2)

11-12: Stabilize the SPI: mark experimental now; you can lift it later

Flagging the extension SPI as experimental communicates potential breaking changes to extension implementers.

-interface ExtensionContextMenuProvider {
+@org.jetbrains.annotations.ApiStatus.Experimental
+interface ExtensionContextMenuProvider {

38-50: Add an overload that accepts DataContext to enable richer, selection-aware actions

This preserves the current signature while allowing providers that need the DataContext (e.g., caret, PSI) to use it without reconstructing an AnActionEvent.

-import com.intellij.openapi.project.Project
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.actionSystem.DataContext
@@
-    fun getContextMenuActions(project: Project): List<AnAction>
+    fun getContextMenuActions(project: Project): List<AnAction>
+    /**
+     * Optional: Provide actions with access to DataContext.
+     * Default implementation falls back to the simpler overload.
+     */
+    fun getContextMenuActions(project: Project, dataContext: DataContext): List<AnAction> =
+        getContextMenuActions(project)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/ActionConstants.kt (4)

11-16: Deprecation is good; add ReplaceWith targets for smoother migrations

Providing ReplaceWith helps IDE users auto-migrate to extension-specific constants.

-@Deprecated("Use extension-specific action names instead")
+@Deprecated(
+  message = "Use extension-specific action names instead",
+  replaceWith = ReplaceWith(
+    "com.sina.weibo.agent.extensions.plugin.roo.RooCodeActionNames"
+  ),
+  level = DeprecationLevel.WARNING
+)
 object ActionNames {

18-35: Add ReplaceWith to each constant; apply similar to FIX, FIX_LOGIC, IMPROVE, ADD_TO_CONTEXT, NEW_TASK

Concrete replacements improve quick-fix accuracy.

-    @Deprecated("Use RooCodeActionNames.EXPLAIN or Cline-specific constants")
-    const val EXPLAIN = "roo-cline: Explain Code"
+    @Deprecated(
+        message = "Use RooCodeActionNames.EXPLAIN or Cline-specific constants",
+        replaceWith = ReplaceWith(
+            "RooCodeActionNames.EXPLAIN",
+            "com.sina.weibo.agent.extensions.plugin.roo.RooCodeActionNames"
+        )
+    )
+    const val EXPLAIN = "roo-cline: Explain Code"

44-61: Mirror ReplaceWith on CommandIds to aid migration of action wiring

Same rationale as ActionNames; include ReplaceWith on each ID constant.

-@Deprecated("Use extension-specific command IDs instead")
+@Deprecated(
+  message = "Use extension-specific command IDs instead",
+  replaceWith = ReplaceWith(
+    "com.sina.weibo.agent.extensions.plugin.roo.RooCodeCommandIds"
+  ),
+  level = DeprecationLevel.WARNING
+)
 object CommandIds {
@@
-    @Deprecated("Use RooCodeCommandIds.EXPLAIN or Cline-specific command IDs")
+    @Deprecated(
+        message = "Use RooCodeCommandIds.EXPLAIN or Cline-specific command IDs",
+        replaceWith = ReplaceWith(
+            "RooCodeCommandIds.EXPLAIN",
+            "com.sina.weibo.agent.extensions.plugin.roo.RooCodeCommandIds"
+        )
+    )
     const val EXPLAIN = "roo-cline.explainCode"

247-318: Minor perf/readability: precompile the placeholder regex

Avoid recompiling the same Regex for every createPrompt call.

 object SupportPrompt {
+    // Precompiled for reuse across prompt creations
+    private val PLACEHOLDER_PATTERN = Regex("""\$\{(.*?)}""")
@@
-    private fun createPrompt(template: String, params: PromptParams): String {
-        val pattern = Regex("""\$\{(.*?)}""")
-        return pattern.replace(template) { matchResult ->
+    private fun createPrompt(template: String, params: PromptParams): String {
+        return PLACEHOLDER_PATTERN.replace(template) { matchResult ->
             val key = matchResult.groupValues[1]
             if (key == "diagnosticText") {
                 generateDiagnosticText(params["diagnostics"] as? List<Map<String, Any?>>)
             } else if (params.containsKey(key)) {
                 // Ensure the value is treated as a string for replacement
                 val value = params[key]
                 when (value) {
                     is String -> value
                     else -> {
                         // Convert non-string values to string for replacement
                         value?.toString() ?: ""
                     }
                 }
             } else {
                 // If the placeholder key is not in params, replace with empty string
                 ""
             }
         }
     }
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicExtensionContextMenuGroup.kt (1)

55-63: Consider caching the context menu manager

The initialization of contextMenuManager happens on every menu update when it's null, but if initialization fails once, it will be retried on every subsequent menu update. This could impact performance if the initialization is expensive or if it consistently fails.

Consider adding a flag to track initialization attempts:

 private var contextMenuManager: DynamicContextMenuManager? = null
+private var initializationAttempted = false

 private fun loadDynamicContextMenuActions(e: AnActionEvent) {
     val project = e.project ?: return
     
     // Get or initialize the context menu manager
-    if (contextMenuManager == null) {
+    if (contextMenuManager == null && !initializationAttempted) {
         try {
+            initializationAttempted = true
             contextMenuManager = DynamicContextMenuManager.getInstance(project)
             contextMenuManager?.initialize()
         } catch (e: Exception) {
             // If the manager is not available, fall back to default actions
             return
         }
     }
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt (1)

249-270: Potential regex injection vulnerability in template processing

The createPrompt function uses regex replacement with user-controlled input. While the current implementation appears safe because it only replaces specific placeholder patterns, care should be taken if this logic is extended.

Consider using a more robust template engine or escaping mechanism if this functionality expands:

 private fun createPrompt(template: String, params: RooCodePromptParams): String {
     val pattern = Regex("""\$\{(.*?)}""")
     return pattern.replace(template) { matchResult ->
         val key = matchResult.groupValues[1]
+        // Validate key to prevent potential issues
+        if (!key.matches(Regex("[a-zA-Z0-9_]+"))) {
+            return@replace matchResult.value // Return original if invalid
+        }
         if (key == "diagnosticText") {
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt (4)

117-135: “Fix Logic” action is routed to the generic “fixCode” command — confirm intent and consider discrete command/prompt

UI shows “Fix Logic”, but the command string and prompt type are the same as “Fix Code”. If the backend differentiates logic-fix vs. general fix, wire the right command ID and/or prompt. Also, prefer using the constants introduced in RooCodeActionConstants.kt to avoid string drift.

Would you like me to patch this to use RooCodeActionConstants (command IDs and names) once you confirm the canonical identifiers?


339-416: Unused prompt helpers (createPrompt/getPromptTemplate/replacePlaceholders)

These helpers aren’t used (you rely on SupportPrompt.create). Keeping them increases maintenance burden.

Apply this diff to remove the unused block:

-        /**
-         * Creates a prompt by replacing placeholders in a template with actual values.
-         */
-        fun createPrompt(promptType: String, params: Map<String, Any?>): String { ... }
-
-        /**
-         * Gets the template for a specific prompt type.
-         */
-        fun getPromptTemplate(type: String): String { ... }
-
-        /**
-         * Replaces placeholders in a template with actual values.
-         */
-        fun replacePlaceholders(template: String, params: Map<String, Any?>): String { ... }

If you intend to keep custom templates, please move them to a shared location to keep parity with other providers.


70-86: Return-on-null silently drops user actions; add lightweight diagnostics

When editor/selection/webview are null the action simply returns. Add a debug log (you already instantiate a logger in each action) so we can trace why an action didn’t fire. Optionally show a balloon notification to guide the user.

Example for one action (replicate similarly):

-            val effectiveRange = RooCodeContextMenuProvider.getEffectiveRange(editor)
-            if (effectiveRange == null) return
+            val effectiveRange = RooCodeContextMenuProvider.getEffectiveRange(editor)
+            if (effectiveRange == null) {
+                logger.debug("ExplainCodeAction skipped: no selection in editor ${editor.document}")
+                return
+            }

Also applies to: 95-111, 120-136, 145-161, 170-186, 195-211


31-35: isAvailable() always returns true — consider delegating to extension configuration

If availability should reflect actual configuration (e.g., active extension id), delegate this check to ExtensionManager/DynamicContextMenuManager to avoid showing actions when the extension isn’t selected.

jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt (3)

47-51: Prefer idiomatic Kotlin call: ExtensionManager.getInstance(project)

Accessing Companion explicitly is non-idiomatic and unnecessary.

-            val extensionManager = ExtensionManager.Companion.getInstance(project)
+            val extensionManager = ExtensionManager.getInstance(project)

96-107: Avoid re-instantiating providers; cache per extension to preserve state and reduce churn

getContextMenuProvider() creates a new provider each call. Caching avoids unnecessary allocations and lets providers maintain internal state if needed.

Apply this refactor:

+import java.util.concurrent.ConcurrentHashMap
...
 class DynamicContextMenuManager(private val project: Project) {
@@
-    private fun getContextMenuProvider(extensionId: String?): ExtensionContextMenuProvider? {
-        if (extensionId == null) return null
-        
-        return when (extensionId) {
-            "roo-code" -> RooCodeContextMenuProvider()
-            "cline" -> ClineContextMenuProvider()
-            else -> null
-        }
-    }
+    private val providerCache = ConcurrentHashMap<String, ExtensionContextMenuProvider>()
+
+    private fun getContextMenuProvider(extensionId: String?): ExtensionContextMenuProvider? {
+        if (extensionId == null) return null
+        return providerCache.computeIfAbsent(extensionId) {
+            when (it) {
+                "roo-code" -> RooCodeContextMenuProvider()
+                "cline" -> ClineContextMenuProvider()
+                else -> return null
+            }
+        }
+    }

152-170: Default config exposes only EXPLAIN and ADD_TO_CONTEXT — confirm UX alignment

If the goal is parity across extensions when no provider is active, consider also exposing IMPROVE_CODE (harmless, helpful) unless product requirements say otherwise.

jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt (4)

281-288: “New Task” currently uses sendMessage; verify whether it should open a new task/session

Roo uses “initClineWithTask” for new tasks. If Cline also expects a new session, align the invoke channel to initClineWithTask; otherwise, confirm sendMessage is correct.

Proposed change if a new session is desired:

-                        "invoke" to "sendMessage",
+                        "invoke" to "initClineWithTask",

167-173: Remove unused local variable ‘range’

range is created and never used; keep only the map passed to executeCommand.

-            val range = ClineContextMenuProvider.Range(
-                startLine = effectiveRange.startLine,
-                startCharacter = effectiveRange.startCharacter,
-                endLine = effectiveRange.endLine,
-                endCharacter = effectiveRange.endCharacter
-            )

82-101: Add minimal diagnostics on early returns

Like Roo, these actions silently no-op on missing editor/selection/webview. Add debug logs or user notifications to aid troubleshooting.

Example:

-            val effectiveRange = ClineContextMenuProvider.getEffectiveRange(editor)
-            if (effectiveRange == null) return
+            val effectiveRange = ClineContextMenuProvider.getEffectiveRange(editor)
+            if (effectiveRange == null) {
+                logger.debug("ClineExplainCodeAction skipped: no selection")
+                return
+            }

Also applies to: 107-125, 131-150, 156-182, 188-207


296-372: Consolidate prompt/placeholder utilities across providers

Both Roo and Cline define near-identical placeholder utilities. Consider centralizing in a shared utility (or rely solely on SupportPrompt) to avoid divergence.

📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1f7699f and 008ed8d.

📒 Files selected for processing (13)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/ActionConstants.kt (2 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RightClickChatActionGroup.kt (1 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionManager.kt (2 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionSwitcher.kt (2 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt (1 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt (1 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt (1 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/buttons/DynamicButtonManager.kt (1 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ContextMenuConfiguration.kt (1 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt (1 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicExtensionContextMenuGroup.kt (1 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ExtensionContextMenuProvider.kt (1 hunks)
  • jetbrains_plugin/src/main/resources/META-INF/plugin.xml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (10)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RightClickChatActionGroup.kt (1)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt (2)
  • createAction (19-132)
  • text (142-146)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicExtensionContextMenuGroup.kt (1)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt (1)
  • createAction (19-132)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt (3)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt (1)
  • getContextMenuConfiguration (48-50)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt (1)
  • getContextMenuConfiguration (46-48)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ExtensionContextMenuProvider.kt (1)
  • getContextMenuConfiguration (49-49)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/buttons/DynamicButtonManager.kt (1)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actors/MainThreadMessageServiceShape.kt (1)
  • logger (21-114)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt (2)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/ActionConstants.kt (3)
  • generateDiagnosticText (255-263)
  • get (302-304)
  • createPrompt (272-293)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt (7)
  • createAction (19-132)
  • handleCodeAction (228-299)
  • registerCodeAction (157-201)
  • provideCodeActions (95-131)
  • text (142-146)
  • createAction (28-51)
  • actionPerformed (164-199)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionSwitcher.kt (1)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/core/ExtensionManager.kt (5)
  • id (262-281)
  • LOG (25-231)
  • parseExtensionDescriptionFromNewConfig (85-120)
  • vscode (253-256)
  • activateExtension (171-222)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt (1)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/plugin/WecoderPlugin.kt (1)
  • LOG (42-220)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ExtensionContextMenuProvider.kt (1)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt (2)
  • createAction (19-132)
  • provideCodeActions (95-131)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt (1)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt (4)
  • createAction (19-132)
  • text (142-146)
  • handleCodeAction (228-299)
  • actionPerformed (33-49)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/ActionConstants.kt (1)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt (6)
  • createAction (19-132)
  • handleCodeAction (228-299)
  • text (142-146)
  • actionPerformed (164-199)
  • createAction (28-51)
  • provideCodeActions (95-131)
🔇 Additional comments (8)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/buttons/DynamicButtonManager.kt (1)

123-124: Action ID Alignment Confirmed

The action ID "RunVSAgent.DynamicExtensionActions" in DynamicButtonManager.kt exactly matches the <group id="RunVSAgent.DynamicExtensionActions"> declaration in META-INF/plugin.xml (line 117). No changes are required.

jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ExtensionContextMenuProvider.kt (1)

36-37: Ensure returned actions are safe during indexing

Either return DumbAware actions or ensure heavy work is deferred to actionPerformed. This avoids UI stalls during indexing.

Would you like me to scan providers under extensions/plugin/** to flag any actions not implementing DumbAware?

jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionManager.kt (1)

184-200: Inconsistent UI update behavior

The UI components (buttons and context menu) are only updated when forceRestart == false, but the configuration is always saved. This could lead to a state where the configuration is updated but the UI doesn't reflect it until restart, which might confuse users.

Based on the logic, when forceRestart is true, the provider is initialized and configuration is saved, but currentProvider and UI remain unchanged. This seems counterintuitive - why would forcing a restart skip updating the current provider?

Consider clarifying the intent or adjusting the logic to be more predictable.

jetbrains_plugin/src/main/resources/META-INF/plugin.xml (1)

130-131: LGTM! Dynamic context menu integration looks good

The migration from the static RightClickChatActionGroup to the dynamic DynamicExtensionContextMenuGroup is well-implemented, enabling extension-specific context menu actions as intended.

jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionSwitcher.kt (1)

155-161: Logic inversion concern

When forceRestart is true, the code only updates button configuration and notifies changes, but based on the ExtensionManager implementation, this is when the currentProvider is NOT updated. This seems backwards - typically a "force restart" would imply more aggressive changes, not fewer.

The current logic appears to be:

  • forceRestart = false: Updates currentProvider, buttons, context menu
  • forceRestart = true: Only updates buttons and notifies changes

This seems inverted from typical expectations. Please verify if this is the intended behavior or if there's a logic error.

jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicExtensionContextMenuGroup.kt (1)

18-81: LGTM! Clean implementation of dynamic context menu group

The implementation correctly handles the dynamic loading of extension-specific context menu actions with proper null safety and exception handling. The use of EDT for UI updates is appropriate.

jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt (1)

1-296: Well-structured constants and prompt management for Roo Code extension

The file provides a clean separation of concerns with clear organization of action names, command IDs, and prompt templates. The documentation is comprehensive and the code follows Kotlin best practices.

jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt (1)

232-246: Good: VS Code-compatible selection semantics

getEffectiveRange returns start-inclusive/end-exclusive line/character positions, mirroring VS Code/LSP behavior, which is important for downstream tools.

Comment on lines 11 to 61
*
* @deprecated These constants are deprecated in favor of extension-specific constants.
* Use RooCodeActionNames for Roo Code extension or Cline-specific constants for Cline extension.
*/
@Deprecated("Use extension-specific action names instead")
object ActionNames {
/** Action to explain selected code */
@Deprecated("Use RooCodeActionNames.EXPLAIN or Cline-specific constants")
const val EXPLAIN = "roo-cline: Explain Code"
/** Action to fix issues in selected code */
@Deprecated("Use RooCodeActionNames.FIX or Cline-specific constants")
const val FIX = "roo-cline: Fix Code"
/** Action to fix logical issues in selected code */
@Deprecated("Use RooCodeActionNames.FIX_LOGIC or Cline-specific constants")
const val FIX_LOGIC = "roo-cline: Fix Logic"
/** Action to improve selected code */
@Deprecated("Use RooCodeActionNames.IMPROVE or Cline-specific constants")
const val IMPROVE = "roo-cline: Improve Code"
/** Action to add selected code to context */
@Deprecated("Use RooCodeActionNames.ADD_TO_CONTEXT or Cline-specific constants")
const val ADD_TO_CONTEXT = "roo-cline: Add to Context"
/** Action to create a new task */
@Deprecated("Use RooCodeActionNames.NEW_TASK or Cline-specific constants")
const val NEW_TASK = "roo-cline: New Task"
}

/**
* Command identifiers used for internal command registration and execution.
* These IDs are used to register commands with the IDE.
*
* @deprecated These command IDs are deprecated in favor of extension-specific command IDs.
* Use RooCodeCommandIds for Roo Code extension or Cline-specific command IDs for Cline extension.
*/
@Deprecated("Use extension-specific command IDs instead")
object CommandIds {
/** Command ID for explaining code */
@Deprecated("Use RooCodeCommandIds.EXPLAIN or Cline-specific command IDs")
const val EXPLAIN = "roo-cline.explainCode"
/** Command ID for fixing code */
@Deprecated("Use RooCodeCommandIds.FIX or Cline-specific command IDs")
const val FIX = "roo-cline.fixCode"
/** Command ID for improving code */
@Deprecated("Use RooCodeCommandIds.IMPROVE or Cline-specific command IDs")
const val IMPROVE = "roo-cline.improveCode"
/** Command ID for adding to context */
@Deprecated("Use RooCodeCommandIds.ADD_TO_CONTEXT or Cline-specific command IDs")
const val ADD_TO_CONTEXT = "roo-cline.addToContext"
/** Command ID for creating a new task */
@Deprecated("Use RooCodeCommandIds.NEW_TASK or Cline-specific command IDs")
const val NEW_TASK = "roo-cline.newTask"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Plan the removal window and find usages to migrate

Given these broad deprecations, it will help to (1) define a target release for removal and (2) locate remaining usages to schedule fixes.

Also applies to: 64-79, 87-239, 247-318


🏁 Script executed:

#!/bin/bash
# 1) Find usages of deprecated constants/prompt utilities across the codebase (excluding build dirs)
rg -nP '\b(ActionNames|CommandIds|SupportPrompt(Configs)?|SupportPromptType|PromptParams)\b' -g '!**/build/**'

# 2) Confirm no plugin.xml entries still reference deprecated command IDs or action groups
rg -nP 'roo-cline\.(explainCode|fixCode|improveCode|addToContext|newTask)|RightClick\.Chat' jetbrains_plugin/src/main/resources/META-INF/plugin.xml

Length of output: 4430


Plan removal window & migrate remaining deprecated constants

Please define a target release by which we will drop ActionNames and CommandIds, and in advance of that release migrate all remaining usages to the new extension-specific constants (e.g. RooCodeActionNames, Cline equivalents) and prompt configurations.

Remaining references to update:

  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt
    • Lines ~280, 295, 329: calls to SupportPrompt.create("ADD_TO_CONTEXT",…) and uses of deprecated prompt types
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt
    • Line ~286: call to SupportPrompt.create(basePromptType,…)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt
    • Lines ~101–102, 109–110, 117–118, 125–126: uses of ActionNames.* and CommandIds.*
    • Lines ~242, 257, 291: calls to SupportPrompt.create(…) with deprecated type strings
  • jetbrains_plugin/src/main/resources/META-INF/plugin.xml
    • Entry for <group id="RunVSAgent.RightClick.Chat" …> still references the old action group

By the chosen removal release, replace all of the above with the corresponding constants in RooCodeActionNames, RooCodeCommandIds, or your Cline-specific analogs (and updated prompt configs). This will ensure a smooth deprecation cycle and avoid last-minute breakage.

🤖 Prompt for AI Agents
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/ActionConstants.kt
lines 11-61: define a target release (e.g., vX.Y.Z) to remove ActionNames and
CommandIds, then migrate all remaining usages to extension-specific constants:
replace ActionNames.* and CommandIds.* in RegisterCodeActions.kt (lines ~101–126
and ~242,257,291) with RooCodeActionNames/RooCodeCommandIds or the Cline
equivalents; update RooCodeContextMenuProvider.kt (≈lines 280,295,329) and
ClineContextMenuProvider.kt (≈line 286) to call SupportPrompt.create with the
new prompt-type constants instead of string literals like "ADD_TO_CONTEXT" or
deprecated types; update plugin.xml entry for the RunVSAgent.RightClick.Chat
group to reference the new action IDs; run a build and tests to ensure no
unresolved references and adjust imports accordingly before merging.

Comment on lines +163 to +170
fun setCurrentProvider(extensionId: String, forceRestart: Boolean? = false): Boolean {
val provider = extensionProviders[extensionId]
if (provider != null && provider.isAvailable(project)) {
val oldProvider = currentProvider
currentProvider = provider

if (forceRestart == false) {
currentProvider = provider
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Potentially confusing parameter handling

The forceRestart parameter is declared as Boolean? but you're using direct equality comparisons with false. This could lead to unexpected behavior when null is passed. The nullable type suggests three states (true/false/null), but the implementation only handles true vs. non-true.

Consider simplifying the parameter type and logic:

-    fun setCurrentProvider(extensionId: String, forceRestart: Boolean? = false): Boolean {
+    fun setCurrentProvider(extensionId: String, forceRestart: Boolean = false): Boolean {
         val provider = extensionProviders[extensionId]
         if (provider != null && provider.isAvailable(project)) {
             val oldProvider = currentProvider
-            if (forceRestart == false) {
+            if (!forceRestart) {
                 currentProvider = provider
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun setCurrentProvider(extensionId: String, forceRestart: Boolean? = false): Boolean {
val provider = extensionProviders[extensionId]
if (provider != null && provider.isAvailable(project)) {
val oldProvider = currentProvider
currentProvider = provider
if (forceRestart == false) {
currentProvider = provider
}
fun setCurrentProvider(extensionId: String, forceRestart: Boolean = false): Boolean {
val provider = extensionProviders[extensionId]
if (provider != null && provider.isAvailable(project)) {
val oldProvider = currentProvider
if (!forceRestart) {
currentProvider = provider
}
// …rest of implementation…

try {
// Step 1: Update extension manager (this will save configuration)
updateExtensionManager(extensionId)
updateExtensionManager(extensionId, forceRestart)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Parameter type consistency issue with ExtensionManager

The forceRestart parameter is passed to extensionManager.setCurrentProvider(), but there's a type mismatch. ExtensionManager.setCurrentProvider() expects Boolean? while this method passes Boolean.

Either update ExtensionManager to use non-nullable Boolean (as suggested in the previous review), or ensure consistent types:

-    private suspend fun updateExtensionManager(extensionId: String, forceRestart: Boolean) {
+    private suspend fun updateExtensionManager(extensionId: String, forceRestart: Boolean?) {

Also applies to: 177-177, 182-182

🤖 Prompt for AI Agents
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionSwitcher.kt
around lines 153, 177, 182: the non-nullable Boolean forceRestart is passed into
ExtensionManager.setCurrentProvider which expects a nullable Boolean, causing a
type mismatch; fix by making types consistent—preferably update
ExtensionManager.setCurrentProvider signature to accept a non-nullable Boolean
(Boolean) so callers can pass forceRestart directly, or if you cannot change the
manager, convert the argument to nullable at call sites (e.g., pass forceRestart
as Boolean? by using forceRestart as Boolean?) ensuring all three call sites
(153, 177, 182) use the same approach.

Comment on lines +38 to +46
override fun getContextMenuActions(project: Project): List<AnAction> {
return listOf(
// ClineExplainCodeAction(),
// ClineFixCodeAction(),
// ClineImproveCodeAction(),
// ClineAddToContextAction(),
// ClineNewTaskAction()
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Context menu returns no actions — users won’t see any Cline items

All actions are commented out in getContextMenuActions(). Wire them up so the dynamic menu can display them.

     override fun getContextMenuActions(project: Project): List<AnAction> {
-        return listOf(
-//            ClineExplainCodeAction(),
-//
-//            ClineFixCodeAction(),
-//
-//            ClineImproveCodeAction(),
-//
-//            ClineAddToContextAction(),
-//
-//            ClineNewTaskAction()
-        )
+        return listOf(
+            ClineExplainCodeAction(),
+            ClineFixCodeAction(),
+            ClineImproveCodeAction(),
+            ClineAddToContextAction(),
+            ClineNewTaskAction()
+        )
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
override fun getContextMenuActions(project: Project): List<AnAction> {
return listOf(
// ClineExplainCodeAction(),
// ClineFixCodeAction(),
// ClineImproveCodeAction(),
// ClineAddToContextAction(),
// ClineNewTaskAction()
)
}
override fun getContextMenuActions(project: Project): List<AnAction> {
return listOf(
ClineExplainCodeAction(),
ClineFixCodeAction(),
ClineImproveCodeAction(),
ClineAddToContextAction(),
ClineNewTaskAction()
)
}

Comment on lines +238 to +246
return if (selectionModel.hasSelection()) {
val selectedText = selectionModel.selectedText ?: ""
val startLine = document.getLineNumber(selectionModel.selectionStart)
val endLine = document.getLineNumber(selectionModel.selectionEnd)
EffectiveRange(selectedText, startLine, endLine)
} else {
null
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Off-by-one risk for endLine when selection ends at a line boundary

Using selectionEnd directly to compute endLine can overshoot by one line when the caret is at the start of the next line. For user-facing prompts that display inclusive line ranges, adjust the end offset.

Apply this diff (and add the import):

+import kotlin.math.max
...
-                val selectedText = selectionModel.selectedText ?: ""
-                val startLine = document.getLineNumber(selectionModel.selectionStart)
-                val endLine = document.getLineNumber(selectionModel.selectionEnd)
-                EffectiveRange(selectedText, startLine, endLine)
+                val selectedText = selectionModel.selectedText ?: ""
+                val selectionStart = selectionModel.selectionStart
+                val selectionEnd = selectionModel.selectionEnd
+                val startLine = document.getLineNumber(selectionStart)
+                val adjustedEndOffset = max(selectionEnd - 1, selectionStart)
+                val endLine = document.getLineNumber(adjustedEndOffset)
+                EffectiveRange(selectedText, startLine, endLine)

Note: Cline’s provider intentionally matches VS Code’s start-inclusive/end-exclusive semantics with characters; here we’re only printing line numbers for humans, so inclusive line display is preferable.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return if (selectionModel.hasSelection()) {
val selectedText = selectionModel.selectedText ?: ""
val startLine = document.getLineNumber(selectionModel.selectionStart)
val endLine = document.getLineNumber(selectionModel.selectionEnd)
EffectiveRange(selectedText, startLine, endLine)
} else {
null
}
}
// At the top of the file, among other imports:
import kotlin.math.max
//
return if (selectionModel.hasSelection()) {
val selectedText = selectionModel.selectedText ?: ""
val selectionStart = selectionModel.selectionStart
val selectionEnd = selectionModel.selectionEnd
val startLine = document.getLineNumber(selectionStart)
// Subtract 1 so that if selectionEnd lands exactly at the start of the next line,
// we still report the last character’s line. Clamp to selectionStart to avoid
// passing a negative offset.
val adjustedEndOffset = max(selectionEnd - 1, selectionStart)
val endLine = document.getLineNumber(adjustedEndOffset)
EffectiveRange(selectedText, startLine, endLine)
} else {
null
}
}
🤖 Prompt for AI Agents
In
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt
around lines 238 to 246, the endLine calculation can overshoot when selectionEnd
is at the start of the next line; change the logic to compute an inclusive end
offset (use selectionEnd - 1 but not below selectionStart), then call
document.getLineNumber on that inclusive offset to get an inclusive endLine, and
add the import kotlin.math.max for the clamp operation.

Comment on lines +248 to +337
/**
* Core logic for handling code actions.
* Processes different types of commands and sends appropriate messages to the webview.
*
* @param command The command identifier
* @param promptType The type of prompt to use
* @param params Parameters for the action
* @param project The current project
*/
/**
* Core logic for handling code actions.
* Processes different types of commands and sends appropriate messages to the webview.
*
* @param command The command identifier
* @param promptType The type of prompt to use
* @param params Parameters for the action (can be Map or List)
* @param project The current project
*/
fun handleCodeAction(command: String, promptType: String, params: Any, project: Project?) {
val latestWebView = project?.getService(WebViewManager::class.java)?.getLatestWebView()
if (latestWebView == null) {
return
}

// Create message content based on command type
val messageContent = when {
// Add to context command
command.contains("addToContext") -> {
val promptParams = if (params is Map<*, *>) params as Map<String, Any?> else emptyMap()
mapOf(
"type" to "invoke",
"invoke" to "setChatBoxMessage",
"text" to SupportPrompt.create("ADD_TO_CONTEXT", promptParams)
)
}
// Command executed in current task
command.endsWith("InCurrentTask") -> {
val promptParams = if (params is Map<*, *>) params as Map<String, Any?> else emptyMap()
val basePromptType = when {
command.contains("explain") -> "EXPLAIN"
command.contains("fix") -> "FIX"
command.contains("improve") -> "IMPROVE"
else -> promptType
}
mapOf(
"type" to "invoke",
"invoke" to "sendMessage",
"text" to SupportPrompt.create(basePromptType, promptParams)
)
}
// Command executed in new task
else -> {
val promptParams = if (params is List<*>) {
// Process parameter list from createAction
val argsList = params as List<Any>
if (argsList.size >= 4) {
mapOf(
"filePath" to argsList[0],
"selectedText" to argsList[1],
"startLine" to argsList[2],
"endLine" to argsList[3]
)
} else {
emptyMap()
}
} else if (params is Map<*, *>) {
params as Map<String, Any?>
} else {
emptyMap()
}

val basePromptType = when {
command.contains("explain") -> "EXPLAIN"
command.contains("fix") -> "FIX"
command.contains("improve") -> "IMPROVE"
else -> promptType
}

mapOf(
"type" to "invoke",
"invoke" to "initClineWithTask",
"text" to SupportPrompt.create(basePromptType, promptParams)
)
}
}

// Convert to JSON and send
val messageJson = com.google.gson.Gson().toJson(messageContent)
latestWebView.postMessageToWebView(messageJson)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

De-duplicate handleCodeAction logic and reuse the shared implementation

This file reimplements a handleCodeAction identical in purpose to com.sina.weibo.agent.actions.handleCodeAction (see RegisterCodeActions.kt). Keeping two versions will drift and complicate fixes. Reuse the shared function and remove the local one. Also swap call sites accordingly.

Apply these diffs:

  1. Import and replace call sites:
+import com.sina.weibo.agent.actions.handleCodeAction
...
-            RooCodeContextMenuProvider.handleCodeAction("roo-cline.explainCode.InCurrentTask", "EXPLAIN", args, project)
+            handleCodeAction("roo-cline.explainCode.InCurrentTask", "EXPLAIN", args, project)
...
-            RooCodeContextMenuProvider.handleCodeAction("roo-cline.fixCode.InCurrentTask", "FIX", args, project)
+            handleCodeAction("roo-cline.fixCode.InCurrentTask", "FIX", args, project)
...
-            RooCodeContextMenuProvider.handleCodeAction("roo-cline.fixCode.InCurrentTask", "FIX", args, project)
+            handleCodeAction("roo-cline.fixCode.InCurrentTask", "FIX", args, project)
...
-            RooCodeContextMenuProvider.handleCodeAction("roo-cline.improveCode.InCurrentTask", "IMPROVE", args, project)
+            handleCodeAction("roo-cline.improveCode.InCurrentTask", "IMPROVE", args, project)
...
-            RooCodeContextMenuProvider.handleCodeAction("roo-cline.addToContext", "ADD_TO_CONTEXT", args, project)
+            handleCodeAction("roo-cline.addToContext", "ADD_TO_CONTEXT", args, project)
...
-            RooCodeContextMenuProvider.handleCodeAction("roo-cline.newTask", "NEW_TASK", args, project)
+            handleCodeAction("roo-cline.newTask", "NEW_TASK", args, project)
  1. Remove the local duplicate (entire block shown here):
-        /**
-         * Core logic for handling code actions.
-         * Processes different types of commands and sends appropriate messages to the webview.
-         *
-         * @param command The command identifier
-         * @param promptType The type of prompt to use
-         * @param params Parameters for the action (can be Map or List)
-         * @param project The current project
-         */
-        fun handleCodeAction(command: String, promptType: String, params: Any, project: Project?) {
-            val latestWebView = project?.getService(WebViewManager::class.java)?.getLatestWebView()
-            if (latestWebView == null) {
-                return
-            }
-            ...
-            latestWebView.postMessageToWebView(messageJson)
-        }

Also applies to: 84-85, 109-110, 134-135, 159-160, 184-185, 209-210

🤖 Prompt for AI Agents
In
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt
around lines 248 to 337, this file contains a duplicate handleCodeAction
implementation that should be removed and callers updated to use the shared
implementation in com.sina.weibo.agent.actions.handleCodeAction; remove the
entire local handleCodeAction block, add an import for
com.sina.weibo.agent.actions.handleCodeAction at the top of the file, and
replace every local call site that referenced the removed method (also at the
other occurrences around lines 84-85, 109-110, 134-135, 159-160, 184-185,
209-210) to call the shared handleCodeAction instead, preserving the same
arguments and nullability handling for Project when forwarding calls.

Comment on lines +115 to +133
// Use IntelliJ Platform's proper mechanism to refresh UI on EDT thread
// This avoids calling @ApiStatus.OverrideOnly methods directly
com.intellij.openapi.application.ApplicationManager.getApplication().invokeLater {
try {
// Get the action manager
val actionManager = ActionManager.getInstance()

// Get the dynamic actions group
val dynamicGroup = actionManager.getAction("RunVSAgent.DynamicExtensionActions")
dynamicGroup?.let { group ->
// Trigger UI refresh by notifying the platform
// The platform will automatically call the appropriate update methods
logger.debug("Triggering UI refresh for dynamic actions group")
}

logger.debug("Action toolbars refresh scheduled for extension: $currentExtensionId")
} catch (e: Exception) {
logger.warn("Failed to schedule action toolbar refresh", e)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

EDT scheduling alone doesn’t refresh actions; call invalidateActions() and guard modality/disposal

The current block retrieves the group and logs but does not actually trigger a refresh. Use ActionManager.invalidateActions() and add a modality/disposal guard. Optional: warn if the action ID is missing.

-            com.intellij.openapi.application.ApplicationManager.getApplication().invokeLater {
-                try {
-                    // Get the action manager
-                    val actionManager = ActionManager.getInstance()
-                    
-                    // Get the dynamic actions group
-                    val dynamicGroup = actionManager.getAction("RunVSAgent.DynamicExtensionActions")
-                    dynamicGroup?.let { group ->
-                        // Trigger UI refresh by notifying the platform
-                        // The platform will automatically call the appropriate update methods
-                        logger.debug("Triggering UI refresh for dynamic actions group")
-                    }
-                    
-                    logger.debug("Action toolbars refresh scheduled for extension: $currentExtensionId")
-                } catch (e: Exception) {
-                    logger.warn("Failed to schedule action toolbar refresh", e)
-                }
-            }
+            com.intellij.openapi.application.ApplicationManager.getApplication().invokeLater({
+                try {
+                    if (project.isDisposed) return@invokeLater
+                    val actionManager = ActionManager.getInstance()
+
+                    // Invalidate action system caches so toolbars/menus re-query presentation
+                    actionManager.invalidateActions()
+
+                    val dynamicGroup = actionManager.getAction("RunVSAgent.DynamicExtensionActions")
+                    if (dynamicGroup == null) {
+                        logger.warn("Dynamic actions group 'RunVSAgent.DynamicExtensionActions' not found")
+                    } else {
+                        logger.debug("Triggered action system invalidation for dynamic actions group")
+                    }
+
+                    logger.debug("Action toolbars refresh scheduled for extension: $currentExtensionId")
+                } catch (e: Exception) {
+                    logger.warn("Failed to schedule action toolbar refresh", e)
+                }
+            }, com.intellij.openapi.application.ModalityState.any())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Use IntelliJ Platform's proper mechanism to refresh UI on EDT thread
// This avoids calling @ApiStatus.OverrideOnly methods directly
com.intellij.openapi.application.ApplicationManager.getApplication().invokeLater {
try {
// Get the action manager
val actionManager = ActionManager.getInstance()
// Get the dynamic actions group
val dynamicGroup = actionManager.getAction("RunVSAgent.DynamicExtensionActions")
dynamicGroup?.let { group ->
// Trigger UI refresh by notifying the platform
// The platform will automatically call the appropriate update methods
logger.debug("Triggering UI refresh for dynamic actions group")
}
logger.debug("Action toolbars refresh scheduled for extension: $currentExtensionId")
} catch (e: Exception) {
logger.warn("Failed to schedule action toolbar refresh", e)
}
com.intellij.openapi.application.ApplicationManager.getApplication().invokeLater({
try {
if (project.isDisposed) return@invokeLater
val actionManager = ActionManager.getInstance()
// Invalidate action system caches so toolbars/menus re-query presentation
actionManager.invalidateActions()
val dynamicGroup = actionManager.getAction("RunVSAgent.DynamicExtensionActions")
if (dynamicGroup == null) {
logger.warn("Dynamic actions group 'RunVSAgent.DynamicExtensionActions' not found")
} else {
logger.debug("Triggered action system invalidation for dynamic actions group")
}
logger.debug("Action toolbars refresh scheduled for extension: $currentExtensionId")
} catch (e: Exception) {
logger.warn("Failed to schedule action toolbar refresh", e)
}
}, com.intellij.openapi.application.ModalityState.any())

Comment on lines +42 to +54
fun initialize() {
logger.info("Initializing dynamic context menu manager")

// Get current extension from extension manager
try {
val extensionManager = ExtensionManager.Companion.getInstance(project)
val currentProvider = extensionManager.getCurrentProvider()
currentExtensionId = currentProvider?.getExtensionId()
logger.info("Dynamic context menu manager initialized with extension: $currentExtensionId")
} catch (e: Exception) {
logger.warn("Failed to initialize dynamic context menu manager", e)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Ensure initialize() is invoked during startup

The manager won’t detect the current extension unless initialize() is called. I don’t see a direct call in WecoderPlugin.kt (provided snippet). Please confirm DynamicExtensionContextMenuGroup or another component calls it on project open.


🏁 Script executed:

#!/bin/bash
# Verify that DynamicContextMenuManager.initialize() is invoked somewhere.
rg -nP '\bDynamicContextMenuManager\b' -C2
rg -nP '\bDynamicContextMenuManager\b.*?initialize\s*\(' -C2
# Verify that plugin.xml declares the dynamic group id used here
rg -n 'RunVSAgent.DynamicExtensionContextMenu' jetbrains_plugin/src/main/resources/META-INF/plugin.xml

Length of output: 4648


🏁 Script executed:

#!/bin/bash
# Search for context menu group definitions in plugin.xml
rg -n '<group' jetbrains_plugin/src/main/resources/META-INF/plugin.xml -C3

Length of output: 2574


DynamicContextMenuManager.initialize() runs only on first right-click
The initialize() method is invoked lazily inside DynamicExtensionContextMenuGroup when the context menu is first shown (see lines 57–58). It is not called at IDE startup or project open, so the manager won’t detect the current extension until the user opens the context menu.

To ensure the dynamic menu is set up on project startup, explicitly invoke initialize() during project open. For example, in WecoderPlugin.kt’s projectOpened():

override fun projectOpened() {
    // existing setup…
    DynamicContextMenuManager
        .getInstance(project)
        .initialize()
}

– Point of action:
• In jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/WecoderPlugin.kt, add the above call in projectOpened().

🤖 Prompt for AI Agents
In jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/WecoderPlugin.kt around
the projectOpened() method, the project-open lifecycle doesn't call
DynamicContextMenuManager.initialize(), so the dynamic context menu only detects
the extension on the first right-click; modify projectOpened() to call
DynamicContextMenuManager.getInstance(project).initialize() after existing setup
so the manager is initialized on project open and will have the current
extension available immediately.

Comment on lines +120 to +133
private fun refreshContextMenus() {
try {
// Get the action manager
val actionManager = com.intellij.openapi.actionSystem.ActionManager.getInstance()

// Refresh the dynamic context menu actions group by invalidating the action
// This will trigger the UI to refresh without directly calling @ApiStatus.OverrideOnly methods
val dynamicGroup = actionManager.getAction("RunVSAgent.DynamicExtensionContextMenu")
dynamicGroup?.let { group ->
// Use the proper IntelliJ Platform mechanism to refresh the action
// Instead of calling update() directly, we invalidate the action
actionManager.invalidateAction(group.id)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Validate ActionManager.invalidateAction availability for target IDE builds

invalidateAction(...) is relatively new and not present in all supported platforms. Confirm it exists for your minimum since using an unavailable API will break refresh in older IDEs.


🌐 Web query:

Is ActionManager.invalidateAction(String actionId) available in IntelliJ Platform 2023.1–2024.3? What is the recommended way to force an action group to refresh if not?

💡 Result:

ActionManager.invalidateAction(String actionId) is not available in IntelliJ Platform versions 2023.1 through 2024.3. There is no mention of this method in the IntelliJ Platform Plugin SDK documentation or community discussions for these or later versions, and current best practices for "refreshing" actions or action groups do not reference such a method[1][3][10].

Why is invalidateAction not available?

  • The ActionManager API in IntelliJ Platform provides methods to add, remove, and retrieve actions, but does not include an invalidateAction(String actionId) method, either in its public interface or in SDK practices since at least 2022.3 and up to 2025.2[1][3][8][10]. API change logs and plugin migration guides do not reference such a utility.

Recommended way to force an Action or Action Group to refresh

The recommended approach to force an action or action group to refresh in the current IntelliJ Platform is:

  • Trigger update() logic on the actions/groups by causing a relevant context change or firing a UI event.
    • The IDE’s action system depends on the state provided by the overridden AnAction.update(AnActionEvent e) method, which is called automatically by the platform when it decides the action context has changed (e.g., toolbar/menu opens, project context changes).
  • To ensure dynamic content in groups, implement a custom ActionGroup (typically extend DefaultActionGroup), and override its update() and/or getChildren() method to provide contextually appropriate actions at each requested update[1][3][6].
    • For example, if your group depends on specific IDE state or data, ensure changes to that state are followed by firing appropriate notifications or events that cause the platform to trigger update() calls[1][6].

Common mechanisms to trigger updates:

  • Call ActionToolbar.updateActionsImmediately() (on a relevant component, e.g., after a file/document change or toolwindow visibility change) to force toolbar to reevaluate action states.
  • Send a DataContext or selection change event in the IDE (such as programmatically changing toolwindow focus, updating project view, or updating application data keys) that your action's update() responds to.
  • Refactor your group to always compute its children on each request, if your logic is truly dynamic, by overriding ActionGroup.getChildren(AnActionEvent e).

Key SDK references:

  • Overriding AnAction.update() and leveraging context[1][3][10].
  • Implementing dynamic child actions in a group by extending DefaultActionGroup and customizing getChildren()[6].
  • Forcing UI components (like toolbars) to update their actions via ActionToolbar.updateActionsImmediately().

Summary Table: Refreshing Actions or Groups

Need Supported Method Example/Reference
Invalidate a single action by ID Not supported (no such method)
Force toolbar or group UI to refresh ActionToolbar.updateActionsImmediately() [1][6]
Ensure dynamic group content Override getChildren() in custom ActionGroup [6]
Update action state for context change Override update(), respond to data/context change [1][3][10]

Conclusion:
Use dynamic group implementations and UI trigger mechanisms documented in the SDK rather than a non-existent invalidateAction method[1][6][10]. For reliable dynamic updates, your update() and getChildren() implementations should respond to changes in the IDE state that matter to your group or action.

If further details on a specific type of refresh or integration with custom UI are needed, please clarify your scenario.

Citations:


Remove unsupported invalidateAction call

The method ActionManager.invalidateAction(String) does not exist in IntelliJ Platform Plugin SDK versions 2023.1 through 2024.3 and will lead to a NoSuchMethodError at runtime (plugins.jetbrains.com).

To implement dynamic context‐menu refresh, consider one of the following supported approaches:

  • Override your action group's getChildren(AnActionEvent)
    Extend ActionGroup (or DefaultActionGroup) and override getChildren(AnActionEvent) to return the current set of child actions. The IDE calls this method each time the menu is shown, ensuring your menu is always up to date (plugins.jetbrains.com).

  • Trigger a global action‐system update
    If you must programmatically force the UI to re‐evaluate action states (e.g., after external state changes), call:
    ActivityTracker.getInstance().inc()
    This notifies the action subsystem to update all toolbar and menu actions (intellij-sdk-docs-cn.github.io).

  • Leverage UI‐component updates
    For toolbars built via ActionToolbar, you can call updateActionsImmediately() on the toolbar instance after changing your plugin’s data model.

Attention is required in:
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt lines 120–133, specifically the block invoking actionManager.invalidateAction(group.id).

Please remove or replace the invalidateAction(...) call and adopt one of the supported mechanisms above.

🤖 Prompt for AI Agents
In
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt
around lines 120-133, remove the unsupported call to
actionManager.invalidateAction(group.id) (it does not exist in supported
IntelliJ SDKs) and replace it with a supported approach: either make the group a
subclass of ActionGroup/DefaultActionGroup and override
getChildren(AnActionEvent) to compute children dynamically, or if you need to
force a programmatic refresh call ActivityTracker.getInstance().inc() to trigger
global action-system updates, or, when updating a specific ActionToolbar
instance, call its updateActionsImmediately() after changing your plugin state;
update imports and comments accordingly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt (1)

43-54: Ensure initialize() is invoked on project open (repeat)

initialize() is only effective if called early. Please confirm a startup lifecycle (e.g., in WecoderPlugin.projectOpened) invokes it; otherwise menus won’t reflect the current extension until the first context-menu open.

Run:

#!/bin/bash
# Verify initialize() is called during project lifecycle and group id exists
rg -nP '\bDynamicContextMenuManager\b' -C2
rg -nP '\bDynamicContextMenuManager\b.*\binitialize\s*\(' -C3
rg -n 'RunVSAgent\.DynamicExtensionContextMenu' jetbrains_plugin/src/main/resources/META-INF/plugin.xml -n -C2
🧹 Nitpick comments (3)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt (3)

96-107: Avoid hard-coded provider branching; reuse provider instances via a registry map

The when-based string matching will sprawl as providers grow and creates new instances per call. Prefer a small registry map and reuse instances.

Inline refactor within this function:

-    private fun getContextMenuProvider(extensionId: String?): ExtensionContextMenuProvider? {
-        if (extensionId == null) return null
-        
-        return when (extensionId) {
-            "roo-code" -> RooCodeContextMenuProvider()
-            "cline" -> ClineContextMenuProvider()
-            // TODO: Add other context menu providers as they are implemented
-            // "copilot" -> CopilotContextMenuProvider()
-            // "claude" -> ClaudeContextMenuProvider()
-            else -> null
-        }
-    }
+    private val providers by lazy {
+        mapOf(
+            "roo-code" to RooCodeContextMenuProvider(),
+            "cline" to ClineContextMenuProvider(),
+        )
+    }
+
+    private fun getContextMenuProvider(extensionId: String?): ExtensionContextMenuProvider? {
+        if (extensionId.isNullOrEmpty()) return null
+        return providers[extensionId]
+    }

20-21: Service lifecycle: implement Disposable so dispose() is called automatically

dispose() won’t be called unless the service implements Disposable. Either remove the method or wire it into the platform lifecycle.

Suggested change:

@@
-import com.intellij.openapi.components.Service
+import com.intellij.openapi.components.Service
+import com.intellij.openapi.Disposable
@@
-@Service(Service.Level.PROJECT)
-class DynamicContextMenuManager(private val project: Project) {
+@Service(Service.Level.PROJECT)
+class DynamicContextMenuManager(private val project: Project) : Disposable {
@@
-    fun dispose() {
+    override fun dispose() {
         logger.info("Disposing dynamic context menu manager")
         currentExtensionId = null
     }

Also applies to: 150-153


129-131: Minor: extract group id into a constant to avoid typos

Centralize the action id string.

@@
     companion object {
         /**
          * Get dynamic context menu manager instance
          */
         fun getInstance(project: Project): DynamicContextMenuManager {
             return project.getService(DynamicContextMenuManager::class.java)
                 ?: error("DynamicContextMenuManager not found")
         }
+        private const val DYNAMIC_GROUP_ID = "RunVSAgent.DynamicExtensionContextMenu"
     }
@@
-                    val dynamicGroup = actionManager.getAction("RunVSAgent.DynamicExtensionContextMenu")
+                    val dynamicGroup = actionManager.getAction(DYNAMIC_GROUP_ID)
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 008ed8d and 3f17c36.

📒 Files selected for processing (1)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt (5)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/buttons/DynamicButtonManager.kt (1)
  • getCurrentExtensionId (71-73)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ExtensionContextMenuProvider.kt (1)
  • getContextMenuConfiguration (49-49)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt (1)
  • getContextMenuConfiguration (46-48)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt (1)
  • getContextMenuConfiguration (48-50)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/core/ExtensionManager.kt (2)
  • LOG (25-231)
  • id (262-281)
🔇 Additional comments (1)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt (1)

159-177: Default configuration looks sensible

The fallback shows a minimal, safe action set (EXPLAIN_CODE, ADD_TO_CONTEXT). No issues spotted.

Comment on lines +26 to +29
// Current extension ID
@Volatile
private var currentExtensionId: String? = null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extension state inconsistency: setCurrentExtension() doesn’t affect lookups

setCurrentExtension() mutates the local currentExtensionId, but getCurrentExtensionId() ignores it and consults ExtensionManager instead. As a result, calling setCurrentExtension() will not change which provider/config the manager returns. Either rely on ExtensionManager as source-of-truth and remove the field and setter, or make this manager’s state authoritative for menu resolution.

Suggested minimal fix to make the setter take effect and avoid redundant refreshes:

@@
-    fun setCurrentExtension(extensionId: String) {
-        logger.info("Setting current extension to: $extensionId")
-        currentExtensionId = extensionId
-        
-        // Refresh all context menus to reflect the change
-        refreshContextMenus()
-    }
+    fun setCurrentExtension(extensionId: String) {
+        logger.info("Setting current extension to: $extensionId")
+        if (extensionId == currentExtensionId) {
+            logger.debug("Current extension unchanged; skipping refresh")
+            return
+        }
+        currentExtensionId = extensionId
+        // Refresh all context menus to reflect the change
+        refreshContextMenus()
+    }
@@
-    fun getCurrentExtensionId(): String? {
-        return extensionManager.getCurrentProvider()?.getExtensionId()
-    }
+    fun getCurrentExtensionId(): String? {
+        return currentExtensionId
+    }

Also applies to: 59-65, 70-72

🤖 Prompt for AI Agents
In
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt
around lines 26-29 (also apply changes near 59-65 and 70-72): the field
currentExtensionId and setCurrentExtension() are mutated but
getCurrentExtensionId() ignores it and always queries ExtensionManager, so the
setter has no effect; to fix, make the manager’s state authoritative by changing
getCurrentExtensionId() to return currentExtensionId if non-null (falling back
to ExtensionManager only when null), update setCurrentExtension(id) to set the
field only when the id actually changes and trigger refresh/update exactly once
when it changes (avoid redundant refresh calls), and remove any redundant use of
ExtensionManager as the sole source-of-truth or delete the field and setter if
you prefer to rely on ExtensionManager instead—implement the first approach for
minimal change.

Comment on lines +120 to +145
private fun refreshContextMenus() {
try {
// Use IntelliJ Platform's proper mechanism to refresh UI on EDT thread
// This avoids calling @ApiStatus.OverrideOnly methods directly
com.intellij.openapi.application.ApplicationManager.getApplication().invokeLater {
try {
// Get the action manager
val actionManager = com.intellij.openapi.actionSystem.ActionManager.getInstance()

// Get the dynamic context menu actions group
val dynamicGroup = actionManager.getAction("RunVSAgent.DynamicExtensionContextMenu")
dynamicGroup?.let { group ->
// Trigger UI refresh by notifying the platform
// The platform will automatically call the appropriate update methods
logger.debug("Triggering UI refresh for dynamic context menu group")
}

logger.debug("Context menus refresh scheduled for extension: $currentExtensionId")
} catch (e: Exception) {
logger.warn("Failed to schedule context menu refresh", e)
}
}
} catch (e: Exception) {
logger.warn("Failed to refresh context menus", e)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

refreshContextMenus() is effectively a no-op—explicitly trigger action-system updates

Good call removing the unsupported ActionManager.invalidateAction(). However, the current EDT task only fetches the group and logs; it doesn’t cause the IDE to re-run update() or recompute group children. Use ActivityTracker to nudge the action system.

Apply:

@@
-            com.intellij.openapi.application.ApplicationManager.getApplication().invokeLater {
+            com.intellij.openapi.application.ApplicationManager.getApplication().invokeLater {
                 try {
                     // Get the action manager
                     val actionManager = com.intellij.openapi.actionSystem.ActionManager.getInstance()
                     
                     // Get the dynamic context menu actions group
                     val dynamicGroup = actionManager.getAction("RunVSAgent.DynamicExtensionContextMenu")
-                    dynamicGroup?.let { group ->
-                        // Trigger UI refresh by notifying the platform
-                        // The platform will automatically call the appropriate update methods
-                        logger.debug("Triggering UI refresh for dynamic context menu group")
-                    }
+                    // Trigger UI refresh by notifying the platform; this causes update() to be re-evaluated
+                    com.intellij.ide.ActivityTracker.getInstance().inc()
+                    if (dynamicGroup == null) {
+                        logger.debug("Dynamic context menu group not found: RunVSAgent.DynamicExtensionContextMenu")
+                    } else {
+                        logger.debug("Triggered UI refresh for dynamic context menu group")
+                    }

Additionally, add the missing import at the top of the file:

import com.intellij.ide.ActivityTracker
🤖 Prompt for AI Agents
In
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt
around lines 120 to 145, refreshContextMenus() currently only fetches the action
group and logs, so it never forces the action system to re-run
update()/recompute children; replace the no-op logging with a call that nudges
the action system via ActivityTracker (e.g., call
ActivityTracker.getInstance().inc(); or notify activity on EDT) to trigger
recomputation of actions, and add the missing import at the top: import
com.intellij.ide.ActivityTracker.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/VsixManager.kt (2)

153-172: Zip Slip risk when extracting entries — sanitize paths before writing

entry.name may contain “../” and escape tempDir. Normalize and enforce that the resolved path stays within tempDir.

-            ZipInputStream(BufferedInputStream(FileInputStream(vsixFile))).use { zis ->
+            ZipInputStream(BufferedInputStream(FileInputStream(vsixFile))).use { zis ->
                 var entry = zis.nextEntry
                 var extractedCount = 0
                 
                 while (entry != null) {
                     if (!entry.isDirectory) {
-                        val entryPath = tempDir!!.toPath().resolve(entry.name)
+                        val base = tempDir!!.toPath()
+                        val entryPath = base.resolve(entry.name).normalize()
+                        // Prevent Zip Slip (path traversal)
+                        if (!entryPath.startsWith(base)) {
+                            LOG.warn("Skipping suspicious zip entry outside extraction dir: ${entry.name}")
+                            entry = zis.nextEntry
+                            continue
+                        }
                         
                         // Create parent directories if needed
                         val parentDir = entryPath.parent
                         if (parentDir != null) {
                             Files.createDirectories(parentDir)
                         }
                         
                         // Extract file
-                        Files.copy(zis, entryPath)
+                        Files.copy(zis, entryPath)
                         extractedCount++
                     }
                     entry = zis.nextEntry
                 }

199-211: Potential failures moving across filesystems (/tmp → home): prefer copy + delete

Files.move can fail across mounts; copy then delete is more robust. Also allows partial resilience.

-            var movedCount = 0
+            var movedCount = 0
             for (file in extensionFiles) {
                 val targetFile = targetPath.resolve(file.name).toFile()
                 
                 if (file.isDirectory) {
-                    // Move directory recursively
-                    moveDirectory(file, targetFile)
+                    // Copy directory recursively
+                    copyDirectory(file, targetFile)
                 } else {
-                    // Move file
-                    Files.move(file.toPath(), targetFile.toPath())
+                    // Copy file
+                    Files.copy(file.toPath(), targetFile.toPath())
                 }
                 movedCount++
             }

And replace moveDirectory with a safe copyDirectory:

-    private fun moveDirectory(source: File, target: File) {
+    private fun copyDirectory(source: File, target: File) {
         if (!target.exists()) {
             target.mkdirs()
         }
         
         val sourceFiles = source.listFiles()
         if (sourceFiles != null) {
             for (file in sourceFiles) {
                 val targetFile = File(target, file.name)
                 
                 if (file.isDirectory) {
-                    moveDirectory(file, targetFile)
+                    copyDirectory(file, targetFile)
                 } else {
-                    Files.move(file.toPath(), targetFile.toPath())
+                    Files.copy(file.toPath(), targetFile.toPath())
                 }
             }
         }
-        
-        // Remove empty source directory
-        source.delete()
+        // Best-effort: remove copied source directory
+        deleteDirectory(source)
     }

Also applies to: 245-265

♻️ Duplicate comments (3)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt (3)

35-43: Wire “New Task” into the menu list

The action is defined but not added, so it won’t appear.

         return listOf(
             ExplainCodeAction(),
             FixCodeAction(),
             FixLogicAction(),
             ImproveCodeAction(),
-            AddToContextAction(),
+            AddToContextAction(),
+            NewTaskAction(),
         )

233-245: Inclusive endLine for human-readable ranges (avoid off-by-one at line boundaries)

selectionEnd at start of next line yields an extra line. Clamp to (selectionEnd - 1).

-            return if (selectionModel.hasSelection()) {
-                val selectedText = selectionModel.selectedText ?: ""
-                val startLine = document.getLineNumber(selectionModel.selectionStart)
-                val endLine = document.getLineNumber(selectionModel.selectionEnd)
-                EffectiveRange(selectedText, startLine, endLine)
-            } else {
-                null
-            }
+            return if (selectionModel.hasSelection()) {
+                val selectedText = selectionModel.selectedText ?: ""
+                val selectionStart = selectionModel.selectionStart
+                val selectionEnd = selectionModel.selectionEnd
+                val startLine = document.getLineNumber(selectionStart)
+                val adjustedEndOffset = kotlin.math.max(selectionEnd - 1, selectionStart)
+                val endLine = document.getLineNumber(adjustedEndOffset)
+                EffectiveRange(selectedText, startLine, endLine)
+            } else null

If you prefer an import:

+import kotlin.math.max
...
-                val adjustedEndOffset = kotlin.math.max(selectionEnd - 1, selectionStart)
+                val adjustedEndOffset = max(selectionEnd - 1, selectionStart)

83-84: De-duplicate handleCodeAction: reuse shared implementation from actions/RegisterCodeActions.kt

This file re-implements the same logic. Import and call the shared handleCodeAction, remove the local duplicate to keep one source of truth.

+import com.sina.weibo.agent.actions.handleCodeAction
...
-            RooCodeContextMenuProvider.handleCodeAction("roo-cline.explainCode.InCurrentTask", "EXPLAIN", args, project)
+            handleCodeAction("roo-cline.explainCode.InCurrentTask", "EXPLAIN", args, project)
...
-            RooCodeContextMenuProvider.handleCodeAction("roo-cline.fixCode.InCurrentTask", "FIX", args, project)
+            handleCodeAction("roo-cline.fixCode.InCurrentTask", "FIX", args, project)
...
-            RooCodeContextMenuProvider.handleCodeAction("roo-cline.fixCode.InCurrentTask", "FIX", args, project)
+            handleCodeAction("roo-cline.fixCode.InCurrentTask", "FIX", args, project)
...
-            RooCodeContextMenuProvider.handleCodeAction("roo-cline.improveCode.InCurrentTask", "IMPROVE", args, project)
+            handleCodeAction("roo-cline.improveCode.InCurrentTask", "IMPROVE", args, project)
...
-            RooCodeContextMenuProvider.handleCodeAction("roo-cline.addToContext", "ADD_TO_CONTEXT", args, project)
+            handleCodeAction("roo-cline.addToContext", "ADD_TO_CONTEXT", args, project)
...
-            RooCodeContextMenuProvider.handleCodeAction("roo-cline.newTask", "NEW_TASK", args, project)
+            handleCodeAction("roo-cline.newTask", "NEW_TASK", args, project)

Remove the local duplicate method:

-        fun handleCodeAction(command: String, promptType: String, params: Any, project: Project?) {
-            val latestWebView = project?.getService(WebViewManager::class.java)?.getLatestWebView()
-            if (latestWebView == null) {
-                return
-            }
-            ...
-            latestWebView.postMessageToWebView(messageJson)
-        }

Also applies to: 108-109, 133-134, 158-159, 183-184, 208-209, 256-336

🧹 Nitpick comments (20)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooExtensionProvider.kt (3)

14-16: Use VsixManager.getBaseDirectory() instead of hardcoding user-config path

Centralize base path logic and avoid drift with other providers by using VsixManager.Companion.getBaseDirectory().

-import com.sina.weibo.agent.util.PluginConstants.ConfigFiles.getUserConfigDir
+import com.sina.weibo.agent.extensions.core.VsixManager.Companion.getBaseDirectory
...
-        val possiblePaths = listOf(
-            "${getUserConfigDir()}/plugins/${config.codeDir}"
-        )
+        val possiblePaths = listOf(
+            "${getBaseDirectory()}/${config.codeDir}"
+        )

Also applies to: 44-47


44-47: Comment says “First check project paths” but code does not check project paths

Either adjust the comment or add project-relative candidates (like projectBase/../codeDir).

Example if you want to actually check project paths:

-        // First check project paths
-        val possiblePaths = listOf(
-            "${getBaseDirectory()}/${config.codeDir}"
-        )
+        // First check installed/base dir (user config), optionally project paths if desired
+        val possiblePaths = buildList {
+            add("${getBaseDirectory()}/${config.codeDir}")
+            project.basePath?.let { base ->
+                add("$base/${config.codeDir}")
+                add("$base/../${config.codeDir}")
+                add("$base/../../${config.codeDir}")
+            }
+        }

66-69: Mismatch between behavior and comment for dev/test fallback

The comment says “always return true if we can't find the files” but the code returns false. Pick one and keep it consistent. If store-review requires strict existence, update the comment; otherwise return true.

-        // For development/testing, always return true if we can't find the files
-        // This allows the extension to work even without the actual extension files
-        return false
+        // For production/store review, return false if files are missing
+        // (Enable a dev toggle if you want to bypass in development builds)
+        return false

Alternatively (dev mode):

-        return false
+        return java.lang.Boolean.getBoolean("runvsagent.dev.allowMissingExtensions")
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/VsixManager.kt (2)

286-301: Safe re-install: allow overwriting theme files

If re-installing and the themes dir already exists, Files.copy will throw. Use REPLACE_EXISTING.

-                    Files.copy(themeFile.toPath(), targetFile.toPath())
+                    Files.copy(
+                        themeFile.toPath(),
+                        targetFile.toPath(),
+                        java.nio.file.StandardCopyOption.REPLACE_EXISTING
+                    )

36-38: Ensure base directory exists before use

Create the base directory up front to simplify downstream checks.

         fun getBaseDirectory(): String {
-            return "${getUserConfigDir()}/plugins"
+            val base = "${getUserConfigDir()}/plugins"
+            try {
+                Files.createDirectories(Path.of(base))
+            } catch (e: Exception) {
+                LOG.warn("Failed to ensure base directory exists: $base", e)
+            }
+            return base
         }

Also applies to: 129-139

jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineExtensionProvider.kt (4)

45-49: Nit: copy-paste in comment (“roo-code”)

This provider is for Cline; update the comment to avoid confusion.

-        // Check if roo-code extension files exist
+        // Check if cline extension files exist

51-53: Comment vs. code mismatch: “First check project paths” but only base dir is checked

Either adjust the comment or include project-relative candidates (like projectBase/../codeDir).

Example to check both:

-        // First check project paths
-        val possiblePaths = listOf(
-            "${getBaseDirectory()}/${config.codeDir}"
-        )
+        // First check installed/base dir, optionally project-relative locations
+        val possiblePaths = buildList {
+            add("${getBaseDirectory()}/${config.codeDir}")
+            project.basePath?.let { base ->
+                add("$base/${config.codeDir}")
+                add("$base/../${config.codeDir}")
+                add("$base/../../${config.codeDir}")
+            }
+        }

72-75: Comment says “always return true” but code returns false

Align the comment with behavior or add a dev toggle as needed (same suggestion as Roo).

-        // For development/testing, always return true if we can't find the files
-        // This allows the extension to work even without the actual extension files
-        return false
+        // For production/store review, return false if files are missing
+        return false

50-68: Avoid duplicating discovery logic — consider VsixManager.getExtensionResourcePath(...)

You already depend on VsixManager; reusing getExtensionResourcePath(extensionId, codeDir, project.basePath) would consolidate search order (project > vsix install > plugin resources).

-        val possiblePaths = buildList {
-            add("${getBaseDirectory()}/${config.codeDir}")
-            project.basePath?.let { base ->
-                add("$base/${config.codeDir}")
-                add("$base/../${config.codeDir}")
-                add("$base/../../${config.codeDir}")
-            }
-        }
-
-        if (possiblePaths.any { File(it).exists() }) {
-            return true
-        }
-
-        // Then check plugin resources (for built-in extensions)
-        try {
-            val pluginResourcePath = PluginResourceUtil.getResourcePath(
-                PluginConstants.PLUGIN_ID,
-                config.codeDir
-            )
-            if (pluginResourcePath != null && File(pluginResourcePath).exists()) {
-                return true
-            }
-        } catch (e: Exception) {
-            // Ignore exceptions when checking plugin resources
-        }
+        val found = VsixManager().getExtensionResourcePath(
+            getExtensionId(),
+            config.codeDir,
+            project.basePath
+        )
+        if (found != null) return true
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/core/ExtensionHostManager.kt (2)

382-395: Don’t gate base-directory lookup on projectPath; and remove unused ‘homeDir’

The base directory resolution doesn’t depend on projectPath; removing the null gate avoids false negatives in non-project contexts. Also, ‘homeDir’ is now unused.

-        val projectPath = project.basePath
-        val homeDir = System.getProperty("user.home")
-        if (projectPath != null) {
-            val possiblePaths = listOf(
-                "${getBaseDirectory()}/${extensionConfig.getCodeDir()}"
-            )
-            
-            val foundPath = possiblePaths.find { File(it).exists() }
-            if (foundPath != null) {
-                return foundPath
-            }
-        }
+        val possiblePaths = listOf(
+            "${getBaseDirectory()}/${extensionConfig.getCodeDir()}"
+        )
+        val foundPath = possiblePaths.find { File(it).exists() }
+        if (foundPath != null) {
+            return foundPath
+        }

397-415: Reuse VsixManager.getExtensionResourcePath to avoid duplication and drift

VsixManager already encapsulates search order (project > vsix install > plugin resources). Using it here reduces maintenance.

-        // Then check plugin resources (for built-in extensions)
-        try {
-            val pluginResourcePath = com.sina.weibo.agent.util.PluginResourceUtil.getResourcePath(
-                com.sina.weibo.agent.util.PluginConstants.PLUGIN_ID, 
-                extensionConfig.getCodeDir()
-            )
-            if (pluginResourcePath != null && File(pluginResourcePath).exists()) {
-                return pluginResourcePath
-            }
-        } catch (e: Exception) {
-            LOG.warn("Failed to get plugin resource path for extension: ${extensionConfig.getCodeDir()}", e)
-        }
+        VsixManager().getExtensionResourcePath(
+            extensionIdentifier = currentExtensionProvider?.getExtensionId() ?: "",
+            codeDir = extensionConfig.getCodeDir(),
+            projectPath = project.basePath
+        )?.let { return it }
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt (1)

338-415: Dead code: prompt templating helpers are unused

createPrompt/getPromptTemplate/replacePlaceholders aren’t referenced after switching to RooCodeSupportPrompt. Remove to reduce noise.

-        fun createPrompt(promptType: String, params: Map<String, Any?>): String { ... }
-        fun getPromptTemplate(type: String): String { ... }
-        fun replacePlaceholders(template: String, params: Map<String, Any?>): String { ... }
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt (8)

8-10: Stringly-typed prompt types and params increase typo risk.

Type aliases to String/Map keep things flexible for extensions but are brittle. Consider providing constants for built‑in prompt types and well-known param keys to reduce typing mistakes, while still allowing custom types.

You can add constants and use them across the codebase:

// New file or top of this file
object RooCodePromptTypes {
  const val ENHANCE = "ENHANCE"
  const val EXPLAIN = "EXPLAIN"
  const val FIX = "FIX"
  const val IMPROVE = "IMPROVE"
  const val ADD_TO_CONTEXT = "ADD_TO_CONTEXT"
  const val TERMINAL_ADD_TO_CONTEXT = "TERMINAL_ADD_TO_CONTEXT"
  const val TERMINAL_FIX = "TERMINAL_FIX"
  const val TERMINAL_EXPLAIN = "TERMINAL_EXPLAIN"
  const val NEW_TASK = "NEW_TASK"
}

object RooCodePromptKeys {
  const val FILE_PATH = "filePath"
  const val START_LINE = "startLine"
  const val END_LINE = "endLine"
  const val USER_INPUT = "userInput"
  const val SELECTED_TEXT = "selectedText"
  const val DIAGNOSTICS = "diagnostics"
  const val TERMINAL_CONTENT = "terminalContent"
}

Then replace string literals in this file with these constants for stronger cohesion.


57-71: FIX template: small robustness note on diagnostics.

Template expects ${diagnosticText}; your generator composes it from params["diagnostics"]. Good. See diagnostic handling comment below to better support non-string codes.


163-174: Reduce key typos by using constants for map keys.

Hard-coded string keys can drift from call sites. Tie them to constants.

Apply:

-    val configs = mapOf(
-        "ENHANCE" to ENHANCE,
-        "EXPLAIN" to EXPLAIN,
-        "FIX" to FIX,
-        "IMPROVE" to IMPROVE,
-        "ADD_TO_CONTEXT" to ADD_TO_CONTEXT,
-        "TERMINAL_ADD_TO_CONTEXT" to TERMINAL_ADD_TO_CONTEXT,
-        "TERMINAL_FIX" to TERMINAL_FIX,
-        "TERMINAL_EXPLAIN" to TERMINAL_EXPLAIN,
-        "NEW_TASK" to NEW_TASK
-    )
+    val configs = mapOf(
+        RooCodePromptTypes.ENHANCE to ENHANCE,
+        RooCodePromptTypes.EXPLAIN to EXPLAIN,
+        RooCodePromptTypes.FIX to FIX,
+        RooCodePromptTypes.IMPROVE to IMPROVE,
+        RooCodePromptTypes.ADD_TO_CONTEXT to ADD_TO_CONTEXT,
+        RooCodePromptTypes.TERMINAL_ADD_TO_CONTEXT to TERMINAL_ADD_TO_CONTEXT,
+        RooCodePromptTypes.TERMINAL_FIX to TERMINAL_FIX,
+        RooCodePromptTypes.TERMINAL_EXPLAIN to TERMINAL_EXPLAIN,
+        RooCodePromptTypes.NEW_TASK to NEW_TASK
+    )

(Include the RooCodePromptTypes object from the earlier comment.)


189-197: Handle numeric or non-string diagnostic codes.

Casting the code to String drops numeric codes; also default message/source handling can be slightly stricter.

Apply:

-        return "\nCurrent problems detected:\n" + diagnostics.joinToString("\n") { d ->
-            val source = d["source"] as? String ?: "Error"
-            val message = d["message"] as? String ?: ""
-            val code = d["code"] as? String
-            "- [$source] $message${code?.let { " ($it)" } ?: ""}"
-        }
+        return "\nCurrent problems detected:\n" + diagnostics.joinToString("\n") { d ->
+            val source = d["source"]?.toString()?.ifBlank { "Error" } ?: "Error"
+            val message = d["message"]?.toString() ?: ""
+            val code = d["code"]?.toString()?.takeIf { it.isNotBlank() }
+            "- [$source] $message${code?.let { " ($it)" } ?: ""}"
+        }

206-227: Placeholder regex can be safer and cheaper.

  • Current pattern \$\{(.*?)} matches any content, including braces; restrict to identifier-like keys.
  • Consider precompiling the regex at object scope.

Minimal change:

-        val pattern = Regex("""\$\{(.*?)}""")
+        val pattern = Regex("""\$\{([A-Za-z0-9_]+)}""")

Optional precompile outside the method:

// at object scope
private val PLACEHOLDER_RE = Regex("""\$\{([A-Za-z0-9_]+)}""")

and then use PLACEHOLDER_RE in createPrompt.


236-238: Unknown prompt types currently return an empty string — consider signaling the issue.

Silent fallback to "" can produce blank messages posted to the webview. Prefer logging and/or a safe fallback (e.g., NEW_TASK or selectedText/userInput) to keep UX predictable.

Example (no external imports needed):

val template = customSupportPrompts?.get(type) ?: RooCodeSupportPromptConfigs.configs[type]?.template
return template ?: ""

Alternatively, throw IllegalArgumentException with a clear message if you want to fail fast during development.


248-251: Provide a non-empty fallback when template not found.

Prevents sending blank messages if a dynamic prompt type is unknown.

Apply:

-        val template = get(customSupportPrompts, type)
-        return createPrompt(template, params)
+        val template = get(customSupportPrompts, type)
+        val effectiveTemplate = template.ifBlank {
+            // Fallback: reuse user-provided content to avoid empty messages
+            (params["selectedText"] as? String)
+                ?: (params["userInput"] as? String)
+                ?: ""
+        }
+        return createPrompt(effectiveTemplate, params)

24-174: Add unit tests for prompt creation and placeholder substitution.

Given the templating logic, tests will prevent regressions (e.g., placeholder typos, missing fallbacks).

I can add tests verifying:

  • Each built-in type renders expected text when all params are provided.
  • Missing optional params don’t produce awkward artifacts (e.g., “:-”).
  • Diagnostics are correctly formatted for empty/non-empty lists.
    Want me to open a follow-up PR with tests?

Also applies to: 189-251

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3f17c36 and 3022488.

📒 Files selected for processing (9)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt (0 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/core/ExtensionHostManager.kt (2 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/VsixManager.kt (3 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt (1 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineExtensionProvider.kt (2 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt (13 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt (1 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooExtensionProvider.kt (2 hunks)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/actions/ExtensionResourceStatusAction.kt (0 hunks)
💤 Files with no reviewable changes (2)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/actions/ExtensionResourceStatusAction.kt
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt
🧰 Additional context used
🧬 Code graph analysis (6)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooExtensionProvider.kt (2)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/util/PluginConstants.kt (4)
  • getUserConfigDir (45-47)
  • MAIN_CONFIG_FILE (16-78)
  • PLUGIN_ID (7-79)
  • getMainConfigPath (52-54)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/plugin/WecoderPlugin.kt (2)
  • LOG (42-220)
  • project (186-214)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/VsixManager.kt (1)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/util/PluginConstants.kt (5)
  • getUserConfigDir (45-47)
  • MAIN_CONFIG_FILE (16-78)
  • getMainConfigPath (52-54)
  • getExtensionConfigPath (59-61)
  • PLUGIN_ID (7-79)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineExtensionProvider.kt (2)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/VsixManager.kt (1)
  • getBaseDirectory (36-38)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/util/PluginConstants.kt (1)
  • getUserConfigDir (45-47)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt (2)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt (1)
  • createPrompt (345-348)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt (7)
  • handleCodeAction (228-299)
  • createAction (19-132)
  • registerCodeAction (157-201)
  • text (142-146)
  • actionPerformed (164-199)
  • createAction (28-51)
  • provideCodeActions (95-131)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/core/ExtensionHostManager.kt (2)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/VsixManager.kt (1)
  • getBaseDirectory (36-38)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/util/PluginConstants.kt (1)
  • getUserConfigDir (45-47)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt (2)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt (9)
  • createAction (19-132)
  • handleCodeAction (228-299)
  • provideCodeActions (95-131)
  • text (142-146)
  • createAction (28-51)
  • registerCodeAction (157-201)
  • actionPerformed (33-49)
  • actionPerformed (164-199)
  • getEffectiveRange (75-87)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/plugin/WecoderPlugin.kt (1)
  • LOG (42-220)
🔇 Additional comments (5)
jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt (5)

24-34: ENHANCE template looks good.

Clear instruction and minimal surface; safe to ship.


98-103: ADD_TO_CONTEXT: concise and correct.

Straight pass-through of location + code is appropriate here.


176-182: No remaining usages of SupportPrompt, ActionNames, or CommandIds detected
All call sites in *.kt now reference RooCodeSupportPrompt and the new constants; there are no leftover references to the old identifiers. The compatibility risk noted in the previous comment has been addressed.


5-5: Package relocation verified—no legacy references found

I searched the entire Kotlin/Java codebase and all plugin.xml definitions for the old com.sina.weibo.agent.extensions.roo package and symbols (SupportPrompt, ActionNames, CommandIds, RooCodeSupportPrompt) and found nothing remaining. All call sites and plugin descriptors now reference the relocated com.sina.weibo.agent.extensions.plugin.roo namespace correctly. No further updates are required.


39-51: Verify all EXPLAIN invocations include line numbers

Please manually confirm that every code path which triggers the EXPLAIN prompt always sets both startLine and endLine before rendering the template. Our automated search didn’t conclusively cover every flow:

  • In RooCodeContextMenuProvider.kt around line 83 (the “InCurrentTask” action), ensure you’re setting both
    args["startLine"] = effectiveRange.startLine
    args["endLine"]   = effectiveRange.endLine + 1
    before calling
    handleCodeAction("roo-cline.explainCode.InCurrentTask", "EXPLAIN", args, project)
  • In the other code-action branches (e.g. the “sendMessage” flows at lines 292–296 and 317–321), verify that the promptParams map passed into
    RooCodeSupportPrompt.create("EXPLAIN", promptParams)
    also contains both startLine and endLine.

Optional refactor to make the template resilient when no lines are provided:

- Explain the following code from file path ${filePath}:${startLine}-${endLine}
+ Explain the following code from file path ${filePath}${lineRange}

Compute lineRange ahead of time as either ":<start>-<end>" or the empty string.

@qdaxb qdaxb merged commit 2ec8797 into main Aug 22, 2025
1 check passed
@qdaxb qdaxb deleted the ContextMenu_20250821 branch August 22, 2025 03:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants