add context implementation

This commit is contained in:
bobo.yang 2023-05-04 16:55:40 +08:00 committed by Rankin Zheng
parent 4b842048a8
commit 0d59445cef
10 changed files with 236 additions and 11 deletions

View File

@ -7,6 +7,7 @@ import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { promisify } from 'util';
import { createTempSubdirectory } from './commonUtil';
import ExtensionContextHolder from './extensionContext';
const mkdirAsync = promisify(fs.mkdir);
@ -46,11 +47,9 @@ export const commitMessageCommand: Command = {
pattern: 'git: commit message',
description: 'commit message for changes',
handler: async (userInput: string) => {
const systemTempDir = os.tmpdir();
const tempDir = path.join(systemTempDir, 'devchat/context');
const tempDir = createTempSubdirectory('devchat/context');
// 创建临时目录
await createTempDirectory(tempDir);
const diff_file = path.join(tempDir, 'diff_output.txt');
await writeDiffFile(diff_file);

102
src/commonUtil.ts Normal file
View File

@ -0,0 +1,102 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import { spawn, exec } from 'child_process';
export function createTempSubdirectory(subdir: string): string {
// 获取系统临时目录
const tempDir = os.tmpdir();
// 构建完整的目录路径
let targetDir = path.join(tempDir, subdir, Date.now().toString());
// 检查目录是否存在,如果存在则重新生成目录名称
while (fs.existsSync(targetDir)) {
targetDir = path.join(tempDir, subdir, Date.now().toString());
}
// 递归创建目录
fs.mkdirSync(targetDir, { recursive: true });
// 返回创建的目录的绝对路径
return targetDir;
}
interface CommandResult {
exitCode: number | null;
stdout: string;
stderr: string;
}
export async function runCommandAndWriteOutput(
command: string,
args: string[],
outputFile: string
): Promise<CommandResult> {
return new Promise((resolve) => {
// 获取当前工作区目录
const workspaceDir = vscode.workspace.workspaceFolders?.[0].uri.fsPath || '.';
// 使用spawn执行命令
const childProcess = spawn(command, args, { cwd: workspaceDir });
let stdout = '';
let stderr = '';
// 监听stdout数据
childProcess.stdout.on('data', (data) => {
stdout += data.toString();
});
// 监听stderr数据
childProcess.stderr.on('data', (data) => {
stderr += data.toString();
});
// 监听进程退出事件
childProcess.on('exit', (exitCode) => {
// 将命令输出结果写入到文件
fs.writeFileSync(outputFile, stdout);
// 返回结果
resolve({
exitCode,
stdout,
stderr,
});
});
});
}
export async function runCommandStringAndWriteOutput(
commandString: string,
outputFile: string
): Promise<CommandResult> {
return new Promise((resolve) => {
const workspaceDir = vscode.workspace.workspaceFolders?.[0].uri.fsPath || '.';
// 使用exec执行命令行字符串
const childProcess = exec(commandString, { cwd: workspaceDir }, (error, stdout, stderr) => {
// 将命令输出结果写入到文件
fs.writeFileSync(outputFile, stdout);
// 返回结果
resolve({
exitCode: error && error.code? error.code : 0,
stdout,
stderr,
});
});
});
}
export async function getLanguageIdByFileName(fileName: string): Promise<string | undefined> {
try {
// 打开指定的文件
const document = await vscode.workspace.openTextDocument(fileName);
// 获取文件的语言标识符
const languageId = document.languageId;
return languageId;
} catch (error) {
// 如果无法打开文件或发生其他错误返回undefined
return undefined;
}
}

View File

@ -0,0 +1,24 @@
import * as vscode from 'vscode';
import * as path from 'path';
import { createTempSubdirectory, getLanguageIdByFileName } from './commonUtil';
export async function handleCodeSelected(fileSelected: string, codeSelected: string) {
// get file name from fileSelected
const fileName = path.basename(fileSelected);
// create temp directory and file
const tempDir = await createTempSubdirectory('devchat/context');
const tempFile = path.join(tempDir, fileName);
// get the language from fileSelected
const languageId = await getLanguageIdByFileName(fileSelected);
// convert fileContent to markdown code block with languageId and file path
const markdownCodeBlock = `\`\`\`${languageId} path=${fileSelected}\n${codeSelected}\n\`\`\``;
// save markdownCodeBlock to temp file
await vscode.workspace.fs.writeFile(vscode.Uri.file(tempFile), Buffer.from(markdownCodeBlock));
return `[context|${tempFile}]`;
}

View File

@ -0,0 +1,28 @@
import * as path from 'path';
import * as vscode from 'vscode';
import { ChatContext } from './contextManager';
import { createTempSubdirectory, runCommandAndWriteOutput, runCommandStringAndWriteOutput } from './commonUtil';
export const customCommandContext: ChatContext = {
name: '<custom command>',
description: 'custorm command',
handler: async () => {
// popup a dialog to ask for the command line to run
const customCommand = await vscode.window.showInputBox({
prompt: 'Input your custom command',
placeHolder: 'for example: ls -l'
});
// 检查用户是否输入了命令
if (customCommand) {
const tempDir = await createTempSubdirectory('devchat/context');
const diff_file = path.join(tempDir, 'custom.txt');
const result = await runCommandStringAndWriteOutput(customCommand, diff_file);
console.log(result.exitCode);
console.log(result.stdout);
console.log(result.stderr);
return `[context|${diff_file}]`;
}
return '';
},
};

View File

@ -0,0 +1,27 @@
import * as vscode from 'vscode';
import * as path from 'path';
import { createTempSubdirectory, getLanguageIdByFileName } from './commonUtil';
export async function handleFileSelected(fileSelected: string) {
// get file name from fileSelected
const fileName = path.basename(fileSelected);
// create temp directory and file
const tempDir = await createTempSubdirectory('devchat/context');
const tempFile = path.join(tempDir, fileName);
// load content in fileSelected
const fileContent = await vscode.workspace.fs.readFile(vscode.Uri.file(fileSelected));
// get the language from fileSelected
const languageId = await getLanguageIdByFileName(fileSelected);
// convert fileContent to markdown code block with languageId and file path
const markdownCodeBlock = `\`\`\`${languageId} path=${fileSelected}\n${fileContent}\n\`\`\``;
// save markdownCodeBlock to temp file
await vscode.workspace.fs.writeFile(vscode.Uri.file(tempFile), Buffer.from(markdownCodeBlock));
return `[context|${tempFile}]`;
}

17
src/contextGitDiff.ts Normal file
View File

@ -0,0 +1,17 @@
import * as path from 'path';
import { ChatContext } from './contextManager';
import { createTempSubdirectory, runCommandAndWriteOutput } from './commonUtil';
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 runCommandAndWriteOutput('git', ['diff'], diff_file);
console.log(result.exitCode);
console.log(result.stdout);
console.log(result.stderr);
return `[context|${diff_file}]`;
},
};

