
- Add safety check to remove existing workflow directory if present - Ensure clean installation by removing old files before copying - Prevent potential conflicts during version updates
471 lines
16 KiB
TypeScript
471 lines
16 KiB
TypeScript
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<void> {
|
|
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<boolean> {
|
|
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" ? "结果输出请使用中文。" : ""} `;
|
|
} |