From c2474e6137ee410dac7dbf4d56b7ed2d7686f35a Mon Sep 17 00:00:00 2001 From: MicroLee Date: Thu, 21 Aug 2025 12:13:46 +0800 Subject: [PATCH 1/4] refactor: Introduce specific context menus and operations for extension --- .../weibo/agent/actions/ActionConstants.kt | 33 ++ .../actions/RightClickChatActionGroup.kt | 5 + .../agent/extensions/core/ExtensionManager.kt | 8 + .../plugin/cline/ClineContextMenuProvider.kt | 473 ++++++++++++++++++ .../plugin/roo/RooCodeActionConstants.kt | 295 +++++++++++ .../plugin/roo/RooCodeContextMenuProvider.kt | 417 +++++++++++++++ .../contextmenu/ContextMenuConfiguration.kt | 34 ++ .../contextmenu/DynamicContextMenuManager.kt | 174 +++++++ .../DynamicExtensionContextMenuGroup.kt | 81 +++ .../ExtensionContextMenuProvider.kt | 50 ++ .../src/main/resources/META-INF/plugin.xml | 4 +- 11 files changed, 1572 insertions(+), 2 deletions(-) create mode 100644 jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt create mode 100644 jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt create mode 100644 jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt create mode 100644 jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ContextMenuConfiguration.kt create mode 100644 jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt create mode 100644 jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicExtensionContextMenuGroup.kt create mode 100644 jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ExtensionContextMenuProvider.kt diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/ActionConstants.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/ActionConstants.kt index e8c85ca6..6715d5bf 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/ActionConstants.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/ActionConstants.kt @@ -8,54 +8,83 @@ package com.sina.weibo.agent.actions /** * Constants for action names displayed in the UI. * These represent the text shown to users in menus and context options. + * + * @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" } /** Type alias for prompt type identifiers */ +@Deprecated("Use extension-specific type aliases instead") typealias SupportPromptType = String /** Type alias for prompt parameters map */ +@Deprecated("Use extension-specific type aliases instead") typealias PromptParams = Map /** * Data class representing a prompt configuration with a template string. * Templates contain placeholders that will be replaced with actual values. + * + * @deprecated This class is deprecated in favor of extension-specific prompt configurations. + * Use RooCodeSupportPromptConfig for Roo Code extension or Cline-specific prompt configurations for Cline extension. */ +@Deprecated("Use extension-specific prompt configurations instead") data class SupportPromptConfig(val template: String) /** * Collection of predefined prompt configurations for different use cases. * Each configuration contains a template with placeholders for dynamic content. + * + * @deprecated This object is deprecated in favor of extension-specific prompt configurations. + * Use RooCodeSupportPromptConfigs for Roo Code extension or Cline-specific prompt configurations for Cline extension. */ +@Deprecated("Use extension-specific prompt configurations instead") object SupportPromptConfigs { /** * Template for enhancing user prompts. @@ -211,7 +240,11 @@ Please provide: /** * Utility object for working with support prompts. * Provides methods for creating and customizing prompts based on templates. + * + * @deprecated This object is deprecated in favor of extension-specific prompt utilities. + * Use RooCodeSupportPrompt for Roo Code extension or Cline-specific prompt utilities for Cline extension. */ +@Deprecated("Use extension-specific prompt utilities instead") object SupportPrompt { /** * Generates formatted diagnostic text from a list of diagnostic items. diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RightClickChatActionGroup.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RightClickChatActionGroup.kt index 7d917342..80d58469 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RightClickChatActionGroup.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RightClickChatActionGroup.kt @@ -10,9 +10,14 @@ import com.intellij.openapi.project.DumbAware /** * 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. + * * Implements DumbAware to ensure the action works during indexing, and ActionUpdateThreadAware * to specify which thread should handle action updates. */ +@Deprecated("Use DynamicExtensionContextMenuGroup instead for extension-based context menus") class RightClickChatActionGroup : DefaultActionGroup(), DumbAware, ActionUpdateThreadAware { /** diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionManager.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionManager.kt index d557e66b..c112ed16 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionManager.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionManager.kt @@ -185,6 +185,14 @@ class ExtensionManager(private val project: Project) { LOG.warn("Failed to update button configuration", e) } + // Update context menu configuration + try { + val contextMenuManager = com.sina.weibo.agent.extensions.ui.contextmenu.DynamicContextMenuManager.getInstance(project) + contextMenuManager.setCurrentExtension(extensionId) + } catch (e: Exception) { + LOG.warn("Failed to update context menu configuration", e) + } + // Notify listeners about configuration change try { project.messageBus.syncPublisher(ExtensionChangeListener.EXTENSION_CHANGE_TOPIC) diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt new file mode 100644 index 00000000..ab9ca86a --- /dev/null +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt @@ -0,0 +1,473 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package com.sina.weibo.agent.extensions.plugin.cline + +import com.google.gson.Gson +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.sina.weibo.agent.actions.SupportPrompt +import com.sina.weibo.agent.actions.executeCommand +import com.sina.weibo.agent.extensions.ui.contextmenu.ExtensionContextMenuProvider +import com.sina.weibo.agent.extensions.ui.contextmenu.ContextMenuConfiguration +import com.sina.weibo.agent.extensions.ui.contextmenu.ContextMenuActionType +import com.sina.weibo.agent.webview.WebViewManager + +/** + * Cline extension context menu provider. + * Provides context menu actions specific to Cline AI extension. + * This includes Cline-specific functionality and commands. + */ +class ClineContextMenuProvider : ExtensionContextMenuProvider { + + override fun getExtensionId(): String = "cline" + + override fun getDisplayName(): String = "Cline AI" + + override fun getDescription(): String = "AI-powered coding assistant with Cline-specific context menu" + + override fun isAvailable(project: Project): Boolean { + // Check if cline extension is available + return true + } + + override fun getContextMenuActions(project: Project): List { + return listOf( +// ClineExplainCodeAction(), +// ClineFixCodeAction(), +// ClineImproveCodeAction(), +// ClineAddToContextAction(), +// ClineNewTaskAction() + ) + } + + override fun getContextMenuConfiguration(): ContextMenuConfiguration { + return ClineContextMenuConfiguration() + } + + /** + * Cline context menu configuration - shows core actions only. + */ + private class ClineContextMenuConfiguration : ContextMenuConfiguration { + override fun isActionVisible(actionType: ContextMenuActionType): Boolean { + return when (actionType) { + ContextMenuActionType.EXPLAIN_CODE, + ContextMenuActionType.FIX_CODE, + ContextMenuActionType.IMPROVE_CODE, + ContextMenuActionType.ADD_TO_CONTEXT, + ContextMenuActionType.NEW_TASK -> true + ContextMenuActionType.FIX_LOGIC -> false // Cline doesn't have separate logic fix + } + } + + override fun getVisibleActions(): List { + return listOf( + ContextMenuActionType.EXPLAIN_CODE, + ContextMenuActionType.FIX_CODE, + ContextMenuActionType.IMPROVE_CODE, + ContextMenuActionType.ADD_TO_CONTEXT, + ContextMenuActionType.NEW_TASK + ) + } + } + + /** + * Cline action to explain selected code. + * Uses Cline-specific command and prompt format. + */ + class ClineExplainCodeAction : AnAction("Explain Code (Cline)") { + private val logger: Logger = Logger.getInstance(ClineExplainCodeAction::class.java) + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val editor = e.getData(CommonDataKeys.EDITOR) ?: return + val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return + + val effectiveRange = ClineContextMenuProvider.getEffectiveRange(editor) + if (effectiveRange == null) return + + val args = mutableMapOf() + args["filePath"] = file.path + args["selectedText"] = effectiveRange.text + args["startLine"] = effectiveRange.startLine + 1 + args["endLine"] = effectiveRange.endLine + 1 + + executeCommand("cline.explainCode", project, args) + } + } + + /** + * Cline action to fix code issues. + * Uses Cline-specific command and prompt format. + */ + class ClineFixCodeAction : AnAction("Fix Code (Cline)") { + private val logger: Logger = Logger.getInstance(ClineFixCodeAction::class.java) + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val editor = e.getData(CommonDataKeys.EDITOR) ?: return + val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return + + val effectiveRange = ClineContextMenuProvider.getEffectiveRange(editor) + if (effectiveRange == null) return + + val args = mutableMapOf() + args["filePath"] = file.path + args["selectedText"] = effectiveRange.text + args["startLine"] = effectiveRange.startLine + 1 + args["endLine"] = effectiveRange.endLine + 1 + executeCommand("cline.fixCode", project, args) + } + } + + /** + * Cline action to improve code quality. + * Uses Cline-specific command and prompt format. + */ + class ClineImproveCodeAction : AnAction("Improve Code (Cline)") { + private val logger: Logger = Logger.getInstance(ClineImproveCodeAction::class.java) + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val editor = e.getData(CommonDataKeys.EDITOR) ?: return + val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return + + val effectiveRange = ClineContextMenuProvider.getEffectiveRange(editor) + if (effectiveRange == null) return + + val args = mutableMapOf() + args["filePath"] = file.path + args["selectedText"] = effectiveRange.text + args["startLine"] = effectiveRange.startLine + 1 + args["endLine"] = effectiveRange.endLine + 1 + executeCommand("cline.improveCode", project, args) + + } + } + + /** + * Cline action to add selected code to context. + * Uses Cline-specific command and prompt format. + */ + class ClineAddToContextAction : AnAction("Add to Context (Cline)") { + private val logger: Logger = Logger.getInstance(ClineAddToContextAction::class.java) + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val editor = e.getData(CommonDataKeys.EDITOR) ?: return + val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return + + val effectiveRange = ClineContextMenuProvider.getEffectiveRange(editor) + if (effectiveRange == null) return + + val range = ClineContextMenuProvider.Range( + startLine = effectiveRange.startLine, + startCharacter = effectiveRange.startCharacter, + endLine = effectiveRange.endLine, + endCharacter = effectiveRange.endCharacter + ) + val rangeMap = mapOf( + "startLine" to effectiveRange.startLine, + "startCharacter" to effectiveRange.startCharacter, + "endLine" to effectiveRange.endLine, + "endCharacter" to effectiveRange.endCharacter + ) + + executeCommand("cline.addToChat", project, rangeMap) + } + } + + /** + * Cline action to create a new task. + * Uses Cline-specific command and prompt format. + */ + class ClineNewTaskAction : AnAction("New Task (Cline)") { + private val logger: Logger = Logger.getInstance(ClineNewTaskAction::class.java) + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val editor = e.getData(CommonDataKeys.EDITOR) ?: return + val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return + + val effectiveRange = ClineContextMenuProvider.getEffectiveRange(editor) + if (effectiveRange == null) return + + val args = mutableMapOf() + args["filePath"] = file.path + args["selectedText"] = effectiveRange.text + args["startLine"] = effectiveRange.startLine + 1 + args["endLine"] = effectiveRange.endLine + 1 + + ClineContextMenuProvider.handleClineCodeAction("cline.newTask", "NEW_TASK", args, project) + } + } + + /** + * Data class representing an effective range of selected text. + * Contains the selected text and its start/end line numbers. + * + * @property text The selected text content + * @property startLine The starting line number (0-based) + * @property endLine The ending line number (0-based) + */ + data class EffectiveRange( + val text: String, + val startLine: Int, + val endLine: Int, + val startCharacter: Int, + val endCharacter: Int, + ) + + companion object { + /** + * Gets the effective range and text from the current editor selection. + * + * @param editor The current editor instance + * @return EffectiveRange object containing selected text and line numbers, or null if no selection + */ + fun getEffectiveRange(editor: com.intellij.openapi.editor.Editor): EffectiveRange? { + val document = editor.document + val selectionModel = editor.selectionModel + + return if (selectionModel.hasSelection()) { + val selectedText = selectionModel.selectedText ?: "" + val startLine = document.getLineNumber(selectionModel.selectionStart) + val endLine = document.getLineNumber(selectionModel.selectionEnd) + val startCharacter = selectionModel.selectionStart - document.getLineStartOffset(startLine) + val endCharacter = selectionModel.selectionEnd - document.getLineStartOffset(endLine) + EffectiveRange(selectedText, startLine, endLine, startCharacter, endCharacter) + } else { + null + } + } + + /** + * Core logic for handling Cline code actions. + * Processes different types of commands and sends appropriate messages to the webview. + * Uses Cline-specific command format and prompt templates. + * + * @param command The Cline command identifier + * @param promptType The type of prompt to use + * @param params Parameters for the action + * @param project The current project + */ + fun handleClineCodeAction(command: String, promptType: String, params: Map, 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") -> { + mapOf( + "type" to "invoke", + "invoke" to "setChatBoxMessage", + "text" to createClinePrompt("ADD_TO_CONTEXT", params) + ) + } + // Command executed in new task + else -> { + val promptParams = params + 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) + ) + } + } + + // Convert to JSON and send + val messageJson = com.google.gson.Gson().toJson(messageContent) + latestWebView.postMessageToWebView(messageJson) + } + + /** + * Creates a Cline-specific prompt by replacing placeholders in a template with actual values. + * + * @param promptType The type of prompt to create + * @param params Parameters to substitute into the template + * @return The final prompt with all placeholders replaced + */ + fun createClinePrompt(promptType: String, params: Map): String { + val template = getClinePromptTemplate(promptType) + return replacePlaceholders(template, params) + } + + /** + * Gets the Cline-specific template for a specific prompt type. + * These templates are optimized for Cline AI's capabilities and style. + * + * @param type The type of prompt to retrieve + * @return The template string for the specified prompt type + */ + fun getClinePromptTemplate(type: String): String { + return when (type) { + "EXPLAIN" -> """Please explain this code from ${'$'}{filePath} (lines ${'$'}{startLine}-${'$'}{endLine}): + +```${'$'}{selectedText}``` + +Focus on: +- What the code does and its purpose +- Key components and their relationships +- Important patterns or techniques used +- Any potential improvements or considerations""" + + "FIX" -> """Please fix any issues in this code from ${'$'}{filePath} (lines ${'$'}{startLine}-${'$'}{endLine}): + +```${'$'}{selectedText}``` + +Please: +- Identify and fix any bugs or issues +- Improve error handling and edge cases +- Provide the corrected code +- Explain what was fixed and why""" + + "IMPROVE" -> """Please improve this code from ${'$'}{filePath} (lines ${'$'}{startLine}-${'$'}{endLine}): + +```${'$'}{selectedText}``` + +Focus on improvements for: +- Code readability and maintainability +- Performance optimization +- Best practices and modern patterns +- Error handling and robustness + +Provide the improved code with explanations for each enhancement.""" + + "ADD_TO_CONTEXT" -> """Code from ${'$'}{filePath} (lines ${'$'}{startLine}-${'$'}{endLine}): +```${'$'}{selectedText}```""" + + "NEW_TASK" -> """${'$'}{selectedText}""" + + else -> "" + } + } + + /** + * Replaces placeholders in a template with actual values. + * + * @param template The prompt template with placeholders + * @param params Map of parameter values to replace placeholders + * @return The processed prompt with placeholders replaced by actual values + */ + fun replacePlaceholders(template: String, params: Map): String { + val pattern = Regex("""\$\{(.*?)}""") + return pattern.replace(template) { matchResult -> + val key = matchResult.groupValues[1] + params[key]?.toString() ?: "" + } + } + } + + /** + * 与 VS Code 完全一致的 Position。 + */ + data class Position(val line: Int, val character: Int) : Comparable { + + init { + require(line >= 0) { "line must be non-negative" } + require(character >= 0) { "character must be non-negative" } + } + + override fun compareTo(other: Position): Int { + return when { + line != other.line -> line - other.line + else -> character - other.character + } + } + + fun isBefore(other: Position): Boolean = compareTo(other) < 0 + fun isBeforeOrEqual(other: Position): Boolean = compareTo(other) <= 0 + fun isAfter(other: Position): Boolean = compareTo(other) > 0 + fun isAfterOrEqual(other: Position): Boolean = compareTo(other) >= 0 + + fun translate(lineDelta: Int = 0, characterDelta: Int = 0): Position { + return Position(line + lineDelta, character + characterDelta) + } + + fun translate(change: Position): Position { + return Position(line + change.line, character + change.character) + } + + fun with(line: Int = this.line, character: Int = this.character): Position { + return Position(line, character) + } + + override fun toString(): String = "($line,$character)" + } + + /** + * 与 VS Code 完全一致的 Range。 + */ + class Range(startLine: Int, startCharacter: Int, endLine: Int, endCharacter: Int) { + + val start: Position + val end: Position + + constructor(start: Position, end: Position) : + this(start.line, start.character, end.line, end.character) + + init { + val s = Position(startLine, startCharacter) + val e = Position(endLine, endCharacter) + if (s <= e) { + start = s + end = e + } else { + start = e + end = s + } + } + + val isEmpty: Boolean get() = start == end + val isSingleLine: Boolean get() = start.line == end.line + + fun contains(position: Position): Boolean { + return position >= start && position <= end + } + + fun contains(range: Range): Boolean { + return range.start >= start && range.end <= end + } + + fun intersection(range: Range): Range? { + val newStart = if (start >= range.start) start else range.start + val newEnd = if (end <= range.end) end else range.end + return if (newStart <= newEnd) Range(newStart, newEnd) else null + } + + fun union(range: Range): Range { + val newStart = if (start <= range.start) start else range.start + val newEnd = if (end >= range.end) end else range.end + return Range(newStart, newEnd) + } + + fun with(start: Position = this.start, end: Position = this.end): Range { + return Range(start, end) + } + + override fun equals(other: Any?): Boolean { + return other is Range && other.start == start && other.end == end + } + + override fun hashCode(): Int { + return 31 * start.hashCode() + end.hashCode() + } + + override fun toString(): String { + return "[${start.toString()}~${end.toString()}]" + } + } +} diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt new file mode 100644 index 00000000..bda12fbb --- /dev/null +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt @@ -0,0 +1,295 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package com.sina.weibo.agent.extensions.plugin.roo + +/** + * Constants for Roo Code extension action names displayed in the UI. + * These represent the text shown to users in menus and context options. + * + * This file contains all the constants that were previously in ActionConstants.kt + * for the roo-cline functionality, now organized under the Roo Code extension. + */ +object RooCodeActionNames { + /** Action to explain selected code */ + const val EXPLAIN = "Explain Code" + /** Action to fix issues in selected code */ + const val FIX = "Fix Code" + /** Action to fix logical issues in selected code */ + const val FIX_LOGIC = "Fix Logic" + /** Action to improve selected code */ + const val IMPROVE = "Improve Code" + /** Action to add selected code to context */ + const val ADD_TO_CONTEXT = "Add to Context" + /** Action to create a new task */ + const val NEW_TASK = "New Task" +} + +/** + * Command identifiers used for internal command registration and execution. + * These IDs are used to register commands with the IDE. + * + * All commands use the roo-cline prefix for backward compatibility. + */ +object RooCodeCommandIds { + /** Command ID for explaining code */ + const val EXPLAIN = "roo-cline.explainCode" + /** Command ID for fixing code */ + const val FIX = "roo-cline.fixCode" + /** Command ID for improving code */ + const val IMPROVE = "roo-cline.improveCode" + /** Command ID for adding to context */ + const val ADD_TO_CONTEXT = "roo-cline.addToContext" + /** Command ID for creating a new task */ + const val NEW_TASK = "roo-cline.newTask" +} + +/** Type alias for prompt type identifiers */ +typealias RooCodeSupportPromptType = String +/** Type alias for prompt parameters map */ +typealias RooCodePromptParams = Map + +/** + * Data class representing a prompt configuration with a template string. + * Templates contain placeholders that will be replaced with actual values. + */ +data class RooCodeSupportPromptConfig(val template: String) + +/** + * Collection of predefined prompt configurations for different use cases. + * Each configuration contains a template with placeholders for dynamic content. + * + * These are the same templates that were previously in ActionConstants.kt, + * now organized under the Roo Code extension. + */ +object RooCodeSupportPromptConfigs { + /** + * Template for enhancing user prompts. + * Instructs the AI to generate an improved version of the user's input. + */ + val ENHANCE = RooCodeSupportPromptConfig( + """Generate an enhanced version of this prompt (reply with only the enhanced prompt - no conversation, explanations, lead-in, bullet points, placeholders, or surrounding quotes): + +${'$'}{userInput}""" + ) + + /** + * Template for explaining code. + * Provides structure for code explanation requests with file path and line information. + */ + val EXPLAIN = RooCodeSupportPromptConfig( + """Explain the following code from file path ${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} +${'$'}{userInput} + +``` +${'$'}{selectedText} +``` + +Please provide a clear and concise explanation of what this code does, including: +1. The purpose and functionality +2. Key components and their interactions +3. Important patterns or techniques used""" + ) + + /** + * Template for fixing code issues. + * Includes diagnostic information and structured format for issue resolution. + */ + val FIX = RooCodeSupportPromptConfig( + """Fix any issues in the following code from file path ${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} +${'$'}{diagnosticText} +${'$'}{userInput} + +``` +${'$'}{selectedText} +``` + +Please: +1. Address all detected problems listed above (if any) +2. Identify any other potential bugs or issues +3. Provide corrected code +4. Explain what was fixed and why""" + ) + + /** + * Template for improving code quality. + * Focuses on readability, performance, best practices, and error handling. + */ + val IMPROVE = RooCodeSupportPromptConfig( + """Improve the following code from file path ${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} +${'$'}{userInput} + +``` +${'$'}{selectedText} +``` + +Please suggest improvements for: +1. Code readability and maintainability +2. Performance optimization +3. Best practices and patterns +4. Error handling and edge cases + +Provide the improved code along with explanations for each enhancement.""" + ) + + /** + * Template for adding code to context. + * Simple format that includes file path, line range, and selected code. + */ + val ADD_TO_CONTEXT = RooCodeSupportPromptConfig( + """${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} +``` +${'$'}{selectedText} +```""" + ) + + /** + * Template for adding terminal output to context. + * Includes user input and terminal content. + */ + val TERMINAL_ADD_TO_CONTEXT = RooCodeSupportPromptConfig( + """${'$'}{userInput} +Terminal output: +``` +${'$'}{terminalContent} +```""" + ) + + /** + * Template for fixing terminal commands. + * Structured format for identifying and resolving command issues. + */ + val TERMINAL_FIX = RooCodeSupportPromptConfig( + """${'$'}{userInput} +Fix this terminal command: +``` +${'$'}{terminalContent} +``` + +Please: +1. Identify any issues in the command +2. Provide the corrected command +3. Explain what was fixed and why""" + ) + + /** + * Template for explaining terminal commands. + * Provides structure for command explanation with focus on functionality and behavior. + */ + val TERMINAL_EXPLAIN = RooCodeSupportPromptConfig( + """${'$'}{userInput} +Explain this terminal command: +``` +${'$'}{terminalContent} +``` + +Please provide: +1. What the command does +2. Explanation of each part/flag +3. Expected output and behavior""" + ) + + /** + * Template for creating a new task. + * Simple format that passes through user input directly. + */ + val NEW_TASK = RooCodeSupportPromptConfig( + """${'$'}{userInput}""" + ) + + /** + * Map of all available prompt configurations indexed by their type identifiers. + * Used for lookup when creating prompts. + */ + 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 + ) +} + +/** + * Utility object for working with Roo Code support prompts. + * Provides methods for creating and customizing prompts based on templates. + * + * This is the same functionality that was previously in ActionConstants.kt, + * now organized under the Roo Code extension. + */ +object RooCodeSupportPrompt { + /** + * Generates formatted diagnostic text from a list of diagnostic items. + * + * @param diagnostics List of diagnostic items containing source, message, and code + * @return Formatted string of diagnostic messages or empty string if no diagnostics + */ + private fun generateDiagnosticText(diagnostics: List>?): String { + if (diagnostics.isNullOrEmpty()) return "" + 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)" } ?: ""}" + } + } + + /** + * Creates a prompt by replacing placeholders in a template with actual values. + * + * @param template The prompt template with placeholders + * @param params Map of parameter values to replace placeholders + * @return The processed prompt with placeholders replaced by actual values + */ + private fun createPrompt(template: String, params: RooCodePromptParams): String { + val pattern = Regex("""\$\{(.*?)}""") + return pattern.replace(template) { matchResult -> + val key = matchResult.groupValues[1] + if (key == "diagnosticText") { + generateDiagnosticText(params["diagnostics"] as? List>) + } 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 + "" + } + } + } + + /** + * Gets the template for a specific prompt type, with optional custom overrides. + * + * @param customSupportPrompts Optional map of custom prompt templates + * @param type The type of prompt to retrieve + * @return The template string for the specified prompt type + */ + fun get(customSupportPrompts: Map?, type: RooCodeSupportPromptType): String { + return customSupportPrompts?.get(type) ?: RooCodeSupportPromptConfigs.configs[type]?.template ?: "" + } + + /** + * Creates a complete prompt by getting the template and replacing placeholders. + * + * @param type The type of prompt to create + * @param params Parameters to substitute into the template + * @param customSupportPrompts Optional custom prompt templates + * @return The final prompt with all placeholders replaced + */ + fun create(type: RooCodeSupportPromptType, params: RooCodePromptParams, customSupportPrompts: Map? = null): String { + val template = get(customSupportPrompts, type) + return createPrompt(template, params) + } +} diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt new file mode 100644 index 00000000..03896a1b --- /dev/null +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt @@ -0,0 +1,417 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package com.sina.weibo.agent.extensions.plugin.roo + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.sina.weibo.agent.actions.SupportPrompt +import com.sina.weibo.agent.extensions.ui.contextmenu.ExtensionContextMenuProvider +import com.sina.weibo.agent.extensions.ui.contextmenu.ContextMenuConfiguration +import com.sina.weibo.agent.extensions.ui.contextmenu.ContextMenuActionType +import com.sina.weibo.agent.webview.WebViewManager + +/** + * Roo Code extension context menu provider. + * Provides context menu actions specific to Roo Code extension. + * This includes all the original roo-cline functionality. + */ +class RooCodeContextMenuProvider : ExtensionContextMenuProvider { + + override fun getExtensionId(): String = "roo-code" + + override fun getDisplayName(): String = "Roo Code" + + override fun getDescription(): String = "AI-powered code assistant with full context menu capabilities" + + override fun isAvailable(project: Project): Boolean { + // Check if roo-code extension is available + return true + } + + override fun getContextMenuActions(project: Project): List { + return listOf( + ExplainCodeAction(), + FixCodeAction(), + FixLogicAction(), + ImproveCodeAction(), + AddToContextAction(), + ) + } + + override fun getContextMenuConfiguration(): ContextMenuConfiguration { + return RooCodeContextMenuConfiguration() + } + + /** + * Roo Code context menu configuration - shows all actions (full-featured). + */ + private class RooCodeContextMenuConfiguration : ContextMenuConfiguration { + override fun isActionVisible(actionType: ContextMenuActionType): Boolean { + return true // All actions are visible for Roo Code + } + + override fun getVisibleActions(): List { + return ContextMenuActionType.values().toList() + } + } + + /** + * Action to explain selected code. + * Creates a new task with the explanation request. + */ + class ExplainCodeAction : AnAction("Explain Code") { + private val logger: Logger = Logger.getInstance(ExplainCodeAction::class.java) + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val editor = e.getData(CommonDataKeys.EDITOR) ?: return + val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return + + val effectiveRange = RooCodeContextMenuProvider.getEffectiveRange(editor) + if (effectiveRange == null) return + + val args = mutableMapOf() + args["filePath"] = file.path + args["selectedText"] = effectiveRange.text + args["startLine"] = effectiveRange.startLine + 1 + args["endLine"] = effectiveRange.endLine + 1 + + RooCodeContextMenuProvider.handleCodeAction("roo-cline.explainCode.InCurrentTask", "EXPLAIN", args, project) + } + } + + /** + * Action to fix code issues. + * Creates a new task with the fix request. + */ + class FixCodeAction : AnAction("Fix Code") { + private val logger: Logger = Logger.getInstance(FixCodeAction::class.java) + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val editor = e.getData(CommonDataKeys.EDITOR) ?: return + val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return + + val effectiveRange = RooCodeContextMenuProvider.getEffectiveRange(editor) + if (effectiveRange == null) return + + val args = mutableMapOf() + args["filePath"] = file.path + args["selectedText"] = effectiveRange.text + args["startLine"] = effectiveRange.startLine + 1 + args["endLine"] = effectiveRange.endLine + 1 + + RooCodeContextMenuProvider.handleCodeAction("roo-cline.fixCode.InCurrentTask", "FIX", args, project) + } + } + + /** + * Action to fix logical issues in code. + * Creates a new task with the logic fix request. + */ + class FixLogicAction : AnAction("Fix Logic") { + private val logger: Logger = Logger.getInstance(FixLogicAction::class.java) + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val editor = e.getData(CommonDataKeys.EDITOR) ?: return + val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return + + val effectiveRange = RooCodeContextMenuProvider.getEffectiveRange(editor) + if (effectiveRange == null) return + + val args = mutableMapOf() + args["filePath"] = file.path + args["selectedText"] = effectiveRange.text + args["startLine"] = effectiveRange.startLine + 1 + args["endLine"] = effectiveRange.endLine + 1 + + RooCodeContextMenuProvider.handleCodeAction("roo-cline.fixCode.InCurrentTask", "FIX", args, project) + } + } + + /** + * Action to improve code quality. + * Creates a new task with the improvement request. + */ + class ImproveCodeAction : AnAction("Improve Code") { + private val logger: Logger = Logger.getInstance(ImproveCodeAction::class.java) + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val editor = e.getData(CommonDataKeys.EDITOR) ?: return + val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return + + val effectiveRange = RooCodeContextMenuProvider.getEffectiveRange(editor) + if (effectiveRange == null) return + + val args = mutableMapOf() + args["filePath"] = file.path + args["selectedText"] = effectiveRange.text + args["startLine"] = effectiveRange.startLine + 1 + args["endLine"] = effectiveRange.endLine + 1 + + RooCodeContextMenuProvider.handleCodeAction("roo-cline.improveCode.InCurrentTask", "IMPROVE", args, project) + } + } + + /** + * Action to add selected code to context. + * Adds the code to the current chat context. + */ + class AddToContextAction : AnAction("Add to Context") { + private val logger: Logger = Logger.getInstance(AddToContextAction::class.java) + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val editor = e.getData(CommonDataKeys.EDITOR) ?: return + val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return + + val effectiveRange = RooCodeContextMenuProvider.getEffectiveRange(editor) + if (effectiveRange == null) return + + val args = mutableMapOf() + args["filePath"] = file.path + args["selectedText"] = effectiveRange.text + args["startLine"] = effectiveRange.startLine + 1 + args["endLine"] = effectiveRange.endLine + 1 + + RooCodeContextMenuProvider.handleCodeAction("roo-cline.addToContext", "ADD_TO_CONTEXT", args, project) + } + } + + /** + * Action to create a new task. + * Opens a new task with the selected code. + */ + class NewTaskAction : AnAction("New Task") { + private val logger: Logger = Logger.getInstance(NewTaskAction::class.java) + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val editor = e.getData(CommonDataKeys.EDITOR) ?: return + val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return + + val effectiveRange = RooCodeContextMenuProvider.getEffectiveRange(editor) + if (effectiveRange == null) return + + val args = mutableMapOf() + args["filePath"] = file.path + args["selectedText"] = effectiveRange.text + args["startLine"] = effectiveRange.startLine + 1 + args["endLine"] = effectiveRange.endLine + 1 + + RooCodeContextMenuProvider.handleCodeAction("roo-cline.newTask", "NEW_TASK", args, project) + } + } + + /** + * Data class representing an effective range of selected text. + * Contains the selected text and its start/end line numbers. + * + * @property text The selected text content + * @property startLine The starting line number (0-based) + * @property endLine The ending line number (0-based) + */ + data class EffectiveRange( + val text: String, + val startLine: Int, + val endLine: Int + ) + + companion object { + /** + * Gets the effective range and text from the current editor selection. + * + * @param editor The current editor instance + * @return EffectiveRange object containing selected text and line numbers, or null if no selection + */ + fun getEffectiveRange(editor: com.intellij.openapi.editor.Editor): EffectiveRange? { + val document = editor.document + val selectionModel = editor.selectionModel + + 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 + } + } + + /** + * 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 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 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 + 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 + } 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) + } + + /** + * Creates a prompt by replacing placeholders in a template with actual values. + * + * @param promptType The type of prompt to create + * @param params Parameters to substitute into the template + * @return The final prompt with all placeholders replaced + */ + fun createPrompt(promptType: String, params: Map): String { + val template = getPromptTemplate(promptType) + return replacePlaceholders(template, params) + } + + /** + * Gets the template for a specific prompt type. + * + * @param type The type of prompt to retrieve + * @return The template string for the specified prompt type + */ + fun getPromptTemplate(type: String): String { + return when (type) { + "EXPLAIN" -> """Explain the following code from file path ${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} + +``` +${'$'}{selectedText} +``` + +Please provide a clear and concise explanation of what this code does, including: +1. The purpose and functionality +2. Key components and their interactions +3. Important patterns or techniques used""" + "FIX" -> """Fix any issues in the following code from file path ${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} + +``` +${'$'}{selectedText} +``` + +Please: +1. Address all detected problems listed above (if any) +2. Identify any other potential bugs or issues +3. Provide corrected code +4. Explain what was fixed and why""" + "IMPROVE" -> """Improve the following code from file path ${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} + +``` +${'$'}{selectedText} +``` + +Please suggest improvements for: +1. Code readability and maintainability +2. Performance optimization +3. Best practices and patterns +4. Error handling and edge cases + +Provide the improved code along with explanations for each enhancement.""" + "ADD_TO_CONTEXT" -> """${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} +``` +${'$'}{selectedText} +```""" + "NEW_TASK" -> """${'$'}{selectedText}""" + else -> "" + } + } + + /** + * Replaces placeholders in a template with actual values. + * + * @param template The prompt template with placeholders + * @param params Map of parameter values to replace placeholders + * @return The processed prompt with placeholders replaced by actual values + */ + fun replacePlaceholders(template: String, params: Map): String { + val pattern = Regex("""\$\{(.*?)}""") + return pattern.replace(template) { matchResult -> + val key = matchResult.groupValues[1] + params[key]?.toString() ?: "" + } + } + } +} diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ContextMenuConfiguration.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ContextMenuConfiguration.kt new file mode 100644 index 00000000..98345a99 --- /dev/null +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ContextMenuConfiguration.kt @@ -0,0 +1,34 @@ +package com.sina.weibo.agent.extensions.ui.contextmenu + +/** + * Context menu configuration interface. + * Defines which context menu actions should be visible for a specific extension. + */ +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 +} + +/** + * Context menu action types that can be configured. + * These represent the different types of right-click context menu actions. + */ +enum class ContextMenuActionType { + EXPLAIN_CODE, + FIX_CODE, + FIX_LOGIC, + IMPROVE_CODE, + ADD_TO_CONTEXT, + NEW_TASK +} diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt new file mode 100644 index 00000000..a4836754 --- /dev/null +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package com.sina.weibo.agent.extensions.ui.contextmenu + +import com.intellij.openapi.components.Service +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.sina.weibo.agent.extensions.core.ExtensionManager +import com.sina.weibo.agent.extensions.plugin.cline.ClineContextMenuProvider +import com.sina.weibo.agent.extensions.plugin.roo.RooCodeContextMenuProvider + +/** + * Dynamic context menu manager that controls which context menu actions are available + * based on the current extension type. + * This manager works in conjunction with DynamicExtensionContextMenuGroup to provide + * dynamic context menu functionality. + */ +@Service(Service.Level.PROJECT) +class DynamicContextMenuManager(private val project: Project) { + + private val logger = Logger.getInstance(DynamicContextMenuManager::class.java) + + // Current extension ID + @Volatile + private var currentExtensionId: String? = null + + companion object { + /** + * Get dynamic context menu manager instance + */ + fun getInstance(project: Project): DynamicContextMenuManager { + return project.getService(DynamicContextMenuManager::class.java) + ?: error("DynamicContextMenuManager not found") + } + } + + /** + * Initialize the dynamic context menu manager + */ + 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) + } + } + + /** + * Set the current extension and update context menu configuration + */ + fun setCurrentExtension(extensionId: String) { + logger.info("Setting current extension to: $extensionId") + currentExtensionId = extensionId + + // Refresh all context menus to reflect the change + refreshContextMenus() + } + + /** + * Get the current extension ID + */ + fun getCurrentExtensionId(): String? { + return currentExtensionId + } + + /** + * Get context menu configuration for the current extension + */ + fun getContextMenuConfiguration(): ContextMenuConfiguration { + val contextMenuProvider = getContextMenuProvider(currentExtensionId) + return contextMenuProvider?.getContextMenuConfiguration() ?: DefaultContextMenuConfiguration() + } + + /** + * Get context menu actions for the current extension + */ + fun getContextMenuActions(): List { + val contextMenuProvider = getContextMenuProvider(currentExtensionId) + return contextMenuProvider?.getContextMenuActions(project) ?: emptyList() + } + + /** + * Get context menu provider for the specified extension. + * + * @param extensionId The extension ID + * @return Context menu provider instance or null if not found + */ + 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 + } + } + + /** + * Check if a specific context menu action should be visible for the current extension + */ + fun isActionVisible(actionType: ContextMenuActionType): Boolean { + val config = getContextMenuConfiguration() + return config.isActionVisible(actionType) + } + + /** + * Refresh all context menus to reflect current configuration + */ + private fun refreshContextMenus() { + try { + // Get the action manager + val actionManager = com.intellij.openapi.actionSystem.ActionManager.getInstance() + + // Refresh the dynamic context menu actions group + val dynamicGroup = actionManager.getAction("RunVSAgent.DynamicExtensionContextMenu") + dynamicGroup?.let { group -> + // Create a dummy event for updating + val dummyEvent = com.intellij.openapi.actionSystem.AnActionEvent.createFromAnAction( + group, + null, + "DynamicContextMenuManager", + com.intellij.ide.DataManager.getInstance().dataContext + ) + group.update(dummyEvent) + } + + logger.debug("Context menus refreshed for extension: $currentExtensionId") + } catch (e: Exception) { + logger.warn("Failed to refresh context menus", e) + } + } + + /** + * Dispose the dynamic context menu manager + */ + fun dispose() { + logger.info("Disposing dynamic context menu manager") + currentExtensionId = null + } +} + +/** + * Default context menu configuration - shows minimal actions + */ +class DefaultContextMenuConfiguration : ContextMenuConfiguration { + override fun isActionVisible(actionType: ContextMenuActionType): Boolean { + return when (actionType) { + ContextMenuActionType.EXPLAIN_CODE, + ContextMenuActionType.ADD_TO_CONTEXT -> true + ContextMenuActionType.FIX_CODE, + ContextMenuActionType.FIX_LOGIC, + ContextMenuActionType.IMPROVE_CODE, + ContextMenuActionType.NEW_TASK -> false + } + } + + override fun getVisibleActions(): List { + return listOf( + ContextMenuActionType.EXPLAIN_CODE, + ContextMenuActionType.ADD_TO_CONTEXT + ) + } +} diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicExtensionContextMenuGroup.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicExtensionContextMenuGroup.kt new file mode 100644 index 00000000..5c242939 --- /dev/null +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicExtensionContextMenuGroup.kt @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package com.sina.weibo.agent.extensions.ui.contextmenu + +import com.intellij.openapi.actionSystem.* +import com.intellij.openapi.project.DumbAware + +/** + * Dynamic extension context menu action group. + * This class manages the dynamic context menu actions that appear in the right-click menu + * when text is selected, based on the current active extension. + * + * Implements DumbAware to ensure the action works during indexing, and ActionUpdateThreadAware + * to specify which thread should handle action updates. + */ +class DynamicExtensionContextMenuGroup : DefaultActionGroup(), DumbAware, ActionUpdateThreadAware { + + /** + * Manager that provides the current extension's context menu actions. + */ + private var contextMenuManager: DynamicContextMenuManager? = null + + /** + * Updates the action group based on the current context and extension. + * This method is called each time the menu needs to be displayed. + * + * @param e The action event containing context information + */ + override fun update(e: AnActionEvent) { + removeAll() + + // Check if there is an editor and selected text + val editor = e.getData(CommonDataKeys.EDITOR) + val hasSelection = editor?.selectionModel?.hasSelection() == true + + if (hasSelection) { + loadDynamicContextMenuActions(e) + } + + // Set the visibility of the action group + e.presentation.isVisible = hasSelection + } + + /** + * Loads dynamic context menu actions into this action group based on the current extension. + * + * @param e The action event containing context information + */ + private fun loadDynamicContextMenuActions(e: AnActionEvent) { + val project = e.project ?: return + + // Get or initialize the context menu manager + if (contextMenuManager == null) { + try { + contextMenuManager = DynamicContextMenuManager.getInstance(project) + contextMenuManager?.initialize() + } catch (e: Exception) { + // If the manager is not available, fall back to default actions + return + } + } + + // Get actions from the current extension + val actions = contextMenuManager?.getContextMenuActions() ?: emptyList() + actions.forEach { action -> + add(action) + } + } + + /** + * Specifies which thread should be used for updating this action. + * EDT (Event Dispatch Thread) is used for UI-related operations. + * + * @return The thread to use for action updates + */ + override fun getActionUpdateThread(): ActionUpdateThread { + return ActionUpdateThread.EDT + } +} diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ExtensionContextMenuProvider.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ExtensionContextMenuProvider.kt new file mode 100644 index 00000000..4c0dda82 --- /dev/null +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/ExtensionContextMenuProvider.kt @@ -0,0 +1,50 @@ +package com.sina.weibo.agent.extensions.ui.contextmenu + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.project.Project + +/** + * Extension context menu provider interface. + * Each extension should implement this interface to provide its specific context menu actions. + * This allows different extensions to have different right-click context menus. + */ +interface ExtensionContextMenuProvider { + + /** + * Get extension identifier. + * @return Unique extension identifier + */ + fun getExtensionId(): String + + /** + * Get extension display name. + * @return Human-readable extension name + */ + fun getDisplayName(): String + + /** + * Get extension description. + * @return Extension description + */ + fun getDescription(): String + + /** + * Check if extension is available. + * @param project Current project + * @return true if extension is available, false otherwise + */ + fun isAvailable(project: Project): Boolean + + /** + * Get context menu actions for this extension. + * @param project Current project + * @return List of AnAction instances representing the context menu actions + */ + fun getContextMenuActions(project: Project): List + + /** + * Get context menu configuration for this extension. + * @return ContextMenuConfiguration object defining action visibility + */ + fun getContextMenuConfiguration(): ContextMenuConfiguration +} diff --git a/jetbrains_plugin/src/main/resources/META-INF/plugin.xml b/jetbrains_plugin/src/main/resources/META-INF/plugin.xml index 3d7a6c94..1bbd5e6f 100644 --- a/jetbrains_plugin/src/main/resources/META-INF/plugin.xml +++ b/jetbrains_plugin/src/main/resources/META-INF/plugin.xml @@ -127,8 +127,8 @@ SPDX-License-Identifier: Apache-2.0 - + From 008ed8d5a55048959843cee25aa3a5f5fcff911a Mon Sep 17 00:00:00 2001 From: MicroLee Date: Thu, 21 Aug 2025 16:34:25 +0800 Subject: [PATCH 2/4] refactor: Use the EDT thread to refresh the UI. --- .../agent/extensions/core/ExtensionManager.kt | 20 +++++++---- .../extensions/core/ExtensionSwitcher.kt | 17 +++++---- .../ui/buttons/DynamicButtonManager.kt | 35 ++++++++++--------- .../contextmenu/DynamicContextMenuManager.kt | 14 +++----- 4 files changed, 47 insertions(+), 39 deletions(-) diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionManager.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionManager.kt index c112ed16..bdfd2d36 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionManager.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionManager.kt @@ -160,12 +160,14 @@ class ExtensionManager(private val project: Project) { * Set current extension provider * Note: This only updates the configuration and UI state, does not restart the extension process */ - fun setCurrentProvider(extensionId: String): Boolean { + 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 + } + // Initialize new provider (but don't restart the process) provider.initialize(project) @@ -179,16 +181,20 @@ class ExtensionManager(private val project: Project) { // Update button configuration try { - val buttonManager = DynamicButtonManager.getInstance(project) - buttonManager.setCurrentExtension(extensionId) + if (forceRestart == false) { + val buttonManager = DynamicButtonManager.getInstance(project) + buttonManager.setCurrentExtension(extensionId) + } } catch (e: Exception) { LOG.warn("Failed to update button configuration", e) } // Update context menu configuration try { - val contextMenuManager = com.sina.weibo.agent.extensions.ui.contextmenu.DynamicContextMenuManager.getInstance(project) - contextMenuManager.setCurrentExtension(extensionId) + if (forceRestart == false) { + val contextMenuManager = com.sina.weibo.agent.extensions.ui.contextmenu.DynamicContextMenuManager.getInstance(project) + contextMenuManager.setCurrentExtension(extensionId) + } } catch (e: Exception) { LOG.warn("Failed to update context menu configuration", e) } diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionSwitcher.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionSwitcher.kt index 580eef47..f5e01f81 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionSwitcher.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/ExtensionSwitcher.kt @@ -150,13 +150,16 @@ class ExtensionSwitcher(private val project: Project) { return withContext(Dispatchers.IO) { try { // Step 1: Update extension manager (this will save configuration) - updateExtensionManager(extensionId) + updateExtensionManager(extensionId, forceRestart) - // Step 2: Update button configuration - updateButtonConfiguration(extensionId) + if (forceRestart) { + // Step 2: Update button configuration + updateButtonConfiguration(extensionId) + + // Step 3: Notify UI components + notifyExtensionChanged(extensionId) + } - // Step 3: Notify UI components - notifyExtensionChanged(extensionId) LOG.info("Extension switching configuration saved successfully: $extensionId (will take effect on next startup)") true @@ -171,12 +174,12 @@ class ExtensionSwitcher(private val project: Project) { * Update extension manager with new provider * This will save the configuration but not restart the process */ - private suspend fun updateExtensionManager(extensionId: String) { + private suspend fun updateExtensionManager(extensionId: String, forceRestart: Boolean) { withContext(Dispatchers.Main) { val extensionManager = ExtensionManager.getInstance(project) // Set new extension provider (this will save configuration) - val success = extensionManager.setCurrentProvider(extensionId) + val success = extensionManager.setCurrentProvider(extensionId, forceRestart) if (!success) { throw IllegalStateException("Failed to set extension provider: $extensionId") } diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/buttons/DynamicButtonManager.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/buttons/DynamicButtonManager.kt index 74d3ecfe..9d0cd702 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/buttons/DynamicButtonManager.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/buttons/DynamicButtonManager.kt @@ -112,23 +112,26 @@ class DynamicButtonManager(private val project: Project) { */ private fun refreshActionToolbars() { try { - // Get the action manager - val actionManager = ActionManager.getInstance() - - // Refresh the dynamic actions group - val dynamicGroup = actionManager.getAction("RunVSAgent.DynamicExtensionActions") - dynamicGroup?.let { group -> - // Create a dummy event for updating - val dummyEvent = AnActionEvent.createFromAnAction( - group, - null, - "DynamicButtonManager", - DataManager.getInstance().dataContext - ) - group.update(dummyEvent) + // 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) + } } - - logger.debug("Action toolbars refreshed for extension: $currentExtensionId") } catch (e: Exception) { logger.warn("Failed to refresh action toolbars", e) } diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt index a4836754..86c9b73c 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt @@ -122,17 +122,13 @@ class DynamicContextMenuManager(private val project: Project) { // Get the action manager val actionManager = com.intellij.openapi.actionSystem.ActionManager.getInstance() - // Refresh the dynamic context menu actions group + // 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 -> - // Create a dummy event for updating - val dummyEvent = com.intellij.openapi.actionSystem.AnActionEvent.createFromAnAction( - group, - null, - "DynamicContextMenuManager", - com.intellij.ide.DataManager.getInstance().dataContext - ) - group.update(dummyEvent) + // Use the proper IntelliJ Platform mechanism to refresh the action + // Instead of calling update() directly, we invalidate the action + actionManager.invalidateAction(group.id) } logger.debug("Context menus refreshed for extension: $currentExtensionId") From 3f17c3644f3ff1f71477d3c9d50d6949f4b911e3 Mon Sep 17 00:00:00 2001 From: MicroLee Date: Thu, 21 Aug 2025 17:47:27 +0800 Subject: [PATCH 3/4] refactor: Refactor the context menu manager to optimize the usage of the extension manager and UI refresh logic. --- .../contextmenu/DynamicContextMenuManager.kt | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt index 86c9b73c..61c871c1 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/contextmenu/DynamicContextMenuManager.kt @@ -21,7 +21,8 @@ import com.sina.weibo.agent.extensions.plugin.roo.RooCodeContextMenuProvider class DynamicContextMenuManager(private val project: Project) { private val logger = Logger.getInstance(DynamicContextMenuManager::class.java) - + private val extensionManager = ExtensionManager.Companion.getInstance(project) + // Current extension ID @Volatile private var currentExtensionId: String? = null @@ -44,7 +45,6 @@ class DynamicContextMenuManager(private val project: Project) { // 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") @@ -68,14 +68,14 @@ class DynamicContextMenuManager(private val project: Project) { * Get the current extension ID */ fun getCurrentExtensionId(): String? { - return currentExtensionId + return extensionManager.getCurrentProvider()?.getExtensionId() } /** * Get context menu configuration for the current extension */ fun getContextMenuConfiguration(): ContextMenuConfiguration { - val contextMenuProvider = getContextMenuProvider(currentExtensionId) + val contextMenuProvider = getContextMenuProvider(getCurrentExtensionId()) return contextMenuProvider?.getContextMenuConfiguration() ?: DefaultContextMenuConfiguration() } @@ -83,7 +83,7 @@ class DynamicContextMenuManager(private val project: Project) { * Get context menu actions for the current extension */ fun getContextMenuActions(): List { - val contextMenuProvider = getContextMenuProvider(currentExtensionId) + val contextMenuProvider = getContextMenuProvider(getCurrentExtensionId()) return contextMenuProvider?.getContextMenuActions(project) ?: emptyList() } @@ -119,19 +119,26 @@ class DynamicContextMenuManager(private val project: Project) { */ 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) + // 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) + } } - - logger.debug("Context menus refreshed for extension: $currentExtensionId") } catch (e: Exception) { logger.warn("Failed to refresh context menus", e) } From 30224882e415c79ea89393c85d5986283c507e66 Mon Sep 17 00:00:00 2001 From: MicroLee Date: Thu, 21 Aug 2025 18:41:41 +0800 Subject: [PATCH 4/4] refactor: Use VsixManager to obtain the base directory for unified path management. --- .../weibo/agent/actions/ActionConstants.kt | 318 -------------- .../agent/actions/RegisterCodeActions.kt | 299 ------------- .../actions/RightClickChatActionGroup.kt | 71 --- .../weibo/agent/core/ExtensionHostManager.kt | 4 +- .../agent/extensions/core/VsixManager.kt | 7 +- .../plugin/cline/ClineContextMenuProvider.kt | 408 ------------------ .../plugin/cline/ClineExtensionProvider.kt | 4 +- .../plugin/roo/RooCodeActionConstants.kt | 43 -- .../plugin/roo/RooCodeContextMenuProvider.kt | 7 +- .../plugin/roo/RooExtensionProvider.kt | 4 +- .../actions/ExtensionResourceStatusAction.kt | 1 - 11 files changed, 13 insertions(+), 1153 deletions(-) delete mode 100644 jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/ActionConstants.kt delete mode 100644 jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt delete mode 100644 jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RightClickChatActionGroup.kt delete mode 100644 jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/actions/ExtensionResourceStatusAction.kt diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/ActionConstants.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/ActionConstants.kt deleted file mode 100644 index 6715d5bf..00000000 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/ActionConstants.kt +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright 2009-2025 Weibo, Inc. -// SPDX-FileCopyrightText: 2025 Weibo, Inc. -// -// SPDX-License-Identifier: Apache-2.0 - -package com.sina.weibo.agent.actions - -/** - * Constants for action names displayed in the UI. - * These represent the text shown to users in menus and context options. - * - * @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" -} - -/** Type alias for prompt type identifiers */ -@Deprecated("Use extension-specific type aliases instead") -typealias SupportPromptType = String -/** Type alias for prompt parameters map */ -@Deprecated("Use extension-specific type aliases instead") -typealias PromptParams = Map - -/** - * Data class representing a prompt configuration with a template string. - * Templates contain placeholders that will be replaced with actual values. - * - * @deprecated This class is deprecated in favor of extension-specific prompt configurations. - * Use RooCodeSupportPromptConfig for Roo Code extension or Cline-specific prompt configurations for Cline extension. - */ -@Deprecated("Use extension-specific prompt configurations instead") -data class SupportPromptConfig(val template: String) - -/** - * Collection of predefined prompt configurations for different use cases. - * Each configuration contains a template with placeholders for dynamic content. - * - * @deprecated This object is deprecated in favor of extension-specific prompt configurations. - * Use RooCodeSupportPromptConfigs for Roo Code extension or Cline-specific prompt configurations for Cline extension. - */ -@Deprecated("Use extension-specific prompt configurations instead") -object SupportPromptConfigs { - /** - * Template for enhancing user prompts. - * Instructs the AI to generate an improved version of the user's input. - */ - val ENHANCE = SupportPromptConfig( - """Generate an enhanced version of this prompt (reply with only the enhanced prompt - no conversation, explanations, lead-in, bullet points, placeholders, or surrounding quotes): - -${'$'}{userInput}""" - ) - - /** - * Template for explaining code. - * Provides structure for code explanation requests with file path and line information. - */ - val EXPLAIN = SupportPromptConfig( - """Explain the following code from file path ${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} -${'$'}{userInput} - -``` -${'$'}{selectedText} -``` - -Please provide a clear and concise explanation of what this code does, including: -1. The purpose and functionality -2. Key components and their interactions -3. Important patterns or techniques used""" - ) - - /** - * Template for fixing code issues. - * Includes diagnostic information and structured format for issue resolution. - */ - val FIX = SupportPromptConfig( - """Fix any issues in the following code from file path ${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} -${'$'}{diagnosticText} -${'$'}{userInput} - -``` -${'$'}{selectedText} -``` - -Please: -1. Address all detected problems listed above (if any) -2. Identify any other potential bugs or issues -3. Provide corrected code -4. Explain what was fixed and why""" - ) - - /** - * Template for improving code quality. - * Focuses on readability, performance, best practices, and error handling. - */ - val IMPROVE = SupportPromptConfig( - """Improve the following code from file path ${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} -${'$'}{userInput} - -``` -${'$'}{selectedText} -``` - -Please suggest improvements for: -1. Code readability and maintainability -2. Performance optimization -3. Best practices and patterns -4. Error handling and edge cases - -Provide the improved code along with explanations for each enhancement.""" - ) - - /** - * Template for adding code to context. - * Simple format that includes file path, line range, and selected code. - */ - val ADD_TO_CONTEXT = SupportPromptConfig( - """${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} -``` -${'$'}{selectedText} -```""" - ) - - /** - * Template for adding terminal output to context. - * Includes user input and terminal content. - */ - val TERMINAL_ADD_TO_CONTEXT = SupportPromptConfig( - """${'$'}{userInput} -Terminal output: -``` -${'$'}{terminalContent} -```""" - ) - - /** - * Template for fixing terminal commands. - * Structured format for identifying and resolving command issues. - */ - val TERMINAL_FIX = SupportPromptConfig( - """${'$'}{userInput} -Fix this terminal command: -``` -${'$'}{terminalContent} -``` - -Please: -1. Identify any issues in the command -2. Provide the corrected command -3. Explain what was fixed and why""" - ) - - /** - * Template for explaining terminal commands. - * Provides structure for command explanation with focus on functionality and behavior. - */ - val TERMINAL_EXPLAIN = SupportPromptConfig( - """${'$'}{userInput} -Explain this terminal command: -``` -${'$'}{terminalContent} -``` - -Please provide: -1. What the command does -2. Explanation of each part/flag -3. Expected output and behavior""" - ) - - /** - * Template for creating a new task. - * Simple format that passes through user input directly. - */ - val NEW_TASK = SupportPromptConfig( - """${'$'}{userInput}""" - ) - - /** - * Map of all available prompt configurations indexed by their type identifiers. - * Used for lookup when creating prompts. - */ - 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 - ) -} - -/** - * Utility object for working with support prompts. - * Provides methods for creating and customizing prompts based on templates. - * - * @deprecated This object is deprecated in favor of extension-specific prompt utilities. - * Use RooCodeSupportPrompt for Roo Code extension or Cline-specific prompt utilities for Cline extension. - */ -@Deprecated("Use extension-specific prompt utilities instead") -object SupportPrompt { - /** - * Generates formatted diagnostic text from a list of diagnostic items. - * - * @param diagnostics List of diagnostic items containing source, message, and code - * @return Formatted string of diagnostic messages or empty string if no diagnostics - */ - private fun generateDiagnosticText(diagnostics: List>?): String { - if (diagnostics.isNullOrEmpty()) return "" - 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)" } ?: ""}" - } - } - - /** - * Creates a prompt by replacing placeholders in a template with actual values. - * - * @param template The prompt template with placeholders - * @param params Map of parameter values to replace placeholders - * @return The processed prompt with placeholders replaced by actual values - */ - private fun createPrompt(template: String, params: PromptParams): String { - val pattern = Regex("""\$\{(.*?)}""") - return pattern.replace(template) { matchResult -> - val key = matchResult.groupValues[1] - if (key == "diagnosticText") { - generateDiagnosticText(params["diagnostics"] as? List>) - } 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 - "" - } - } - } - - /** - * Gets the template for a specific prompt type, with optional custom overrides. - * - * @param customSupportPrompts Optional map of custom prompt templates - * @param type The type of prompt to retrieve - * @return The template string for the specified prompt type - */ - fun get(customSupportPrompts: Map?, type: SupportPromptType): String { - return customSupportPrompts?.get(type) ?: SupportPromptConfigs.configs[type]?.template ?: "" - } - - /** - * Creates a complete prompt by getting the template and replacing placeholders. - * - * @param type The type of prompt to create - * @param params Parameters to substitute into the template - * @param customSupportPrompts Optional custom prompt templates - * @return The final prompt with all placeholders replaced - */ - fun create(type: SupportPromptType, params: PromptParams, customSupportPrompts: Map? = null): String { - val template = get(customSupportPrompts, type) - return createPrompt(template, params) - } -} \ No newline at end of file diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt deleted file mode 100644 index 5a409ff4..00000000 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RegisterCodeActions.kt +++ /dev/null @@ -1,299 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Weibo, Inc. -// -// SPDX-License-Identifier: Apache-2.0 - -package com.sina.weibo.agent.actions - -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.CommonDataKeys -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.Messages -import com.sina.weibo.agent.webview.WebViewManager - -/** - * Code action provider, similar to VSCode's CodeActionProvider. - * Provides functionality for creating and managing code-related actions. - */ -class CodeActionProvider { - - /** - * Creates a single code action with the specified title and command. - * - * @param title The display title for the action - * @param command The command identifier to execute when action is triggered - * @return An AnAction instance that can be registered with the IDE - */ - private fun createAction( - title: String, - command: String - ): AnAction { - return object : AnAction(title) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - val editor = e.getData(CommonDataKeys.EDITOR) ?: return - val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return - - // Get current parameters when the action is clicked - val effectiveRange = getEffectiveRange(editor) - if (effectiveRange == null) return - - val args = mutableMapOf() - args["filePath"] = file.path - args["selectedText"] = effectiveRange.text - args["startLine"] = effectiveRange.startLine + 1 - args["endLine"] = effectiveRange.endLine + 1 - - handleCodeAction(command, title, args, project) - } - } - } - - /** - * Creates a pair of actions (new task version and current task version). - * - * @param baseTitle The base title for the actions - * @param baseCommand The base command identifier - * @return List of AnAction instances - */ - private fun createActionPair( - baseTitle: String, - baseCommand: String - ): List { - return listOf( - createAction("$baseTitle in Current Task", "${baseCommand}InCurrentTask") - ) - } - - /** - * Gets the effective range and text from the current editor selection. - * - * @param editor The current editor instance - * @return EffectiveRange object containing selected text and line numbers, or null if no selection - */ - private fun getEffectiveRange(editor: Editor): EffectiveRange? { - val document = editor.document - val selectionModel = editor.selectionModel - - 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 - } - } - - /** - * Provides a list of code actions for the given action event. - * - * @param e The action event containing context information - * @return List of available code actions - */ - fun provideCodeActions(e: AnActionEvent): List { - val actions = mutableListOf() - - // Add to context action - actions.add( - createAction( - ActionNames.ADD_TO_CONTEXT, - CommandIds.ADD_TO_CONTEXT - ) - ) - - // Explain code action pair - actions.addAll( - createActionPair( - ActionNames.EXPLAIN, - CommandIds.EXPLAIN - ) - ) - - // Fix code action pair (logic fix) - actions.addAll( - createActionPair( - ActionNames.FIX_LOGIC, - CommandIds.FIX - ) - ) - - // Improve code action pair - actions.addAll( - createActionPair( - ActionNames.IMPROVE, - CommandIds.IMPROVE - ) - ) - - return actions - } -} - -/** - * Data class representing an effective range of selected text. - * Contains the selected text and its start/end line numbers. - * - * @property text The selected text content - * @property startLine The starting line number (0-based) - * @property endLine The ending line number (0-based) - */ -data class EffectiveRange( - val text: String, - val startLine: Int, - val endLine: Int -) - -/** - * Registers a code action with the specified parameters. - * - * @param command The command identifier - * @param promptType The type of prompt to use - * @param inputPrompt Optional prompt text for user input dialog - * @param inputPlaceholder Optional placeholder text for input field - * @return An AnAction instance that can be registered with the IDE - */ -fun registerCodeAction( - command: String, - promptType: String, - inputPrompt: String? = null, - inputPlaceholder: String? = null -) : AnAction { - return object : AnAction(command) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - val editor = e.getData(CommonDataKeys.EDITOR) ?: return - - var userInput: String? = null - if (inputPrompt != null) { - userInput = Messages.showInputDialog( - project, - inputPrompt, - "RunVSAgent", - null, - inputPlaceholder, - null - ) - if (userInput == null) return // Cancelled - } - - // Get selected content, line numbers, etc. - val document = editor.document - val selectionModel = editor.selectionModel - val selectedText = selectionModel.selectedText ?: "" - val startLine = if (selectionModel.hasSelection()) document.getLineNumber(selectionModel.selectionStart) else null - val endLine = if (selectionModel.hasSelection()) document.getLineNumber(selectionModel.selectionEnd) else null - val file = e.getData(CommonDataKeys.VIRTUAL_FILE) - val filePath = file?.path ?: "" - - val params = mutableMapOf( - "filePath" to filePath, - "selectedText" to selectedText - ) - if (startLine != null) params["startLine"] = (startLine + 1).toString() - if (endLine != null) params["endLine"] = (endLine + 1).toString() - if (!userInput.isNullOrEmpty()) params["userInput"] = userInput - - handleCodeAction(command, promptType, params, e.project) - } - } -} -/** - * Registers a pair of code actions with the specified parameters. - * - * @param baseCommand The base command identifier - * @param inputPrompt Optional prompt text for user input dialog - * @param inputPlaceholder Optional placeholder text for input field - * @return An AnAction instance for the new task version - */ -fun registerCodeActionPair( - baseCommand: String, - inputPrompt: String? = null, - inputPlaceholder: String? = null -) : AnAction { - // New task version - return registerCodeAction(baseCommand, baseCommand, inputPrompt, inputPlaceholder) -} - -/** - * 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 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 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 - 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 - } 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) -} \ No newline at end of file diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RightClickChatActionGroup.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RightClickChatActionGroup.kt deleted file mode 100644 index 80d58469..00000000 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/actions/RightClickChatActionGroup.kt +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Weibo, Inc. -// -// SPDX-License-Identifier: Apache-2.0 - -package com.sina.weibo.agent.actions - -import com.intellij.openapi.actionSystem.* -import com.intellij.openapi.project.DumbAware - -/** - * 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. - * - * Implements DumbAware to ensure the action works during indexing, and ActionUpdateThreadAware - * to specify which thread should handle action updates. - */ -@Deprecated("Use DynamicExtensionContextMenuGroup instead for extension-based context menus") -class RightClickChatActionGroup : DefaultActionGroup(), DumbAware, ActionUpdateThreadAware { - - /** - * Provider that supplies the actual code actions to be displayed in the menu. - */ - private val codeActionProvider = CodeActionProvider() - - /** - * Updates the action group based on the current context. - * This method is called each time the menu needs to be displayed. - * - * @param e The action event containing context information - */ - override fun update(e: AnActionEvent) { - removeAll() - - // Check if there is an editor and selected text - val editor = e.getData(CommonDataKeys.EDITOR) - val hasSelection = editor?.selectionModel?.hasSelection() == true - - if (hasSelection) { - loadDynamicActions(e) - } - - // Set the visibility of the action group - e.presentation.isVisible = hasSelection - } - - /** - * Loads dynamic actions into this action group based on the current context. - * - * @param e The action event containing context information - */ - private fun loadDynamicActions(e: AnActionEvent) { - // Use actions provided by CodeActionProvider - val actions = codeActionProvider.provideCodeActions(e) - actions.forEach { action -> - add(action) - } - } - - /** - * Specifies which thread should be used for updating this action. - * EDT (Event Dispatch Thread) is used for UI-related operations. - * - * @return The thread to use for action updates - */ - override fun getActionUpdateThread(): ActionUpdateThread { - return ActionUpdateThread.EDT - } -} diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/core/ExtensionHostManager.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/core/ExtensionHostManager.kt index 9c26f9db..3c0146a2 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/core/ExtensionHostManager.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/core/ExtensionHostManager.kt @@ -30,6 +30,8 @@ import com.intellij.openapi.extensions.PluginId import com.sina.weibo.agent.extensions.core.ExtensionManager as GlobalExtensionManager import com.sina.weibo.agent.extensions.config.ExtensionProvider import com.sina.weibo.agent.extensions.config.ExtensionMetadata +import com.sina.weibo.agent.extensions.core.VsixManager.Companion.getBaseDirectory +import com.sina.weibo.agent.util.PluginConstants.ConfigFiles.getUserConfigDir import java.io.File /** @@ -383,7 +385,7 @@ class ExtensionHostManager : Disposable { val homeDir = System.getProperty("user.home") if (projectPath != null) { val possiblePaths = listOf( - "$homeDir/.run-vs-agent/plugins/${extensionConfig.getCodeDir()}" + "${getBaseDirectory()}/${extensionConfig.getCodeDir()}" ) val foundPath = possiblePaths.find { File(it).exists() } diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/VsixManager.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/VsixManager.kt index 66d1561c..aef2d9cc 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/VsixManager.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/core/VsixManager.kt @@ -7,6 +7,7 @@ package com.sina.weibo.agent.extensions.core import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import com.sina.weibo.agent.theme.ThemeManager.Companion.getThemeResourceDir +import com.sina.weibo.agent.util.PluginConstants.ConfigFiles.getUserConfigDir import java.io.File import java.nio.file.Files import java.nio.file.Path @@ -23,8 +24,7 @@ class VsixManager { companion object { private val LOG = Logger.getInstance(VsixManager::class.java) - private const val BASE_DIR = ".run-vs-agent/plugins" - + /** * Get VSIX manager instance */ @@ -34,8 +34,7 @@ class VsixManager { * Get base directory for VSIX installations */ fun getBaseDirectory(): String { - val homeDir = System.getProperty("user.home") - return "$homeDir/$BASE_DIR" + return "${getUserConfigDir()}/plugins" } } diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt index ab9ca86a..62186d92 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineContextMenuProvider.kt @@ -4,18 +4,11 @@ package com.sina.weibo.agent.extensions.plugin.cline -import com.google.gson.Gson import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.CommonDataKeys -import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project -import com.sina.weibo.agent.actions.SupportPrompt -import com.sina.weibo.agent.actions.executeCommand import com.sina.weibo.agent.extensions.ui.contextmenu.ExtensionContextMenuProvider import com.sina.weibo.agent.extensions.ui.contextmenu.ContextMenuConfiguration import com.sina.weibo.agent.extensions.ui.contextmenu.ContextMenuActionType -import com.sina.weibo.agent.webview.WebViewManager /** * Cline extension context menu provider. @@ -37,11 +30,6 @@ class ClineContextMenuProvider : ExtensionContextMenuProvider { override fun getContextMenuActions(project: Project): List { return listOf( -// ClineExplainCodeAction(), -// ClineFixCodeAction(), -// ClineImproveCodeAction(), -// ClineAddToContextAction(), -// ClineNewTaskAction() ) } @@ -74,400 +62,4 @@ class ClineContextMenuProvider : ExtensionContextMenuProvider { ) } } - - /** - * Cline action to explain selected code. - * Uses Cline-specific command and prompt format. - */ - class ClineExplainCodeAction : AnAction("Explain Code (Cline)") { - private val logger: Logger = Logger.getInstance(ClineExplainCodeAction::class.java) - - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - val editor = e.getData(CommonDataKeys.EDITOR) ?: return - val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return - - val effectiveRange = ClineContextMenuProvider.getEffectiveRange(editor) - if (effectiveRange == null) return - - val args = mutableMapOf() - args["filePath"] = file.path - args["selectedText"] = effectiveRange.text - args["startLine"] = effectiveRange.startLine + 1 - args["endLine"] = effectiveRange.endLine + 1 - - executeCommand("cline.explainCode", project, args) - } - } - - /** - * Cline action to fix code issues. - * Uses Cline-specific command and prompt format. - */ - class ClineFixCodeAction : AnAction("Fix Code (Cline)") { - private val logger: Logger = Logger.getInstance(ClineFixCodeAction::class.java) - - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - val editor = e.getData(CommonDataKeys.EDITOR) ?: return - val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return - - val effectiveRange = ClineContextMenuProvider.getEffectiveRange(editor) - if (effectiveRange == null) return - - val args = mutableMapOf() - args["filePath"] = file.path - args["selectedText"] = effectiveRange.text - args["startLine"] = effectiveRange.startLine + 1 - args["endLine"] = effectiveRange.endLine + 1 - executeCommand("cline.fixCode", project, args) - } - } - - /** - * Cline action to improve code quality. - * Uses Cline-specific command and prompt format. - */ - class ClineImproveCodeAction : AnAction("Improve Code (Cline)") { - private val logger: Logger = Logger.getInstance(ClineImproveCodeAction::class.java) - - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - val editor = e.getData(CommonDataKeys.EDITOR) ?: return - val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return - - val effectiveRange = ClineContextMenuProvider.getEffectiveRange(editor) - if (effectiveRange == null) return - - val args = mutableMapOf() - args["filePath"] = file.path - args["selectedText"] = effectiveRange.text - args["startLine"] = effectiveRange.startLine + 1 - args["endLine"] = effectiveRange.endLine + 1 - executeCommand("cline.improveCode", project, args) - - } - } - - /** - * Cline action to add selected code to context. - * Uses Cline-specific command and prompt format. - */ - class ClineAddToContextAction : AnAction("Add to Context (Cline)") { - private val logger: Logger = Logger.getInstance(ClineAddToContextAction::class.java) - - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - val editor = e.getData(CommonDataKeys.EDITOR) ?: return - val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return - - val effectiveRange = ClineContextMenuProvider.getEffectiveRange(editor) - if (effectiveRange == null) return - - val range = ClineContextMenuProvider.Range( - startLine = effectiveRange.startLine, - startCharacter = effectiveRange.startCharacter, - endLine = effectiveRange.endLine, - endCharacter = effectiveRange.endCharacter - ) - val rangeMap = mapOf( - "startLine" to effectiveRange.startLine, - "startCharacter" to effectiveRange.startCharacter, - "endLine" to effectiveRange.endLine, - "endCharacter" to effectiveRange.endCharacter - ) - - executeCommand("cline.addToChat", project, rangeMap) - } - } - - /** - * Cline action to create a new task. - * Uses Cline-specific command and prompt format. - */ - class ClineNewTaskAction : AnAction("New Task (Cline)") { - private val logger: Logger = Logger.getInstance(ClineNewTaskAction::class.java) - - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - val editor = e.getData(CommonDataKeys.EDITOR) ?: return - val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return - - val effectiveRange = ClineContextMenuProvider.getEffectiveRange(editor) - if (effectiveRange == null) return - - val args = mutableMapOf() - args["filePath"] = file.path - args["selectedText"] = effectiveRange.text - args["startLine"] = effectiveRange.startLine + 1 - args["endLine"] = effectiveRange.endLine + 1 - - ClineContextMenuProvider.handleClineCodeAction("cline.newTask", "NEW_TASK", args, project) - } - } - - /** - * Data class representing an effective range of selected text. - * Contains the selected text and its start/end line numbers. - * - * @property text The selected text content - * @property startLine The starting line number (0-based) - * @property endLine The ending line number (0-based) - */ - data class EffectiveRange( - val text: String, - val startLine: Int, - val endLine: Int, - val startCharacter: Int, - val endCharacter: Int, - ) - - companion object { - /** - * Gets the effective range and text from the current editor selection. - * - * @param editor The current editor instance - * @return EffectiveRange object containing selected text and line numbers, or null if no selection - */ - fun getEffectiveRange(editor: com.intellij.openapi.editor.Editor): EffectiveRange? { - val document = editor.document - val selectionModel = editor.selectionModel - - return if (selectionModel.hasSelection()) { - val selectedText = selectionModel.selectedText ?: "" - val startLine = document.getLineNumber(selectionModel.selectionStart) - val endLine = document.getLineNumber(selectionModel.selectionEnd) - val startCharacter = selectionModel.selectionStart - document.getLineStartOffset(startLine) - val endCharacter = selectionModel.selectionEnd - document.getLineStartOffset(endLine) - EffectiveRange(selectedText, startLine, endLine, startCharacter, endCharacter) - } else { - null - } - } - - /** - * Core logic for handling Cline code actions. - * Processes different types of commands and sends appropriate messages to the webview. - * Uses Cline-specific command format and prompt templates. - * - * @param command The Cline command identifier - * @param promptType The type of prompt to use - * @param params Parameters for the action - * @param project The current project - */ - fun handleClineCodeAction(command: String, promptType: String, params: Map, 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") -> { - mapOf( - "type" to "invoke", - "invoke" to "setChatBoxMessage", - "text" to createClinePrompt("ADD_TO_CONTEXT", params) - ) - } - // Command executed in new task - else -> { - val promptParams = params - 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) - ) - } - } - - // Convert to JSON and send - val messageJson = com.google.gson.Gson().toJson(messageContent) - latestWebView.postMessageToWebView(messageJson) - } - - /** - * Creates a Cline-specific prompt by replacing placeholders in a template with actual values. - * - * @param promptType The type of prompt to create - * @param params Parameters to substitute into the template - * @return The final prompt with all placeholders replaced - */ - fun createClinePrompt(promptType: String, params: Map): String { - val template = getClinePromptTemplate(promptType) - return replacePlaceholders(template, params) - } - - /** - * Gets the Cline-specific template for a specific prompt type. - * These templates are optimized for Cline AI's capabilities and style. - * - * @param type The type of prompt to retrieve - * @return The template string for the specified prompt type - */ - fun getClinePromptTemplate(type: String): String { - return when (type) { - "EXPLAIN" -> """Please explain this code from ${'$'}{filePath} (lines ${'$'}{startLine}-${'$'}{endLine}): - -```${'$'}{selectedText}``` - -Focus on: -- What the code does and its purpose -- Key components and their relationships -- Important patterns or techniques used -- Any potential improvements or considerations""" - - "FIX" -> """Please fix any issues in this code from ${'$'}{filePath} (lines ${'$'}{startLine}-${'$'}{endLine}): - -```${'$'}{selectedText}``` - -Please: -- Identify and fix any bugs or issues -- Improve error handling and edge cases -- Provide the corrected code -- Explain what was fixed and why""" - - "IMPROVE" -> """Please improve this code from ${'$'}{filePath} (lines ${'$'}{startLine}-${'$'}{endLine}): - -```${'$'}{selectedText}``` - -Focus on improvements for: -- Code readability and maintainability -- Performance optimization -- Best practices and modern patterns -- Error handling and robustness - -Provide the improved code with explanations for each enhancement.""" - - "ADD_TO_CONTEXT" -> """Code from ${'$'}{filePath} (lines ${'$'}{startLine}-${'$'}{endLine}): -```${'$'}{selectedText}```""" - - "NEW_TASK" -> """${'$'}{selectedText}""" - - else -> "" - } - } - - /** - * Replaces placeholders in a template with actual values. - * - * @param template The prompt template with placeholders - * @param params Map of parameter values to replace placeholders - * @return The processed prompt with placeholders replaced by actual values - */ - fun replacePlaceholders(template: String, params: Map): String { - val pattern = Regex("""\$\{(.*?)}""") - return pattern.replace(template) { matchResult -> - val key = matchResult.groupValues[1] - params[key]?.toString() ?: "" - } - } - } - - /** - * 与 VS Code 完全一致的 Position。 - */ - data class Position(val line: Int, val character: Int) : Comparable { - - init { - require(line >= 0) { "line must be non-negative" } - require(character >= 0) { "character must be non-negative" } - } - - override fun compareTo(other: Position): Int { - return when { - line != other.line -> line - other.line - else -> character - other.character - } - } - - fun isBefore(other: Position): Boolean = compareTo(other) < 0 - fun isBeforeOrEqual(other: Position): Boolean = compareTo(other) <= 0 - fun isAfter(other: Position): Boolean = compareTo(other) > 0 - fun isAfterOrEqual(other: Position): Boolean = compareTo(other) >= 0 - - fun translate(lineDelta: Int = 0, characterDelta: Int = 0): Position { - return Position(line + lineDelta, character + characterDelta) - } - - fun translate(change: Position): Position { - return Position(line + change.line, character + change.character) - } - - fun with(line: Int = this.line, character: Int = this.character): Position { - return Position(line, character) - } - - override fun toString(): String = "($line,$character)" - } - - /** - * 与 VS Code 完全一致的 Range。 - */ - class Range(startLine: Int, startCharacter: Int, endLine: Int, endCharacter: Int) { - - val start: Position - val end: Position - - constructor(start: Position, end: Position) : - this(start.line, start.character, end.line, end.character) - - init { - val s = Position(startLine, startCharacter) - val e = Position(endLine, endCharacter) - if (s <= e) { - start = s - end = e - } else { - start = e - end = s - } - } - - val isEmpty: Boolean get() = start == end - val isSingleLine: Boolean get() = start.line == end.line - - fun contains(position: Position): Boolean { - return position >= start && position <= end - } - - fun contains(range: Range): Boolean { - return range.start >= start && range.end <= end - } - - fun intersection(range: Range): Range? { - val newStart = if (start >= range.start) start else range.start - val newEnd = if (end <= range.end) end else range.end - return if (newStart <= newEnd) Range(newStart, newEnd) else null - } - - fun union(range: Range): Range { - val newStart = if (start <= range.start) start else range.start - val newEnd = if (end >= range.end) end else range.end - return Range(newStart, newEnd) - } - - fun with(start: Position = this.start, end: Position = this.end): Range { - return Range(start, end) - } - - override fun equals(other: Any?): Boolean { - return other is Range && other.start == start && other.end == end - } - - override fun hashCode(): Int { - return 31 * start.hashCode() + end.hashCode() - } - - override fun toString(): String { - return "[${start.toString()}~${end.toString()}]" - } - } } diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineExtensionProvider.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineExtensionProvider.kt index 17ba79c8..c241cd86 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineExtensionProvider.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/cline/ClineExtensionProvider.kt @@ -11,6 +11,7 @@ import com.sina.weibo.agent.extensions.core.VsixManager import com.sina.weibo.agent.extensions.config.ExtensionProvider import com.sina.weibo.agent.extensions.common.ExtensionType import com.sina.weibo.agent.extensions.config.ExtensionMetadata +import com.sina.weibo.agent.extensions.core.VsixManager.Companion.getBaseDirectory import com.sina.weibo.agent.util.PluginConstants import com.sina.weibo.agent.util.PluginResourceUtil import java.io.File @@ -47,9 +48,8 @@ class ClineExtensionProvider : ExtensionProvider { val config = extensionConfig.getConfig(ExtensionType.CLINE) // First check project paths - val homeDir = System.getProperty("user.home") val possiblePaths = listOf( - "$homeDir/.run-vs-agent/plugins/${config.codeDir}" + "${getBaseDirectory()}/${config.codeDir}" ) if (possiblePaths.any { File(it).exists() }) { diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt index bda12fbb..d8e0b90c 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeActionConstants.kt @@ -4,47 +4,6 @@ package com.sina.weibo.agent.extensions.plugin.roo -/** - * Constants for Roo Code extension action names displayed in the UI. - * These represent the text shown to users in menus and context options. - * - * This file contains all the constants that were previously in ActionConstants.kt - * for the roo-cline functionality, now organized under the Roo Code extension. - */ -object RooCodeActionNames { - /** Action to explain selected code */ - const val EXPLAIN = "Explain Code" - /** Action to fix issues in selected code */ - const val FIX = "Fix Code" - /** Action to fix logical issues in selected code */ - const val FIX_LOGIC = "Fix Logic" - /** Action to improve selected code */ - const val IMPROVE = "Improve Code" - /** Action to add selected code to context */ - const val ADD_TO_CONTEXT = "Add to Context" - /** Action to create a new task */ - const val NEW_TASK = "New Task" -} - -/** - * Command identifiers used for internal command registration and execution. - * These IDs are used to register commands with the IDE. - * - * All commands use the roo-cline prefix for backward compatibility. - */ -object RooCodeCommandIds { - /** Command ID for explaining code */ - const val EXPLAIN = "roo-cline.explainCode" - /** Command ID for fixing code */ - const val FIX = "roo-cline.fixCode" - /** Command ID for improving code */ - const val IMPROVE = "roo-cline.improveCode" - /** Command ID for adding to context */ - const val ADD_TO_CONTEXT = "roo-cline.addToContext" - /** Command ID for creating a new task */ - const val NEW_TASK = "roo-cline.newTask" -} - /** Type alias for prompt type identifiers */ typealias RooCodeSupportPromptType = String /** Type alias for prompt parameters map */ @@ -60,7 +19,6 @@ data class RooCodeSupportPromptConfig(val template: String) * Collection of predefined prompt configurations for different use cases. * Each configuration contains a template with placeholders for dynamic content. * - * These are the same templates that were previously in ActionConstants.kt, * now organized under the Roo Code extension. */ object RooCodeSupportPromptConfigs { @@ -219,7 +177,6 @@ Please provide: * Utility object for working with Roo Code support prompts. * Provides methods for creating and customizing prompts based on templates. * - * This is the same functionality that was previously in ActionConstants.kt, * now organized under the Roo Code extension. */ object RooCodeSupportPrompt { diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt index 03896a1b..440e859b 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooCodeContextMenuProvider.kt @@ -9,7 +9,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project -import com.sina.weibo.agent.actions.SupportPrompt import com.sina.weibo.agent.extensions.ui.contextmenu.ExtensionContextMenuProvider import com.sina.weibo.agent.extensions.ui.contextmenu.ContextMenuConfiguration import com.sina.weibo.agent.extensions.ui.contextmenu.ContextMenuActionType @@ -277,7 +276,7 @@ class RooCodeContextMenuProvider : ExtensionContextMenuProvider { mapOf( "type" to "invoke", "invoke" to "setChatBoxMessage", - "text" to SupportPrompt.create("ADD_TO_CONTEXT", promptParams) + "text" to RooCodeSupportPrompt.create("ADD_TO_CONTEXT", promptParams) ) } // Command executed in current task @@ -292,7 +291,7 @@ class RooCodeContextMenuProvider : ExtensionContextMenuProvider { mapOf( "type" to "invoke", "invoke" to "sendMessage", - "text" to SupportPrompt.create(basePromptType, promptParams) + "text" to RooCodeSupportPrompt.create(basePromptType, promptParams) ) } // Command executed in new task @@ -326,7 +325,7 @@ class RooCodeContextMenuProvider : ExtensionContextMenuProvider { mapOf( "type" to "invoke", "invoke" to "initClineWithTask", - "text" to SupportPrompt.create(basePromptType, promptParams) + "text" to RooCodeSupportPrompt.create(basePromptType, promptParams) ) } } diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooExtensionProvider.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooExtensionProvider.kt index 20c3b146..782bd50b 100644 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooExtensionProvider.kt +++ b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/plugin/roo/RooExtensionProvider.kt @@ -11,6 +11,7 @@ import com.sina.weibo.agent.extensions.core.ExtensionManagerFactory import com.sina.weibo.agent.extensions.config.ExtensionProvider import com.sina.weibo.agent.extensions.config.ExtensionMetadata import com.sina.weibo.agent.util.PluginConstants +import com.sina.weibo.agent.util.PluginConstants.ConfigFiles.getUserConfigDir import com.sina.weibo.agent.util.PluginResourceUtil import java.io.File @@ -41,9 +42,8 @@ class RooExtensionProvider : ExtensionProvider { val config = extensionConfig.getConfig(ExtensionType.ROO_CODE) // First check project paths - val homeDir = System.getProperty("user.home") val possiblePaths = listOf( - "$homeDir/.run-vs-agent/plugins/${config.codeDir}" + "${getUserConfigDir()}/plugins/${config.codeDir}" ) if (possiblePaths.any { File(it).exists() }) { diff --git a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/actions/ExtensionResourceStatusAction.kt b/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/actions/ExtensionResourceStatusAction.kt deleted file mode 100644 index 0519ecba..00000000 --- a/jetbrains_plugin/src/main/kotlin/com/sina/weibo/agent/extensions/ui/actions/ExtensionResourceStatusAction.kt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file