import * as vscode from "vscode"; import * as fs from "fs"; import * as path from "path"; import * as util from "util"; import { sendFileSelectMessage, sendCodeSelectMessage } from "./util"; import { ExtensionContextHolder } from "../util/extensionContext"; import { FilePairManager } from "../util/diffFilePairs"; import { UiUtilWrapper } from "../util/uiUtil"; import { sendCommandListByDevChatRun } from '../handler/workflowCommandHandler'; import { DevChatClient } from "../toolwrapper/devchatClient"; import { chatWithDevChat } from '../handler/chatHandler'; import { focusDevChatInput } from '../handler/focusHandler'; import { DevChatConfig } from '../util/config'; import { MessageHandler } from "../handler/messageHandler"; import { startLocalService } from '../util/localService'; import { logger } from "../util/logger"; const readdir = util.promisify(fs.readdir); const mkdir = util.promisify(fs.mkdir); const copyFile = util.promisify(fs.copyFile); // It is used to copy workflow commands to user directory. async function copyDirectory(src: string, dest: string): Promise { await mkdir(dest, { recursive: true }); const entries = await readdir(src, { withFileTypes: true }); for (let entry of entries) { const srcPath = path.join(src, entry.name); const destPath = path.join(dest, entry.name); if (entry.name === ".git") { continue; } if (entry.isDirectory()) { await copyDirectory(srcPath, destPath); } else { await copyFile(srcPath, destPath); } } } export function registerOpenChatPanelCommand(context: vscode.ExtensionContext) { let disposable = vscode.commands.registerCommand( "devchat.openChatPanel", async () => { await vscode.commands.executeCommand("devchat-view.focus"); await focusDevChatInput(ExtensionContextHolder.provider?.view()!); } ); context.subscriptions.push(disposable); } async function ensureChatPanel( context: vscode.ExtensionContext ): Promise { await vscode.commands.executeCommand("devchat-view.focus"); return true; } export function registerAddContextCommand(context: vscode.ExtensionContext) { const callback = async (uri: { fsPath: any }) => { if (!(await ensureChatPanel(context))) { return; } await sendFileSelectMessage( ExtensionContextHolder.provider?.view()!, uri.fsPath ); }; context.subscriptions.push( vscode.commands.registerCommand("devchat.addContext", callback) ); context.subscriptions.push( vscode.commands.registerCommand("devchat.addConext_chinese", callback) ); } export function registerAskForCodeCommand(context: vscode.ExtensionContext) { const callback = async () => { const editor = vscode.window.activeTextEditor; if (editor) { if (!(await ensureChatPanel(context))) { return; } const selectedText = editor.document.getText(editor.selection); await sendCodeSelectMessage( ExtensionContextHolder.provider?.view()!, editor.document.fileName, selectedText, editor.selection.start.line ); } }; context.subscriptions.push( vscode.commands.registerCommand("devchat.askForCode", callback) ); context.subscriptions.push( vscode.commands.registerCommand("devchat.askForCode_chinese", callback) ); } export function registerAskForFileCommand(context: vscode.ExtensionContext) { const callback = async () => { const editor = vscode.window.activeTextEditor; if (editor) { if (!(await ensureChatPanel(context))) { return; } await sendFileSelectMessage( ExtensionContextHolder.provider?.view()!, editor.document.fileName ); } }; context.subscriptions.push( vscode.commands.registerCommand("devchat.askForFile", callback) ); context.subscriptions.push( vscode.commands.registerCommand("devchat.askForFile_chinese", callback) ); } export function registerStatusBarItemClickCommand( context: vscode.ExtensionContext ) { context.subscriptions.push( vscode.commands.registerCommand("devcaht.onStatusBarClick", async () => { await vscode.commands.executeCommand("devchat-view.focus"); }) ); } export function regPythonPathCommand(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.commands.registerCommand("devchat.PythonPath", async () => { const pythonPath = (await vscode.window.showInputBox({ title: "Set Python Path", placeHolder: "Set Python Path", })) ?? ""; if (pythonPath) { DevChatConfig.getInstance().set("python_for_chat", pythonPath); } }) ); } export function regApplyDiffResultCommand(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.commands.registerCommand("devchat.applyDiffResult", async () => { const activeEditor = vscode.window.activeTextEditor; const fileName = activeEditor!.document.fileName; const [leftUri, rightUri] = FilePairManager.getInstance().findPair( fileName ) || [undefined, undefined]; if (leftUri && rightUri) { // 获取对比的两个文件 const leftDoc = await vscode.workspace.openTextDocument(leftUri); const rightDoc = await vscode.workspace.openTextDocument(rightUri); // close rightDoc await vscode.commands.executeCommand( "workbench.action.closeActiveEditor" ); // 将右边文档的内容替换到左边文档 const leftEditor = await vscode.window.showTextDocument(leftDoc); await leftEditor.edit((editBuilder) => { const fullRange = new vscode.Range(0, 0, leftDoc.lineCount, 0); editBuilder.replace(fullRange, rightDoc.getText()); }); // 保存左边文档 await leftDoc.save(); MessageHandler.sendMessage( ExtensionContextHolder.provider?.view()!, { command: 'codeDiffApply', 'value': {} } ); } else { vscode.window.showErrorMessage("No file to apply diff result."); } }) ); } function getExtensionVersion(): string { const packageJsonPath = path.join(__dirname, '..', 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); return packageJson.version; } export function registerInstallCommandsCommand( context: vscode.ExtensionContext ) { let disposable = vscode.commands.registerCommand( "DevChat.InstallCommands", async () => { logger.channel()?.debug("InstallCommands command triggered."); const homePath = process.env.HOME || process.env.USERPROFILE || ""; const sysDirPath = path.join(homePath, ".chat", "scripts"); const sysMericoDirPath = path.join(homePath, ".chat", "scripts", "merico"); const devchatConfig = DevChatConfig.getInstance(); const pluginDirPath = path.join( UiUtilWrapper.extensionPath(), "workflowsCommands" ); // Adjust this path as needed const dcClient = new DevChatClient(); const updatePublicWorkflow = devchatConfig.get("update_public_workflow", true); const currentVersion = UiUtilWrapper.extensionPath(); const previousVersion = devchatConfig.get("last_devchat_version", ""); let copiedDirectory = false; if (!fs.existsSync(sysMericoDirPath) || (updatePublicWorkflow === false && currentVersion !== previousVersion)) { logger.channel()?.debug("Creating directory: " + sysMericoDirPath); if (fs.existsSync(sysMericoDirPath)) { fs.rmSync(sysMericoDirPath, { recursive: true, force: true }); } await copyDirectory(pluginDirPath, sysDirPath); copiedDirectory = true; } devchatConfig.set("last_devchat_version", currentVersion); if (copiedDirectory) { logger.channel()?.debug("Directory copied successfully."); sendCommandListByDevChatRun(); } else { // Check if ~/.chat/scripts directory exists if (!fs.existsSync(sysMericoDirPath)) { // Directory does not exist, wait for updateWorkflows to finish logger.channel()?.debug("Update workflows..."); await dcClient.updateWorkflows(); await dcClient.updateCustomWorkflows(); sendCommandListByDevChatRun(); } else { // Directory exists, execute sendCommandListByDevChatRun immediately logger.channel()?.debug("Sending and updating workflows..."); await sendCommandListByDevChatRun(); // Then asynchronously execute updateWorkflows await dcClient.updateWorkflows(); await dcClient.updateCustomWorkflows(); await sendCommandListByDevChatRun(); } } // Ensure the panel is activated await ensureChatPanel(context); } ); context.subscriptions.push(disposable); } export function registerStartLocalServiceCommand( context: vscode.ExtensionContext ) { let disposable = vscode.commands.registerCommand( "DevChat.StartLocalService", async () => { try { const workspaceDir = UiUtilWrapper.workspaceFoldersFirstPath() ?? ''; logger.channel()?.debug(`extensionPath: ${context.extensionPath}`); logger.channel()?.debug(`workspacePath: ${workspaceDir}`); const port = await startLocalService(context.extensionPath, workspaceDir); logger.channel()?.debug(`Local service started on port ${port}`); } catch (error) { logger.channel()?.error('Failed to start local service:', error); } } ); context.subscriptions.push(disposable); } export function registerDevChatChatCommand(context: vscode.ExtensionContext) { let disposable = vscode.commands.registerCommand( "DevChat.Chat", async (message: string) => { ensureChatPanel(context); if (!ExtensionContextHolder.provider?.view()) { // wait 2 seconds await new Promise((resolve, reject) => { setTimeout(() => { resolve(true); }, 2000); }); } chatWithDevChat(ExtensionContextHolder.provider?.view()!, message); } ); context.subscriptions.push(disposable); } export function registerCodeLensRangeCommand(context: vscode.ExtensionContext) { let disposable = vscode.commands.registerCommand( "CodeLens.Range", async (message: string, pos: { start: number; end: number }) => { const editor = vscode.window.activeTextEditor; if (editor) { const range = new vscode.Range( new vscode.Position(pos.start, 0), new vscode.Position(pos.end + 1, 0) ); editor.selection = new vscode.Selection(range.start, range.end); } ensureChatPanel(context); if (!ExtensionContextHolder.provider?.view()) { // wait 2 seconds await new Promise((resolve, reject) => { setTimeout(() => { resolve(true); }, 2000); }); } chatWithDevChat(ExtensionContextHolder.provider?.view()!, message); } ); context.subscriptions.push(disposable); } export function registerHandleUri(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.window.registerUriHandler({ async handleUri(uri) { // 解析 URI 并执行相应的操作 if (uri.path.includes("accesskey")) { const accessKey = uri.path.split("/")[2]; DevChatConfig.getInstance().set("provides.devchat.api_key", accessKey); DevChatConfig.getInstance().set("provides.devchat.api_base", "https://api.devchat.ai/v1"); ensureChatPanel(context); await new Promise((resolve, reject) => { setTimeout(() => { resolve(true); }, 1000); }); ExtensionContextHolder.provider?.reloadWebview(); } }, }) ); } export function registerExplainCommand(context: vscode.ExtensionContext) { const callback = async () => { const editor = vscode.window.activeTextEditor; if (editor) { if (!(await ensureChatPanel(context))) { return; } chatWithDevChat(ExtensionContextHolder.provider?.view()!, "/explain"); } }; context.subscriptions.push( vscode.commands.registerCommand("devchat.explain", callback) ); context.subscriptions.push( vscode.commands.registerCommand("devchat.explain_chinese", callback) ); } export function registerCommentCommand(context: vscode.ExtensionContext) { const callback = async () => { const editor = vscode.window.activeTextEditor; if (editor) { if (!(await ensureChatPanel(context))) { return; } chatWithDevChat(ExtensionContextHolder.provider?.view()!, "/comments"); } }; context.subscriptions.push( vscode.commands.registerCommand("devchat.comments", callback) ); context.subscriptions.push( vscode.commands.registerCommand("devchat.comments_chinese", callback) ); } export function registerFixCommand(context: vscode.ExtensionContext) { const callback = async () => { const editor = vscode.window.activeTextEditor; if (editor) { if (!(await ensureChatPanel(context))) { return; } chatWithDevChat(ExtensionContextHolder.provider?.view()!, "/fix"); } }; context.subscriptions.push( vscode.commands.registerCommand("devchat.fix", callback) ); context.subscriptions.push( vscode.commands.registerCommand("devchat.fix_chinese", callback) ); } export async function registerQuickFixCommand(context: vscode.ExtensionContext) { let disposable = vscode.commands.registerCommand( "DevChat.quickFixAskDevChat", async (document: vscode.TextDocument, range: vscode.Range | vscode.Selection, diagnostic: vscode.Diagnostic) => { ensureChatPanel(context); if (!ExtensionContextHolder.provider?.view()) { await waitForPanelActivation(); } // select the code const editor = vscode.window.activeTextEditor; editor!.selection = new vscode.Selection(range.start, range.end); chatWithDevChat(ExtensionContextHolder.provider?.view()!, "/ask_issue "); } ); let disposableFixUsingDevChat = vscode.commands.registerCommand( "DevChat.quickFixUsingDevChat", async (document: vscode.TextDocument, range: vscode.Range | vscode.Selection, diagnostic: vscode.Diagnostic) => { ensureChatPanel(context); if (!ExtensionContextHolder.provider?.view()) { await waitForPanelActivation(); } // select the code const editor = vscode.window.activeTextEditor; editor!.selection = new vscode.Selection(range.start, range.end); chatWithDevChat(ExtensionContextHolder.provider?.view()!, "/fix_issue "); } ); context.subscriptions.push(disposable); context.subscriptions.push(disposableFixUsingDevChat); } async function waitForPanelActivation() { return new Promise((resolve) => { setTimeout(() => { resolve(true); }, 2000); }); } async function generatePrompt(code: string, surroundingCode: string, diagnosticMessage: string, language: string) { const editor = vscode.window.activeTextEditor; if (editor) { const selectedText = editor.document.getText(editor.selection); await sendCodeSelectMessage( ExtensionContextHolder.provider?.view()!, editor.document.fileName, code, 0 ); // wait 1 second await new Promise((resolve) => setTimeout(resolve, 1000)); return `Context code is current edit file.\n\nThere is an error in the context code:\n\`\`\`\n${surroundingCode}\n\`\`\`\n\nHow do I fix this problem in the above code?: ${diagnosticMessage}, please output steps to fix it. ${language === "zh" ? "结果输出请使用中文。" : ""} `; } return `current edit file is:\n\`\`\`\n${code}\n\`\`\`\n\nThere is an error in the above code:\n\`\`\`\n${surroundingCode}\n\`\`\`\n\nHow do I fix this problem in the above code?: ${diagnosticMessage}, please output steps to fix it. ${language === "zh" ? "结果输出请使用中文。" : ""} `; }