多语言编码Agent解决方案(5)-IntelliJ插件实现

发布于:2025-09-15 ⋅ 阅读:(12) ⋅ 点赞:(0)

IntelliJ插件实现:支持多语言的编码Agent集成

本部分包含IntelliJ插件的完整实现,包括多语言支持、动作注册、API调用和UI集成。插件使用Java开发,基于IntelliJ Platform Plugin SDK。

1. IntelliJ插件目录结构

intellij-plugin/
├── src/                           # 源代码
│   └── main
│       ├── java
│       │   └── com
│       │       └── codingagent
│       │           ├── AgentActions.java # 动作处理类
│       │           ├── ApiClient.java    # API调用工具
│       │           └── i18n
│       │               └── Messages.java # 国际化消息类
│       └── resources/                    # 资源文件
│           └── locales/                  # 语言文件
│               ├── messages.properties    # 英文(默认)
│               ├── messages_zh_CN.properties # 中文
│               └── messages_ja.properties # 日文
├── META-INF/                      # 元数据
│   └── plugin.xml                 # 插件配置
└── build.gradle                   # 构建脚本(可选,假设使用Gradle)

2. 国际化支持 (Messages.java)

// src/main/java/com/codingagent/i18n/Messages.java
package com.codingagent.i18n;

import com.intellij.BundleBase;
import com.intellij.openapi.util.NotNullLazyValue;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.PropertyKey;

import java.util.ResourceBundle;

public class Messages {
    @NonNls
    private static final String BUNDLE = "locales.messages";

    private static final NotNullLazyValue<ResourceBundle> LAZY_BUNDLE = NotNullLazyValue.createValue(() -> {
        String lang = System.getProperty("user.language");
        if ("zh".equals(lang)) {
            return ResourceBundle.getBundle(BUNDLE, new java.util.Locale("zh", "CN"));
        } else if ("ja".equals(lang)) {
            return ResourceBundle.getBundle(BUNDLE, java.util.Locale.JAPANESE);
        } else {
            return ResourceBundle.getBundle(BUNDLE, java.util.Locale.ENGLISH);
        }
    });

    private Messages() {}

    public static String message(@PropertyKey(resourceBundle = BUNDLE) String key, Object... params) {
        return BundleBase.message(LAZY_BUNDLE.getValue(), key, params);
    }
}

3. 语言文件 (locales)

messages.properties (英文,默认)

extension.activated=Coding Agent plugin activated
extension.ready=Agent Ready
extension.completing=Completing...
extension.generating=Generating...
extension.explaining=Explaining...
extension.refactoring=Refactoring...
extension.debugging=Debugging...
extension.generatingTests=Generating tests...
extension.error=Error
extension.disconnected=Disconnected
extension.connected=Coding Agent backend connected
extension.cannotConnect=Cannot connect to Coding Agent backend, please ensure the service is started

commands.complete=Agent: Complete Code
commands.generate=Agent: Generate Code
commands.explain=Agent: Explain Code
commands.refactor=Agent: Refactor Code
commands.debug=Agent: Debug Code
commands.test=Agent: Generate Tests

prompts.generateDescription=Describe the code you want to generate
prompts.generatePlaceholder=e.g., Create a REST API endpoint
prompts.selectCode=Please select code first
prompts.completeFailed=Completion failed: {0}
prompts.generateFailed=Generation failed: {0}
prompts.explainFailed=Explanation failed: {0}
prompts.refactorFailed=Refactoring failed: {0}
prompts.debugFailed=Debug failed: {0}
prompts.testFailed=Test generation failed: {0}
prompts.refactorComplete=Refactoring complete: {0}

output.explanation=Code Explanation
output.debugAnalysis=Debug Analysis
output.aiGenerated=AI Generated
output.agentSuggestion=Coding Agent Suggestion

messages_zh_CN.properties (中文)

extension.activated=编码助手插件已激活
extension.ready=助手就绪
extension.completing=正在补全...
extension.generating=正在生成...
extension.explaining=正在解释...
extension.refactoring=正在重构...
extension.debugging=正在调试...
extension.generatingTests=正在生成测试...
extension.error=错误
extension.disconnected=未连接
extension.connected=编码助手后端已连接
extension.cannotConnect=无法连接到编码助手后端,请确保服务已启动

