This commit is contained in:
bobo.yang 2023-05-09 10:34:33 +08:00
parent 2eca8ee438
commit 59d1a0eb3e
19 changed files with 156 additions and 136 deletions

View File

@ -8,7 +8,7 @@ import * as os from 'os';
import * as path from 'path';
import { promisify } from 'util';
import { createTempSubdirectory } from '../util/commonUtil';
import ExtensionContextHolder from '../util/extensionContext';
import { logger } from '../util/logger';
const mkdirAsync = promisify(fs.mkdir);
const execAsync = promisify(exec);
@ -19,26 +19,8 @@ async function createTempDirectory(tempDir: string): Promise<void> {
try {
await mkdirAsync(tempDir, {recursive: true});
} catch (err) {
console.error(`Error creating temporary directory: ${err}`);
}
}
async function writeDiffFile(diff_file: string) {
try {
const workspaceDir = vscode.workspace.workspaceFolders?.[0].uri.fsPath;
const { stdout, stderr } = await execAsync('git diff --cached', {
cwd: workspaceDir
});
if (stderr) {
console.error(`Error output from git diff --cached: ${stderr}`);
return;
}
// 将结果写入到临时文件中
const tempFilePath = diff_file;
await writeFileAsync(tempFilePath, stdout);
} catch (err) {
console.error(`Error executing git diff --cached: ${err}`);
logger.channel()?.error(`Error creating temporary directory: ${err}`);
logger.channel()?.show();
}
}
@ -49,12 +31,6 @@ export const commitMessageCommand: Command = {
handler: async (userInput: string) => {
const tempDir = createTempSubdirectory('devchat/context');
// // 创建临时目录
// const diff_file = path.join(tempDir, 'diff_output.txt');
// await writeDiffFile(diff_file);
// return `[context|${diff_file}] Write a commit message`;
const workspaceDir = vscode.workspace.workspaceFolders?.[0].uri.fsPath;
if (workspaceDir) {
const commitmessageInstruction = path.join(workspaceDir, '.chat', 'instructions', 'commit_message', 'instCommitMessage.txt');

View File

@ -2,6 +2,7 @@ import * as path from 'path';
import * as vscode from 'vscode';
import { ChatContext } from './contextManager';
import { createTempSubdirectory, runCommandStringAndWriteOutput } from '../util/commonUtil';
import { logger } from '../util/logger';
export const customCommandContext: ChatContext = {
name: '<custom command>',
@ -17,10 +18,13 @@ export const customCommandContext: ChatContext = {
if (customCommand) {
const tempDir = await createTempSubdirectory('devchat/context');
const diff_file = path.join(tempDir, 'custom.txt');
logger.channel()?.info(`custom command: ${customCommand}`);
const result = await runCommandStringAndWriteOutput(customCommand, diff_file);
console.log(result.exitCode);
console.log(result.stdout);
console.log(result.stderr);
logger.channel()?.info(`custom command: ${customCommand} exit code:`, result.exitCode);
logger.channel()?.debug(`custom command: ${customCommand} stdout:`, result.stdout);
logger.channel()?.debug(`custom command: ${customCommand} stderr:`, result.stderr);
return `[context|${diff_file}]`;
}
return '';

View File

@ -2,16 +2,22 @@ import * as path from 'path';
import { ChatContext } from './contextManager';
import { createTempSubdirectory, runCommandStringAndWriteOutput } from '../util/commonUtil';
import { logger } from '../util/logger';
export const gitDiffContext: ChatContext = {
name: 'git diff',
description: 'diff for all changes',
handler: async () => {
const tempDir = await createTempSubdirectory('devchat/context');
const diff_file = path.join(tempDir, 'diff_all.txt');
const result = await runCommandStringAndWriteOutput('git diff', diff_file);
console.log(result.exitCode);
console.log(result.stdout);
console.log(result.stderr);
logger.channel()?.info(`git diff`);
const result = await runCommandStringAndWriteOutput('git diff', diff_file);
logger.channel()?.info(`git diff exit code:`, result.exitCode);
logger.channel()?.debug(`git diff stdout:`, result.stdout);
logger.channel()?.debug(`git diff stderr:`, result.stderr);
return `[context|${diff_file}]`;
},
};

View File

@ -2,16 +2,22 @@ import * as path from 'path';
import { ChatContext } from './contextManager';
import { createTempSubdirectory, runCommandStringAndWriteOutput } from '../util/commonUtil';
import { logger } from '../util/logger';
export const gitDiffCachedContext: ChatContext = {
name: 'git diff cached',
description: 'diff for cached changes',
handler: async () => {
const tempDir = await createTempSubdirectory('devchat/context');
const diff_file = path.join(tempDir, 'diff_cached.txt');
const result = await runCommandStringAndWriteOutput('git diff --cached', diff_file);
console.log(result.exitCode);
console.log(result.stdout);
console.log(result.stderr);
logger.channel()?.info(`git diff --cached`);
const result = await runCommandStringAndWriteOutput('git diff --cached', diff_file);
logger.channel()?.info(`git diff --cached exit code:`, result.exitCode);
logger.channel()?.debug(`git diff --cached stdout:`, result.stdout);
logger.channel()?.debug(`git diff --cached stderr:`, result.stderr);
return `[context|${diff_file}]`;
},
};

View File

@ -2,15 +2,20 @@
import * as vscode from 'vscode';
import * as path from 'path';
import { createTempSubdirectory, runCommandStringAndWriteOutput } from '../util/commonUtil';
import { logger } from '../util/logger';
export async function handleRefCommand(ref_command: string) {
if (ref_command) {
const tempDir = await createTempSubdirectory('devchat/context');
const diff_file = path.join(tempDir, 'custom.txt');
logger.channel()?.info(`custom command: ${ref_command}`);
const result = await runCommandStringAndWriteOutput(ref_command, diff_file);
console.log(result.exitCode);
console.log(result.stdout);
console.log(result.stderr);
logger.channel()?.info(`custom command: ${ref_command} exit code:`, result.exitCode);
logger.channel()?.debug(`custom command: ${ref_command} stdout:`, result.stdout);
logger.channel()?.debug(`custom command: ${ref_command} stderr:`, result.stderr);
return `[context|${diff_file}]`;
}

View File

@ -2,13 +2,14 @@ import * as vscode from 'vscode';
import { handleCodeSelected } from '../context/contextCodeSelected';
import { handleFileSelected } from '../context/contextFileSelected';
import { MessageHandler } from '../handler/messageHandler';
export async function sendFileSelectMessage(panel: vscode.WebviewPanel, filePath: string): Promise<void> {
const codeContext = await handleFileSelected(filePath);
panel.webview.postMessage({ command: 'appendContext', context: codeContext });
}
export async function sendCodeSelectMessage(panel: vscode.WebviewPanel, filePath: string, codeBlock: string): Promise<void> {
MessageHandler.sendMessage(panel, { command: 'appendContext', context: codeContext });
}
export async function sendCodeSelectMessage(panel: vscode.WebviewPanel, filePath: string, codeBlock: string): Promise<void> {
const codeContext = await handleCodeSelected(filePath, codeBlock);
panel.webview.postMessage({ command: 'appendContext', context: codeContext });
}
MessageHandler.sendMessage(panel, { command: 'appendContext', context: codeContext });
}

View File

@ -1,9 +1,11 @@
import * as vscode from 'vscode';
import ChatContextManager from '../context/contextManager';
import { MessageHandler } from './messageHandler';
export async function addConext(message: any, panel: vscode.WebviewPanel): Promise<void> {
const contextStr = await ChatContextManager.getInstance().processText(message.selected);
panel.webview.postMessage({ command: 'appendContext', context: contextStr });
MessageHandler.sendMessage(panel, { command: 'appendContext', context: contextStr });
return;
}

View File

@ -1,10 +1,11 @@
import * as vscode from 'vscode';
import { handleRefCommand } from '../context/contextRef';
import { MessageHandler } from './messageHandler';
// message: { command: 'addRefCommandContext', refCommand: string }
// User input: /ref ls . then "ls ." will be passed to refCommand
export async function addRefCommandContext(message: any, panel: vscode.WebviewPanel): Promise<void> {
const contextStr = await handleRefCommand(message.refCommand);
panel.webview.postMessage({ command: 'appendContext', context: contextStr });
MessageHandler.sendMessage(panel, { command: 'appendContext', context: contextStr });
return;
}

View File

@ -1,12 +1,12 @@
import * as vscode from 'vscode';
import * as fs from 'fs';
import { handleRefCommand } from '../context/contextRef';
import { MessageHandler } from './messageHandler';
// message: { command: 'contextDetail', file: string }
// read detail context information from file
// return json string
export async function contextDetail(message: any, panel: vscode.WebviewPanel): Promise<void> {
const fileContent = fs.readFileSync(message.file, 'utf-8');
panel.webview.postMessage({ command: 'contextDetailResponse', 'file':message.file, result: fileContent });
MessageHandler.sendMessage(panel, { command: 'contextDetailResponse', 'file':message.file, result: fileContent });
return;
}

View File

@ -1,10 +1,10 @@
import * as vscode from 'vscode';
import CommandManager from '../command/commandManager';
import { MessageHandler } from './messageHandler';
export async function convertCommand(message: any, panel: vscode.WebviewPanel): Promise<void> {
const newText = await CommandManager.getInstance().processText(message.text);
panel.webview.postMessage({ command: 'convertCommand', result: newText });
MessageHandler.sendMessage(panel, { command: 'convertCommand', result: newText });
return;
}

View File

@ -1,13 +1,13 @@
import * as vscode from 'vscode';
import DevChat, { LogOptions } from '../toolwrapper/devchat';
import { MessageHandler } from './messageHandler';
export async function historyMessages(message: any, panel: vscode.WebviewPanel): Promise<void> {
const devChat = new DevChat();
const logOptions: LogOptions = message.options || {};
const logEntries = await devChat.log(logOptions);
panel.webview.postMessage({ command: 'loadHistoryMessages', entries: logEntries });
MessageHandler.sendMessage(panel, { command: 'loadHistoryMessages', entries: logEntries });
return;
}

View File

@ -4,9 +4,10 @@ import * as vscode from 'vscode';
import '../command/loadCommands';
import '../context/loadContexts'
import { logger } from '../util/logger';
class MessageHandler {
export class MessageHandler {
private handlers: { [command: string]: (message: any, panel: vscode.WebviewPanel) => Promise<void> } = {};
constructor() {
@ -19,14 +20,20 @@ class MessageHandler {
async handleMessage(message: any, panel: vscode.WebviewPanel): Promise<void> {
const handler = this.handlers[message.command];
if (handler) {
logger.channel()?.info(`Handling command "${message.command}"`);
await handler(message, panel);
logger.channel()?.info(`Handling command "${message.command}" done`);
} else {
console.error(`No handler found for command "${message.command}"`);
logger.channel()?.error(`No handler found for command "${message.command}"`);
logger.channel()?.show();
}
}
sendMessage(panel: vscode.WebviewPanel, command: string, data: any): void {
panel.webview.postMessage({ command, ...data });
public static sendMessage(panel: vscode.WebviewPanel, message: object, log: boolean = true): void {
if (log) {
logger.channel()?.info(`Sending message "${JSON.stringify(message)}"`);
}
panel.webview.postMessage(message);
}
}

View File

@ -1,10 +1,10 @@
import * as vscode from 'vscode';
import CommandManager from '../command/commandManager';
import { MessageHandler } from './messageHandler';
export async function regCommandList(message: any, panel: vscode.WebviewPanel): Promise<void> {
const commandList = CommandManager.getInstance().getCommandList();
panel.webview.postMessage({ command: 'regCommandList', result: commandList });
MessageHandler.sendMessage(panel, { command: 'regCommandList', result: commandList });
return;
}

View File

@ -1,10 +1,10 @@
import * as vscode from 'vscode';
import ChatContextManager from '../context/contextManager';
import { MessageHandler } from './messageHandler';
export async function regContextList(message: any, panel: vscode.WebviewPanel): Promise<void> {
const contextList = ChatContextManager.getInstance().getContextList();
panel.webview.postMessage({ command: 'regContextList', result: contextList });
MessageHandler.sendMessage(panel, { command: 'regContextList', result: contextList });
return;
}

View File

@ -4,6 +4,8 @@ import * as fs from 'fs';
import * as path from 'path';
import DevChat from '../toolwrapper/devchat';
import CommandManager from '../command/commandManager';
import { logger } from '../util/logger';
import { MessageHandler } from './messageHandler';
// Add this function to messageHandler.ts
@ -60,7 +62,8 @@ function getInstructionFiles(): string[] {
}
}
} catch (error) {
console.error('Error reading instruction files:', error);
logger.channel()?.error(`Error reading instruction files: ${error}`);
logger.channel()?.show();
}
}
return instructionFiles;
@ -73,8 +76,6 @@ export async function sendMessage(message: any, panel: vscode.WebviewPanel): Pro
const devChat = new DevChat();
const newText2 = await CommandManager.getInstance().processText(message.text);
panel.webview.postMessage({ command: 'convertCommand', result: newText2 });
const parsedMessage = parseMessage(newText2);
const chatOptions: any = lastPromptHash ? { parent: lastPromptHash } : {};
@ -94,7 +95,7 @@ export async function sendMessage(message: any, panel: vscode.WebviewPanel): Pro
let partialData = "";
const onData = (partialResponse: string) => {
partialData += partialResponse;
panel.webview.postMessage({ command: 'receiveMessagePartial', text: partialData });
MessageHandler.sendMessage(panel, { command: 'receiveMessagePartial', text: partialData }, false);
};
const chatResponse = await devChat.chat(parsedMessage.text, chatOptions, onData);
@ -109,7 +110,7 @@ export async function sendMessage(message: any, panel: vscode.WebviewPanel): Pro
}
}
const response = chatResponse.response;
panel.webview.postMessage({ command: 'receiveMessage', text: response });
MessageHandler.sendMessage(panel, { command: 'receiveMessage', text: response });
return;
}

View File

@ -3,6 +3,8 @@ import * as fs from 'fs';
import * as path from 'path';
import * as ncp from 'ncp';
import { logger } from '../util/logger';
export function createChatDirectoryAndCopyInstructionsSync(extensionUri: vscode.Uri) {
const workspaceFolders = vscode.workspace.workspaceFolders;
@ -25,10 +27,12 @@ export function createChatDirectoryAndCopyInstructionsSync(extensionUri: vscode.
// 将 instructions 目录复制到 .chat 目录中
ncp.ncp(instructionsSrcPath, path.join(chatDirPath, 'instructions'), (err) => {
if (err) {
console.error('Error copying instructions:', err);
logger.channel()?.error('Error copying instructions:', err);
logger.channel()?.show();
}
});
} catch (error) {
console.error('Error creating .chat directory and copying instructions:', error);
logger.channel()?.error('Error creating .chat directory and copying instructions:', error);
logger.channel()?.show();
}
}

View File

@ -7,6 +7,8 @@ import * as dotenv from 'dotenv';
import * as path from 'path';
import * as fs from 'fs';
import { logger } from '../util/logger';
const spawnAsync = async (command: string, args: string[], options: any, onData: (data: string) => void): Promise<{ code: number, stdout: string; stderr: string }> => {
return new Promise((resolve, reject) => {
const child = spawn(command, args, options);
@ -121,6 +123,7 @@ class DevChat {
fs.writeFileSync(configPath, configJson);
try {
logger.channel()?.info(`Running devchat with args: ${args.join(" ")}`);
const { code, stdout, stderr } = await spawnAsync('devchat', args, {
maxBuffer: 10 * 1024 * 1024, // Set maxBuffer to 10 MB
cwd: workspaceDir,
@ -142,8 +145,7 @@ class DevChat {
}
const responseLines = stdout.trim().split("\n");
console.log(responseLines)
if (responseLines.length === 0) {
return {
"prompt-hash": "",
@ -215,6 +217,7 @@ class DevChat {
const openaiApiKey = process.env.OPENAI_API_KEY;
try {
logger.channel()?.info(`Running devchat with args: ${args.join(" ")}`);
const { code, stdout, stderr } = await spawnAsync('devchat', args, {
maxBuffer: 10 * 1024 * 1024, // Set maxBuffer to 10 MB
cwd: workspaceDir,
@ -225,13 +228,15 @@ class DevChat {
}, (partialResponse: string) => { });
if (stderr) {
console.error(stderr);
logger.channel()?.error(`Error getting log: ${stderr}`);
logger.channel()?.show();
return [];
}
return JSON.parse(stdout.trim());
} catch (error) {
console.error(error)
logger.channel()?.error(`Error getting log: ${error}`);
logger.channel()?.show();
return [];
}
}

View File

@ -1,67 +1,71 @@
import { spawn } from "child_process";
import * as vscode from 'vscode';
import { logger } from "../util/logger";
interface DtmResponse {
status: number;
message: string;
log: string;
status: number;
message: string;
log: string;
}
class DtmWrapper {
private workspaceDir: string;
private workspaceDir: string;
constructor() {
this.workspaceDir = vscode.workspace.workspaceFolders?.[0].uri.fsPath || '.';
}
private async runCommand(command: string, args: string[]): Promise<DtmResponse> {
return new Promise((resolve, reject) => {
const child = spawn(command, args, { cwd: this.workspaceDir });
let stdout = '';
let stderr = '';
child.stdout.on('data', (data) => {
stdout += data;
});
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', (code) => {
try {
const parsedOutput = JSON.parse(stdout.trim());
if (code === 0) {
resolve(parsedOutput);
} else {
reject(parsedOutput);
}
} catch (error) {
// 处理 JSON 解析异常
const errorObj = error as Error;
reject({ status: -1, message: 'JSON parse error', log: errorObj.message });
}
});
});
}
async scaffold(directoryTree: string): Promise<DtmResponse> {
return await this.runCommand('dtm', ['scaffold', directoryTree, '-o', 'json']);
}
async patch(patchFilePath: string): Promise<DtmResponse> {
return await this.runCommand('dtm', ['patch', patchFilePath, '-o', 'json']);
}
async commit(commitMsg: string): Promise<DtmResponse> {
try {
return await this.runCommand('dtm', ['commit', '-m', commitMsg, '-o', 'json']);
} catch (error) {
// 处理 runCommand 中的 reject 错误
console.error('Error in commit:', error);
return error as DtmResponse;
constructor() {
this.workspaceDir = vscode.workspace.workspaceFolders?.[0].uri.fsPath || '.';
}
private async runCommand(command: string, args: string[]): Promise<DtmResponse> {
return new Promise((resolve, reject) => {
logger.channel()?.info(`Running command: ${command} ${args.join(' ')}`);
const child = spawn(command, args, { cwd: this.workspaceDir });
let stdout = '';
let stderr = '';
child.stdout.on('data', (data) => {
stdout += data;
});
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', (code) => {
try {
const parsedOutput = JSON.parse(stdout.trim());
if (code === 0) {
resolve(parsedOutput);
} else {
reject(parsedOutput);
}
} catch (error) {
// 处理 JSON 解析异常
const errorObj = error as Error;
reject({ status: -1, message: 'JSON parse error', log: errorObj.message });
}
});
});
}
async scaffold(directoryTree: string): Promise<DtmResponse> {
return await this.runCommand('dtm', ['scaffold', directoryTree, '-o', 'json']);
}
async patch(patchFilePath: string): Promise<DtmResponse> {
return await this.runCommand('dtm', ['patch', patchFilePath, '-o', 'json']);
}
async commit(commitMsg: string): Promise<DtmResponse> {
try {
return await this.runCommand('dtm', ['commit', '-m', commitMsg, '-o', 'json']);
} catch (error) {
// 处理 runCommand 中的 reject 错误
logger.channel()?.error(`Error in commit: ${error}`);
logger.channel()?.show();
return error as DtmResponse;
}
}
}
}
export default DtmWrapper;

View File

@ -1,15 +1,13 @@
import * as vscode from 'vscode'
export class logger {
private static _channel: vscode.OutputChannel | undefined;
private static _channel: vscode.LogOutputChannel | undefined;
public static init(context: vscode.ExtensionContext): void {
this._channel = vscode.window.createOutputChannel('DevChat');
this.log('DevChat is active');
this._channel = vscode.window.createOutputChannel('DevChat', { log: true });
}
public static log(text: string): void {
if (this._channel) {
this._channel.appendLine(text);
}
public static channel(): vscode.LogOutputChannel | undefined {
return this._channel;
}
}