diff --git a/src/command/commandManager.ts b/src/command/commandManager.ts index b0600d0..9c53dfd 100644 --- a/src/command/commandManager.ts +++ b/src/command/commandManager.ts @@ -1,31 +1,31 @@ import CustomCommands from "./customCommand"; export interface Command { - name: string; - pattern: string; - description: string; - handler: (commandName: string, userInput: string) => Promise; - } - - class CommandManager { - private static instance: CommandManager; - private commands: Command[] = []; - - private constructor() {} - - public static getInstance(): CommandManager { - if (!CommandManager.instance) { - CommandManager.instance = new CommandManager(); - } - - return CommandManager.instance; - } - - registerCommand(command: Command): void { - this.commands.push(command); - } - - getCommandList(includeHide: boolean = false): Command[] { + name: string; + pattern: string; + description: string; + handler: (commandName: string, userInput: string) => Promise; +} + +class CommandManager { + private static instance: CommandManager; + private commands: Command[] = []; + + private constructor() { } + + public static getInstance(): CommandManager { + if (!CommandManager.instance) { + CommandManager.instance = new CommandManager(); + } + + return CommandManager.instance; + } + + registerCommand(command: Command): void { + this.commands.push(command); + } + + getCommandList(includeHide: boolean = false): Command[] { // load commands from CustomCommands let newCommands: Command[] = [...this.commands]; const customCommands = CustomCommands.getInstance(); @@ -36,49 +36,49 @@ export interface Command { pattern: command.pattern, description: command.description, handler: async (commandName: string, userInput: string) => { - return CustomCommands.getInstance().handleCommand(commandName); + return CustomCommands.getInstance().handleCommand(commandName, userInput); } }; if (command.show || includeHide) { newCommands.push(commandObj); } }); - return newCommands; - } - - async processText(text: string): Promise { - // 定义一个异步函数来处理单个命令 - const processCommand = async (commandObj: Command, userInput: string) => { - // 转义特殊字符 - const escapedPattern = commandObj.pattern.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); - const commandPattern = new RegExp( - `\\/(${escapedPattern.replace('{{prompt}}', '(.+?)')})`, - 'g' - ); + return newCommands; + } - const matches = Array.from(text.matchAll(commandPattern)); - const replacements = await Promise.all( - matches.map(async (match) => { - const matchedUserInput = match[1]; - return await commandObj.handler(commandObj.name, matchedUserInput); - }) - ); + async processText(text: string): Promise { + // 定义一个异步函数来处理单个命令 + const processCommand = async (commandObj: Command, userInput: string) => { + // 转义特殊字符 + const escapedPattern = commandObj.pattern.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + const commandPattern = new RegExp( + `\\/(${escapedPattern.replace('\\{\\{prompt\\}\\}', '\\{\\{(.+?)\\}\\}')})`, + 'g' + ); - let result = userInput; - for (let i = 0; i < matches.length; i++) { - result = result.replace(matches[i][0], replacements[i]); - } - return result; + const matches = Array.from(text.matchAll(commandPattern)); + const replacements = await Promise.all( + matches.map(async (match) => { + const matchedUserInput = match[2]; + return await commandObj.handler(commandObj.name, matchedUserInput); + }) + ); + + let result = userInput; + for (let i = 0; i < matches.length; i++) { + result = result.replace(matches[i][0], replacements[i]); + } + return result; }; - - // 处理所有命令 - let result = text; - for (const commandObj of this.getCommandList(true)) { - result = await processCommand(commandObj, result); - } - - return result; - } - } - - export default CommandManager; + + // 处理所有命令 + let result = text; + for (const commandObj of this.getCommandList(true)) { + result = await processCommand(commandObj, result); + } + + return result; + } +} + +export default CommandManager; diff --git a/src/command/customCommand.ts b/src/command/customCommand.ts index 381005c..4fe66d6 100644 --- a/src/command/customCommand.ts +++ b/src/command/customCommand.ts @@ -30,24 +30,33 @@ class CustomCommands { this.commands = []; try { - const subDirs = fs.readdirSync(workflowsDir, { withFileTypes: true }) + const extensionDirs = fs.readdirSync(workflowsDir, { withFileTypes: true }) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); - for (const dir of subDirs) { - const settingsPath = path.join(workflowsDir, dir, '_setting_.json'); - if (fs.existsSync(settingsPath)) { - const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); - const command: Command = { - name: dir, - pattern: settings.pattern, - description: settings.description, - message: settings.message, - default: settings.default, - show: settings.show === undefined ? "true" : settings.show, - instructions: settings.instructions - }; - this.commands.push(command); + for (const extensionDir of extensionDirs) { + const commandDir = path.join(workflowsDir, extensionDir, 'command'); + if (fs.existsSync(commandDir)) { + const commandSubDirs = fs.readdirSync(commandDir, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name); + + for (const commandSubDir of commandSubDirs) { + const settingsPath = path.join(commandDir, commandSubDir, '_setting_.json'); + if (fs.existsSync(settingsPath)) { + const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); + const command: Command = { + name: commandSubDir, + pattern: settings.pattern, + description: settings.description, + message: settings.message, + default: settings.default, + show: settings.show === undefined ? "true" : settings.show, + instructions: settings.instructions.map((instruction: string) => path.join(commandDir, commandSubDir, instruction)) + }; + this.commands.push(command); + } + } } } } catch (error) { @@ -71,7 +80,7 @@ class CustomCommands { } - public handleCommand(commandName: string): string { + public handleCommand(commandName: string, userInput: string): string { // 获取命令对象,这里假设您已经有一个方法或属性可以获取到命令对象 const command = this.getCommand(commandName); if (!command) { @@ -80,13 +89,39 @@ class CustomCommands { return ''; } - // 构建instructions列表字符串 + let commandMessage = command.message; + if (userInput && userInput.length > 0) { + // userInput is "['aa', 'bb]" like string + // parse userInput to array + // handle eval exception + + try { + const userInputArray = eval(userInput); + + // replace command message $1 with userInputArray[0], $2 with userInputArray[1] and so on + for (let i = 0; i < userInputArray.length; i++) { + commandMessage = commandMessage.replace(`$${i + 1}`, userInputArray[i]); + } + } catch (error) { + logger.channel()?.error(`Failed to parse user input: ${userInput} error: ${error}`); + logger.channel()?.show(); + return ''; + } + } + + // replace ${Name} with enviroment var Name + const envVarRegex = /\${(\w+)}/g; + commandMessage = commandMessage.replace(envVarRegex, (match, p1) => { + return process.env[p1] || ''; + }); + + // build instrctions const instructions = command!.instructions - .map((instruction: string) => `[instruction|./.chat/workflows/${command.name}/${instruction}]`) + .map((instruction: string) => `[instruction|${instruction}]`) .join(' '); // 返回结果字符串 - return `${instructions} ${command!.message}`; + return `${instructions} ${commandMessage}`; } } diff --git a/test/command/customCommand.test.ts b/test/command/customCommand.test.ts index b0afdc6..d013bef 100644 --- a/test/command/customCommand.test.ts +++ b/test/command/customCommand.test.ts @@ -23,20 +23,24 @@ describe('CustomCommands', () => { // Mock the file system with two directories, one with _setting_.json and one without mockFs({ 'workflows': { - 'command1': { - '_setting_.json': JSON.stringify({ - pattern: 'command1', - description: 'Command 1', - message: 'Command 1 message', - default: false, - show: true, - instructions: ['instruction1', 'instruction2'], - }), - }, - 'command2': { - // No _setting_.json file - }, - }, + "some": { + "command": { + 'command1': { + '_setting_.json': JSON.stringify({ + pattern: 'command1', + description: 'Command 1', + message: 'Command 1 message', + default: false, + show: true, + instructions: ['instruction1', 'instruction2'], + }), + }, + 'command2': { + // No _setting_.json file + }, + } + } + } }); const workflowsDir = path.join(process.cwd(), 'workflows'); @@ -54,6 +58,7 @@ describe('CustomCommands', () => { }, ]; + expectedResult[0].instructions = [path.join(workflowsDir, 'some', 'command', 'command1', 'instruction1'), path.join(workflowsDir, 'some', 'command', 'command1', 'instruction2')]; expect(customCommands['commands']).to.deep.equal(expectedResult); }); @@ -100,7 +105,23 @@ describe('CustomCommands', () => { }; customCommands.regCommand(command); - const result = customCommands.handleCommand('test'); - expect(result).to.equal('[instruction|./.chat/workflows/test/instruction1] [instruction|./.chat/workflows/test/instruction2] Test message'); + const result = customCommands.handleCommand('test', ''); + expect(result).to.equal('[instruction|instruction1] [instruction|instruction2] Test message'); + }); + + it('should handle a custom command with args', () => { + const command: Command = { + name: 'test', + pattern: 'test {{prompt}}', + description: 'Test command', + message: 'Test message "$1","$2"', + default: false, + show: true, + instructions: ['instruction1', 'instruction2'], + }; + + customCommands.regCommand(command); + const result = customCommands.handleCommand('test', '["v1", "v2"]'); + expect(result).to.equal('[instruction|instruction1] [instruction|instruction2] Test message "v1","v2"'); }); }); \ No newline at end of file diff --git a/workflows/code/_setting_.json b/workflows/default/command/code/_setting_.json similarity index 100% rename from workflows/code/_setting_.json rename to workflows/default/command/code/_setting_.json diff --git a/workflows/code/instruct.txt b/workflows/default/command/code/instruct.txt similarity index 100% rename from workflows/code/instruct.txt rename to workflows/default/command/code/instruct.txt diff --git a/workflows/code/python.txt b/workflows/default/command/code/python.txt similarity index 100% rename from workflows/code/python.txt rename to workflows/default/command/code/python.txt diff --git a/workflows/code_actions/_setting_.json b/workflows/default/command/code_actions/_setting_.json similarity index 100% rename from workflows/code_actions/_setting_.json rename to workflows/default/command/code_actions/_setting_.json diff --git a/workflows/code_actions/instruct.txt b/workflows/default/command/code_actions/instruct.txt similarity index 100% rename from workflows/code_actions/instruct.txt rename to workflows/default/command/code_actions/instruct.txt diff --git a/workflows/commit_message/_setting_.json b/workflows/default/command/commit_message/_setting_.json similarity index 100% rename from workflows/commit_message/_setting_.json rename to workflows/default/command/commit_message/_setting_.json diff --git a/workflows/commit_message/instruct.txt b/workflows/default/command/commit_message/instruct.txt similarity index 100% rename from workflows/commit_message/instruct.txt rename to workflows/default/command/commit_message/instruct.txt