commands.complete=助手: 补全代码
commands.generate=助手: 生成代码
commands.explain=助手: 解释代码
commands.refactor=助手: 重构代码
commands.debug=助手: 调试代码
commands.test=助手: 生成测试

prompts.generateDescription=描述你想生成的代码
prompts.generatePlaceholder=例如:创建一个REST API端点
prompts.selectCode=请先选择代码
prompts.completeFailed=补全失败: {0}
prompts.generateFailed=生成失败: {0}
prompts.explainFailed=解释失败: {0}
prompts.refactorFailed=重构失败: {0}
prompts.debugFailed=调试失败: {0}
prompts.testFailed=测试生成失败: {0}
prompts.refactorComplete=重构完成: {0}

output.explanation=代码解释
output.debugAnalysis=调试分析
output.aiGenerated=AI生成
output.agentSuggestion=编码助手建议

messages_ja.properties (日文)

extension.activated=コーディングエージェントプラグインが有効になりました
extension.ready=エージェント準備完了
extension.completing=補完中...
extension.generating=生成中...
extension.explaining=説明中...
extension.refactoring=リファクタリング中...
extension.debugging=デバッグ中...
extension.generatingTests=テスト生成中...
extension.error=エラー
extension.disconnected=切断
extension.connected=コーディングエージェントバックエンドに接続しました
extension.cannotConnect=コーディングエージェントバックエンドに接続できません。サービスが起動していることを確認してください

commands.complete=エージェント: コード補完
commands.generate=エージェント: コード生成
commands.explain=エージェント: コード説明
commands.refactor=エージェント: コードリファクタリング
commands.debug=エージェント: コードデバッグ
commands.test=エージェント: テスト生成

prompts.generateDescription=生成したいコードを説明してください
prompts.generatePlaceholder=例:REST APIエンドポイントを作成
prompts.selectCode=最初にコードを選択してください
prompts.completeFailed=補完失敗: {0}
prompts.generateFailed=生成失敗: {0}
prompts.explainFailed=説明失敗: {0}
prompts.refactorFailed=リファクタリング失敗: {0}
prompts.debugFailed=デバッグ失敗: {0}
prompts.testFailed=テスト生成失敗: {0}
prompts.refactorComplete=リファクタリング完了: {0}

output.explanation=コード説明
output.debugAnalysis=デバッグ分析
output.aiGenerated=AI生成
output.agentSuggestion=コーディングエージェントの提案

4. API调用工具 (ApiClient.java)

// src/main/java/com/codingagent/ApiClient.java
package com.codingagent;

import com.intellij.openapi.util.text.StringUtil;
import okhttp3.*;
import org.jetbrains.annotations.NotNull;
import org.json.JSONObject;

import java.io.IOException;

public class ApiClient {
    private static final String BASE_URL = "http://localhost:8000/api";
    private final OkHttpClient client = new OkHttpClient();
    private final String acceptLanguage;

    public ApiClient(String acceptLanguage) {
        this.acceptLanguage = acceptLanguage;
    }

    public JSONObject post(String endpoint, JSONObject requestBody) throws IOException {
        RequestBody body = RequestBody.create(
                requestBody.toString(),
                MediaType.get("application/json; charset=utf-8")
        );

        Request request = new Request.Builder()
                .url(BASE_URL + endpoint)
                .post(body)
                .addHeader("Accept-Language", acceptLanguage)
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

            return new JSONObject(StringUtil.notNullize(response.body().string()));
        }
    }

    public JSONObject get(String endpoint) throws IOException {
        Request request = new Request.Builder()
                .url(BASE_URL + endpoint)
                .addHeader("Accept-Language", acceptLanguage)
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

            return new JSONObject(StringUtil.notNullize(response.body().string()));
        }
    }
}

5. 动作处理类 (AgentActions.java)

// src/main/java/com/codingagent/AgentActions.java
package com.codingagent;