View File

@ -0,0 +1,17 @@
import * as path from 'path';
import { ChatContext } from './contextManager';
import { createTempSubdirectory, runCommandAndWriteOutput } from './commonUtil';
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 runCommandAndWriteOutput('git', ['diff', '--cached'], diff_file);
console.log(result.exitCode);
console.log(result.stdout);
console.log(result.stderr);
return `[context|${diff_file}]`;
},
};

View File

@ -9,6 +9,7 @@ const sendCodeSelectMessage = require('./messageHandler').sendCodeSelectMessage;
import ExtensionContextHolder from './extensionContext';
function createChatDirectoryAndCopyInstructionsSync(extensionUri: vscode.Uri) {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders) {
return;
@ -51,7 +52,7 @@ function activate(context: vscode.ExtensionContext) {
}
});
const disposable_add_context = vscode.commands.registerCommand('devchat.addConext', (uri: { path: any; }) => {
const disposable_add_context = vscode.commands.registerCommand('devchat.addConext', async (uri: { path: any; }) => {
if (!ChatPanel.currentPanel()) {
if (vscode.workspace.workspaceFolders) {
ChatPanel.createOrShow(context.extensionUri);
@ -61,7 +62,7 @@ function activate(context: vscode.ExtensionContext) {
}
}
sendFileSelectMessage(ChatPanel.currentPanel().panel(), uri.path);
await sendFileSelectMessage(ChatPanel.currentPanel().panel(), uri.path);
});
const disposableCodeContext = vscode.commands.registerCommand('devchat.askForCode', async () => {
@ -77,7 +78,7 @@ function activate(context: vscode.ExtensionContext) {
}
const selectedText = editor.document.getText(editor.selection);
sendCodeSelectMessage(ChatPanel.currentPanel().panel(), selectedText);
await sendCodeSelectMessage(ChatPanel.currentPanel().panel(), editor.document.fileName, selectedText);
}
});

View File

@ -1,7 +1,13 @@
import ChatContextManager from './contextManager';
import { exampleContext } from './exampleContext';
import { gitDiffCachedContext } from './contextGitDiffCached';
import { gitDiffContext } from './contextGitDiff';
import { customCommandContext } from './contextCustomCommand';
const chatContextManager = ChatContextManager.getInstance();
// 注册命令
chatContextManager.registerContext(exampleContext);
chatContextManager.registerContext(gitDiffCachedContext);
chatContextManager.registerContext(gitDiffContext);
chatContextManager.registerContext(customCommandContext);

View File

@ -12,6 +12,8 @@ import './loadCommands';
import './loadContexts'
import CommandManager, { Command } from './commandManager';
import ChatContextManager from './contextManager';
import { handleCodeSelected } from './contextCodeSelected';
import { handleFileSelected } from './contextFileSelected';
import * as vscode3 from 'vscode';
@ -30,12 +32,14 @@ async function deleteTempPatchFile(filePath: string): Promise<void> {
await unlinkAsync(filePath);
}
export function sendFileSelectMessage(panel: vscode.WebviewPanel, filePath: string): void {
panel.webview.postMessage({ command: 'file_select', filePath });
export async function sendFileSelectMessage(panel: vscode.WebviewPanel, filePath: string): Promise<void> {
const codeContext = await handleFileSelected(filePath);
panel.webview.postMessage({ command: 'appendContext', context: codeContext });
}
export function sendCodeSelectMessage(panel: vscode.WebviewPanel, codeBlock: string): void {
panel.webview.postMessage({ command: 'code_select', codeBlock });
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 });
}
export function askAI(panel: vscode.WebviewPanel, codeBlock: string, question: string): void {