From 0d59445cef5d5082914da8ccc8417aa1257b0bb9 Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Thu, 4 May 2023 16:55:40 +0800 Subject: [PATCH] add context implementation --- src/commitMessageCommand.ts | 5 +- src/commonUtil.ts | 102 ++++++++++++++++++++++++++++++++++++ src/contextCodeSelected.ts | 24 +++++++++ src/contextCustomCommand.ts | 28 ++++++++++ src/contextFileSelected.ts | 27 ++++++++++ src/contextGitDiff.ts | 17 ++++++ src/contextGitDiffCached.ts | 17 ++++++ src/extension.ts | 7 +-- src/loadContexts.ts | 8 ++- src/messageHandler.ts | 12 +++-- 10 files changed, 236 insertions(+), 11 deletions(-) create mode 100644 src/commonUtil.ts create mode 100644 src/contextCodeSelected.ts create mode 100644 src/contextCustomCommand.ts create mode 100644 src/contextFileSelected.ts create mode 100644 src/contextGitDiff.ts create mode 100644 src/contextGitDiffCached.ts diff --git a/src/commitMessageCommand.ts b/src/commitMessageCommand.ts index d169131..3796e83 100644 --- a/src/commitMessageCommand.ts +++ b/src/commitMessageCommand.ts @@ -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); diff --git a/src/commonUtil.ts b/src/commonUtil.ts new file mode 100644 index 0000000..bb82a14 --- /dev/null +++ b/src/commonUtil.ts @@ -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 { + 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 { + 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 { + try { + // 打开指定的文件 + const document = await vscode.workspace.openTextDocument(fileName); + // 获取文件的语言标识符 + const languageId = document.languageId; + return languageId; + } catch (error) { + // 如果无法打开文件或发生其他错误,返回undefined + return undefined; + } + } \ No newline at end of file diff --git a/src/contextCodeSelected.ts b/src/contextCodeSelected.ts new file mode 100644 index 0000000..e86eda2 --- /dev/null +++ b/src/contextCodeSelected.ts @@ -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}]`; +} \ No newline at end of file diff --git a/src/contextCustomCommand.ts b/src/contextCustomCommand.ts new file mode 100644 index 0000000..3ec5914 --- /dev/null +++ b/src/contextCustomCommand.ts @@ -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: '', + 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 ''; + }, +}; diff --git a/src/contextFileSelected.ts b/src/contextFileSelected.ts new file mode 100644 index 0000000..faa212e --- /dev/null +++ b/src/contextFileSelected.ts @@ -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}]`; +} \ No newline at end of file diff --git a/src/contextGitDiff.ts b/src/contextGitDiff.ts new file mode 100644 index 0000000..a902e21 --- /dev/null +++ b/src/contextGitDiff.ts @@ -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}]`; + }, +}; diff --git a/src/contextGitDiffCached.ts b/src/contextGitDiffCached.ts new file mode 100644 index 0000000..f029d0b --- /dev/null +++ b/src/contextGitDiffCached.ts @@ -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}]`; + }, +}; diff --git a/src/extension.ts b/src/extension.ts index d3c5e4f..4cfeca0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -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); } }); diff --git a/src/loadContexts.ts b/src/loadContexts.ts index 4f91cbd..28c6796 100644 --- a/src/loadContexts.ts +++ b/src/loadContexts.ts @@ -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); \ No newline at end of file +chatContextManager.registerContext(exampleContext); +chatContextManager.registerContext(gitDiffCachedContext); +chatContextManager.registerContext(gitDiffContext); +chatContextManager.registerContext(customCommandContext); diff --git a/src/messageHandler.ts b/src/messageHandler.ts index b30e822..ed66670 100644 --- a/src/messageHandler.ts +++ b/src/messageHandler.ts @@ -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 { 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 { + 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 { + const codeContext = await handleCodeSelected(filePath, codeBlock); + panel.webview.postMessage({ command: 'appendContext', context: codeContext }); } export function askAI(panel: vscode.WebviewPanel, codeBlock: string, question: string): void {