import com.codingagent.i18n.Messages;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.vfs.VirtualFile;
import org.json.JSONArray;
import org.json.JSONObject;

public class AgentActions {

    private static final ApiClient apiClient = new ApiClient(System.getProperty("user.language"));

    public static class CompleteAction extends AnAction {
        @Override
        public void actionPerformed(@NotNull AnActionEvent e) {
            Editor editor = e.getData(CommonDataKeys.EDITOR);
            if (editor == null) return;

            Document document = editor.getDocument();
            CaretModel caretModel = editor.getCaretModel();
            int offset = caretModel.getOffset();

            try {
                JSONObject request = new JSONObject();
                request.put("action", "complete");
                request.put("code", document.getText());
                request.put("cursor_position", offset);
                request.put("language", getLanguage(e.getData(CommonDataKeys.VIRTUAL_FILE)));

                JSONObject response = apiClient.post("/code", request);
                String result = response.getString("result");

                com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction(e.getProject(), () -> {
                    document.insertString(offset, result);
                });
            } catch (Exception ex) {
                Messages.showErrorDialog(Messages.message("prompts.completeFailed", ex.getMessage()), Messages.message("extension.error"));
            }
        }
    }

    public static class GenerateAction extends AnAction {
        @Override
        public void actionPerformed(@NotNull AnActionEvent e) {
            Project project = e.getProject();
            String instruction = Messages.showInputDialog(project,
                    Messages.message("prompts.generateDescription"),
                    Messages.message("commands.generate"),
                    null,
                    Messages.message("prompts.generatePlaceholder"), null);

            if (instruction == null) return;

            Editor editor = e.getData(CommonDataKeys.EDITOR);
            if (editor == null) return;

            Document document = editor.getDocument();
            int offset = editor.getCaretModel().getOffset();

            try {
                JSONObject request = new JSONObject();
                request.put("action", "generate");
                request.put("instruction", instruction);
                request.put("language", getLanguage(e.getData(CommonDataKeys.VIRTUAL_FILE)));

                JSONObject response = apiClient.post("/code", request);
                String result = response.getString("result");

                com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction(project, () -> {
                    document.insertString(offset, result);
                });
            } catch (Exception ex) {
                Messages.showErrorDialog(Messages.message("prompts.generateFailed", ex.getMessage()), Messages.message("extension.error"));
            }
        }
    }

    public static class ExplainAction extends AnAction {
        @Override
        public void actionPerformed(@NotNull AnActionEvent e) {
            Editor editor = e.getData(CommonDataKeys.EDITOR);
            if (editor == null) return;

            SelectionModel selectionModel = editor.getSelectionModel();
            String code = selectionModel.getSelectedText();
            if (code == null || code.isEmpty()) {
                Messages.showWarningDialog(Messages.message("prompts.selectCode"), "Warning");
                return;
            }

            try {
                JSONObject request = new JSONObject();
                request.put("action", "explain");
                request.put("code", code);

                JSONObject response = apiClient.post("/code", request);
                String result = response.getString("result");

                JBPopupFactory.getInstance().createMessage(result).showInFocusCenter();
            } catch (Exception ex) {
                Messages.showErrorDialog(Messages.message("prompts.explainFailed", ex.getMessage()), Messages.message("extension.error"));
            }
        }
    }

    public static class RefactorAction extends AnAction {
        @Override
        public void actionPerformed(@NotNull AnActionEvent e) {
            Editor editor = e.getData(CommonDataKeys.EDITOR);
            if (editor == null) return;

            SelectionModel selectionModel = editor.getSelectionModel();
            String code = selectionModel.getSelectedText();
            if (code == null || code.isEmpty()) {
                Messages.showWarningDialog(Messages.message("prompts.selectCode"), "Warning");
                return;
            }

            int start = selectionModel.getSelectionStart();
            int end = selectionModel.getSelectionEnd();

            try {
                JSONObject request = new JSONObject();
                request.put("action", "refactor");
                request.put("code", code);

                JSONObject response = apiClient.post("/code", request);
                String result = response.getString("result");

                Document document = editor.getDocument();
                com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction(e.getProject(), () -> {
                    document.replaceString(start, end, result);
                });

                JSONArray suggestions = response.optJSONArray("suggestions");
                if (suggestions != null) {
                    Messages.showInfoDialog(Messages.message("prompts.refactorComplete", suggestions.toString()), "Info");
                }
            } catch (Exception ex) {
                Messages.showErrorDialog(Messages.message("prompts.refactorFailed", ex.getMessage()), Messages.message("extension.error"));
            }
        }
    }

    public static class DebugAction extends AnAction {
        @Override
        public void actionPerformed(@NotNull AnActionEvent e) {
            Editor editor = e.getData(CommonDataKeys.EDITOR);
            if (editor == null) return;

            String code = editor.getDocument().getText();

            try {
                JSONObject request = new JSONObject();
                request.put("action", "debug");
                request.put("code", code);

                JSONObject response = apiClient.post("/code", request);
                String result = response.getString("result");

                JBPopupFactory.getInstance().createMessage(result).showInFocusCenter();
            } catch (Exception ex) {
                Messages.showErrorDialog(Messages.message("prompts.debugFailed", ex.getMessage()), Messages.message("extension.error"));
            }
        }
    }

    public static class TestAction extends AnAction {
        @Override
        public void actionPerformed(@NotNull AnActionEvent e) {
            Editor editor = e.getData(CommonDataKeys.EDITOR);
            if (editor == null) return;

            SelectionModel selectionModel = editor.getSelectionModel();
            String code = selectionModel.hasSelection() ? selectionModel.getSelectedText() : editor.getDocument().getText();

            try {
                JSONObject request = new JSONObject();
                request.put("action", "test");
                request.put("code", code);

                JSONObject response = apiClient.post("/code", request);
                String result = response.getString("result");

                // 创建新文件或显示在弹出窗口中
                JBPopupFactory.getInstance().createMessage(result).showInFocusCenter();
            } catch (Exception ex) {
                Messages.showErrorDialog(Messages.message("prompts.testFailed", ex.getMessage()), Messages.message("extension.error"));
            }
        }
    }

    private static String getLanguage(VirtualFile file) {
        if (file == null) return "java";
        String extension = file.getExtension();
        if (extension == null) return "java";
        switch (extension) {
            case "py": return "python";
            case "js": return "javascript";
            // 添加更多
            default: return "java";
        }
    }
}

6. 插件配置 (META-INF/plugin.xml)

<idea-plugin>
    <id>com.codingagent.intellij</id>
    <name>Coding Agent</name>
    <vendor>Your Company</vendor>
    <version>1.0.0</version>
    <description>Local AI-powered coding assistant with multi-language support for IntelliJ</description>

    <depends>com.intellij.modules.platform</depends>

    <actions>
        <group id="CodingAgent.Menu" text="Coding Agent" popup="true">
            <add-to-group group-id="EditorPopupMenu" anchor="last"/>
            <action id="CodingAgent.Complete" class="com.codingagent.AgentActions$CompleteAction" text="%commands.complete%"/>
            <action id="CodingAgent.Generate" class="com.codingagent.AgentActions$GenerateAction" text="%commands.generate%"/>
            <action id="CodingAgent.Explain" class="com.codingagent.AgentActions$ExplainAction" text="%commands.explain%"/>
            <action id="CodingAgent.Refactor" class="com.codingagent.AgentActions$RefactorAction" text="%commands.refactor%"/>
            <action id="CodingAgent.Debug" class="com.codingagent.AgentActions$DebugAction" text="%commands.debug%"/>
            <action id="CodingAgent.Test" class="com.codingagent.AgentActions$TestAction" text="%commands.test%"/>
        </group>
    </actions>

    <extensions defaultExtensionNs="com.intellij">
        <!-- 如有需要,添加更多扩展 -->
    </extensions>
</idea-plugin>

这个IntelliJ插件实现提供了完整的编码辅助功能,支持多语言界面,并与本地后端服务集成。注意:实际开发中需要添加OkHttp依赖到build.gradle,并处理更多边缘情况。