Separate VSCode from core logic for ISSUE #125

- Extract VSCode-related code from core logic.
- Improve modularity for easier unit testing.
This commit is contained in:
bobo.yang 2023-05-31 16:10:53 +08:00
parent 562a125902
commit 82faf14eb6
21 changed files with 563 additions and 673 deletions

View File

@ -9,6 +9,8 @@
"@typescript-eslint"
],
"rules": {
"no-unused-vars": "warn",
"no-unused-expressions": "warn",
"@typescript-eslint/naming-convention": "warn",
"@typescript-eslint/semi": "warn",
"curly": "warn",

View File

@ -4,6 +4,7 @@ import { createTempSubdirectory, runCommandStringAndWriteOutput } from '../util/
import { logger } from '../util/logger';
import { UiUtilWrapper } from '../util/uiUtil';
export const customCommandContext: ChatContext = {
name: '<custom command>',
description: 'custorm command',
@ -17,15 +18,15 @@ export const customCommandContext: ChatContext = {
// 检查用户是否输入了命令
if (customCommand) {
const tempDir = await createTempSubdirectory('devchat/context');
const diff_file = path.join(tempDir, 'custom.txt');
const diffFile = path.join(tempDir, 'custom.txt');
logger.channel()?.info(`custom command: ${customCommand}`);
const result = await runCommandStringAndWriteOutput(customCommand, diff_file);
const result = await runCommandStringAndWriteOutput(customCommand, diffFile);
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 `[context|${diffFile}]`;
}
return '';
},

View File

@ -1,141 +1,64 @@
import * as vscode from 'vscode';
import { sendFileSelectMessage, sendCodeSelectMessage } from './util';
import { logger } from '../util/logger';
import * as childProcess from 'child_process';
import ExtensionContextHolder from '../util/extensionContext';
import { TopicManager, Topic } from '../topic/topicManager';
import { TopicManager } from '../topic/topicManager';
import { TopicTreeDataProvider, TopicTreeItem } from '../panel/topicView';
import { FilePairManager } from '../util/diffFilePairs';
import * as process from 'process';
import { UiUtilWrapper } from '../util/uiUtil';
export function checkDevChatDependency(): boolean {
try {
// Get pipx environment
const pipxEnvOutput = childProcess.execSync('python3 -m pipx environment').toString();
const binPathRegex = /PIPX_BIN_DIR=\s*(.*)/;
// Get BIN path from pipx environment
const match = pipxEnvOutput.match(binPathRegex);
if (match && match[1]) {
const binPath = match[1];
// Add BIN path to PATH
process.env.PATH = `${binPath}:${process.env.PATH}`;
// Check if DevChat is installed
childProcess.execSync('devchat --help');
return true;
} else {
return false;
}
} catch (error) {
// DevChat dependency check failed
return false;
}
}
export async function checkOpenAiAPIKey() {
let openaiApiKey = await UiUtilWrapper.secretStorageGet("devchat_OPENAI_API_KEY");
if (!openaiApiKey) {
openaiApiKey = UiUtilWrapper.getConfiguration('DevChat', 'API_KEY');
}
if (!openaiApiKey) {
openaiApiKey = process.env.OPENAI_API_KEY;
}
if (!openaiApiKey) {
return false;
}
return true;
}
function registerOpenChatPanelCommand(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand('devchat.openChatPanel',async () => {
let disposable = vscode.commands.registerCommand('devchat.openChatPanel', async () => {
await vscode.commands.executeCommand('devchat-view.focus');
});
context.subscriptions.push(disposable);
});
context.subscriptions.push(disposable);
}
async function ensureChatPanel(context: vscode.ExtensionContext): Promise<boolean> {
await vscode.commands.executeCommand('devchat-view.focus');
return true;
await vscode.commands.executeCommand('devchat-view.focus');
return true;
}
function registerAddContextCommand(context: vscode.ExtensionContext) {
const disposableAddContext = vscode.commands.registerCommand('devchat.addConext', async (uri: { path: any; }) => {
if (!await ensureChatPanel(context)) {
return;
}
const callback = async (uri: { path: any; }) => {
if (!await ensureChatPanel(context)) {
return;
}
await sendFileSelectMessage(ExtensionContextHolder.provider?.view()!, uri.path);
});
context.subscriptions.push(disposableAddContext);
const disposableAddContextChinese = vscode.commands.registerCommand('devchat.addConext_chinese', async (uri: { path: any; }) => {
if (!await ensureChatPanel(context)) {
return;
}
await sendFileSelectMessage(ExtensionContextHolder.provider?.view()!, uri.path);
});
context.subscriptions.push(disposableAddContextChinese);
await sendFileSelectMessage(ExtensionContextHolder.provider?.view()!, uri.path);
};
context.subscriptions.push(vscode.commands.registerCommand('devchat.addConext', callback));
context.subscriptions.push(vscode.commands.registerCommand('devchat.addConext_chinese', callback));
}
function registerAskForCodeCommand(context: vscode.ExtensionContext) {
const disposableCodeContext = vscode.commands.registerCommand('devchat.askForCode', async () => {
const editor = vscode.window.activeTextEditor;
if (editor) {
if (!await ensureChatPanel(context)) {
return;
}
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);
}
});
context.subscriptions.push(disposableCodeContext);
const disposableCodeContextChinese = vscode.commands.registerCommand('devchat.askForCode_chinese', 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);
}
});
context.subscriptions.push(disposableCodeContextChinese);
const selectedText = editor.document.getText(editor.selection);
await sendCodeSelectMessage(ExtensionContextHolder.provider?.view()!, editor.document.fileName, selectedText);
}
};
context.subscriptions.push(vscode.commands.registerCommand('devchat.askForCode', callback));
context.subscriptions.push(vscode.commands.registerCommand('devchat.askForCode_chinese', callback));
}
function registerAskForFileCommand(context: vscode.ExtensionContext) {
const disposableAskFile = vscode.commands.registerCommand('devchat.askForFile', async () => {
const editor = vscode.window.activeTextEditor;
if (editor) {
if (!await ensureChatPanel(context)) {
return;
}
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(disposableAskFile);
const disposableAskFileChinese = vscode.commands.registerCommand('devchat.askForFile_chinese', async () => {
const editor = vscode.window.activeTextEditor;
if (editor) {
if (!await ensureChatPanel(context)) {
return;
}
await sendFileSelectMessage(ExtensionContextHolder.provider?.view()!, editor.document.fileName);
}
});
context.subscriptions.push(disposableAskFileChinese);
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 registerApiKeySettingCommand(context: vscode.ExtensionContext) {
@ -160,11 +83,27 @@ export function registerStatusBarItemClickCommand(context: vscode.ExtensionConte
);
}
const topicDeleteCallback = async (item: TopicTreeItem) => {
const confirm = 'Delete';
const cancel = 'Cancel';
const label = typeof item.label === 'string' ? item.label : item.label!.label;
const truncatedLabel = label.substring(0, 20) + (label.length > 20 ? '...' : '');
const result = await vscode.window.showWarningMessage(
`Are you sure you want to delete the topic "${truncatedLabel}"?`,
{ modal: true },
confirm,
cancel
);
if (result === confirm) {
TopicManager.getInstance().deleteTopic(item.id);
}
};
;
export function regTopicDeleteCommand(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('devchat-topicview.deleteTopic', (item: TopicTreeItem) => {
TopicManager.getInstance().deleteTopic(item.id);
})
vscode.commands.registerCommand('devchat-topicview.deleteTopic', topicDeleteCallback)
);
}
@ -182,7 +121,7 @@ export function regDeleteSelectTopicCommand(context: vscode.ExtensionContext) {
vscode.commands.registerCommand('devchat-topicview.deleteSelectedTopic', () => {
const selectedItem = TopicTreeDataProvider.getInstance().selectedItem;
if (selectedItem) {
TopicManager.getInstance().deleteTopic(selectedItem.id);
topicDeleteCallback(selectedItem);
} else {
vscode.window.showErrorMessage('No item selected');
}
@ -201,7 +140,7 @@ export function regSelectTopicCommand(context: vscode.ExtensionContext) {
export function regReloadTopicCommand(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('devchat-topicview.reloadTopic', async (item: TopicTreeItem) => {
vscode.commands.registerCommand('devchat-topicview.reloadTopic', async () => {
TopicManager.getInstance().loadTopics();
})
);
@ -209,7 +148,7 @@ export function regReloadTopicCommand(context: vscode.ExtensionContext) {
export function regApplyDiffResultCommand(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('devchat.applyDiffResult', async (data) => {
vscode.commands.registerCommand('devchat.applyDiffResult', async () => {
const activeEditor = vscode.window.activeTextEditor;
const fileName = activeEditor!.document.fileName;
@ -236,8 +175,8 @@ export function regApplyDiffResultCommand(context: vscode.ExtensionContext) {
}
export {
registerOpenChatPanelCommand,
registerAddContextCommand,
registerAskForCodeCommand,
registerAskForFileCommand,
registerOpenChatPanelCommand,
registerAddContextCommand,
registerAskForCodeCommand,
registerAskForFileCommand,
};

View File

@ -0,0 +1,45 @@
import * as childProcess from 'child_process';
import { UiUtilWrapper } from '../util/uiUtil';
export function checkDevChatDependency(): boolean {
try {
// Get pipx environment
const pipxEnvOutput = childProcess.execSync('python3 -m pipx environment').toString();
const binPathRegex = /PIPX_BIN_DIR=\s*(.*)/;
// Get BIN path from pipx environment
const match = pipxEnvOutput.match(binPathRegex);
if (match && match[1]) {
const binPath = match[1];
// Add BIN path to PATH
process.env.PATH = `${binPath}:${process.env.PATH}`;
// Check if DevChat is installed
childProcess.execSync('devchat --help');
return true;
} else {
return false;
}
} catch (error) {
// DevChat dependency check failed
return false;
}
}
export async function checkOpenaiApiKey() {
let openaiApiKey = await UiUtilWrapper.secretStorageGet("devchat_OPENAI_API_KEY");
if (!openaiApiKey) {
openaiApiKey = UiUtilWrapper.getConfiguration('DevChat', 'API_KEY');
}
if (!openaiApiKey) {
openaiApiKey = process.env.OPENAI_API_KEY;
}
if (!openaiApiKey) {
return false;
}
return true;
}

View File

@ -1,21 +1,18 @@
import * as vscode from 'vscode';
import {
checkOpenaiApiKey,
checkDevChatDependency,
checkDependencyPackage,
registerOpenChatPanelCommand,
registerAddContextCommand,
registerAskForCodeCommand,
registerAskForFileCommand,
registerApiKeySettingCommand,
registerStatusBarItemClickCommand,
regTopicDeleteCommand,
regAddTopicCommand,
regDeleteSelectTopicCommand,
regSelectTopicCommand,
regReloadTopicCommand,
regApplyDiffResultCommand,
registerStatusBarItemClickCommand,
} from './contributes/commands';
import { regLanguageContext } from './contributes/context';
import { regDevChatView, regTopicView } from './contributes/views';
@ -24,7 +21,7 @@ import ExtensionContextHolder from './util/extensionContext';
import { logger } from './util/logger';
import { LoggerChannelVscode } from './util/logger_vscode';
import { createStatusBarItem } from './panel/statusBarView';
import { UiUtilWrapper } from './util/uiUtil';
import { UiUtilWrapper, UiUtilVscode } from './util/uiUtil';
function activate(context: vscode.ExtensionContext) {
@ -43,200 +40,15 @@ function activate(context: vscode.ExtensionContext) {
registerAddContextCommand(context);
registerAskForCodeCommand(context);
registerAskForFileCommand(context);
registerStatusBarItemClickCommand(context);
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
createStatusBarItem(context);
// Set the status bar item properties
// const iconPath = context.asAbsolutePath(path.join('assets', 'tank.png'));
// Set the status bar item properties
statusBarItem.text = `$(warning)DevChat`;
statusBarItem.tooltip = 'DevChat checking ..., please wait.';
statusBarItem.command = '';
// add a timer to update the status bar item
let devchatStatus = '';
let apiKeyStatus = '';
let isVersionChangeCompare: boolean|undefined = undefined;
setInterval(async () => {
const versionOld = await secretStorage.get("DevChatVersionOld");
const versionNew = extensionVersion;
const versionChanged = versionOld !== versionNew;
await secretStorage.store("DevChatVersionOld", versionNew!);
// status item has three status type
// 1. not in a folder
// 2. dependence is invalid
// 3. ready
if (devchatStatus === '' || devchatStatus === 'waiting install devchat') {
let bOk = true;
let devChat: string | undefined = vscode.workspace.getConfiguration('DevChat').get('DevChatPath');
if (!devChat) {
bOk = false;
}
if (!bOk) {
bOk = checkDevChatDependency();
}
if (bOk && versionChanged && !isVersionChangeCompare) {
logger.channel()?.info(`versionOld: ${versionOld}, versionNew: ${versionNew}, versionChanged: ${versionChanged}`);
bOk = false;
}
if (bOk) {
devchatStatus = 'ready';
TopicManager.getInstance().loadTopics();
} else {
if (devchatStatus === '') {
devchatStatus = 'not ready';
}
}
}
if (devchatStatus === 'not ready') {
// auto install devchat
const terminal = vscode.window.createTerminal("DevChat Install");
terminal.sendText(`python ${context.extensionUri.fsPath + "/tools/install.py"}`);
terminal.show();
devchatStatus = 'waiting install devchat';
isVersionChangeCompare = true;
}
if (devchatStatus !== 'ready') {
statusBarItem.text = `$(warning)DevChat`;
statusBarItem.tooltip = `${devchatStatus}`;
statusBarItem.command = undefined;
// set statusBarItem warning color
return;
}
// check api key
if (apiKeyStatus === '' || apiKeyStatus === 'please set api key') {
const bOk = await checkOpenaiApiKey();
if (bOk) {
apiKeyStatus = 'ready';
} else {
apiKeyStatus = 'please set api key';
}
}
if (apiKeyStatus !== 'ready') {
statusBarItem.text = `$(warning)DevChat`;
statusBarItem.tooltip = `${apiKeyStatus}`;
statusBarItem.command = 'DevChat.OPENAI_API_KEY';
return;
}
statusBarItem.text = `$(pass)DevChat`;
statusBarItem.tooltip = `ready to chat`;
statusBarItem.command = 'devcaht.onStatusBarClick';
}, 3000);
// Add the status bar item to the status bar
statusBarItem.show();
context.subscriptions.push(statusBarItem);
// Register the command
context.subscriptions.push(
vscode.commands.registerCommand('devcaht.onStatusBarClick', async () => {
await vscode.commands.executeCommand('devchat-view.focus');
})
);
ExtensionContextHolder.provider = new DevChatViewProvider(context);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider('devchat-view', ExtensionContextHolder.provider, {
webviewOptions: { retainContextWhenHidden: true }
})
);
const yourTreeDataProvider = new TopicTreeDataProvider();
const yourTreeView = vscode.window.createTreeView('devchat-topicview', {
treeDataProvider: yourTreeDataProvider,
});
context.subscriptions.push(yourTreeView);
const topicDeleteCallback = async (item: TopicTreeItem) => {
const confirm = 'Delete';
const cancel = 'Cancel';
const label = typeof item.label === 'string' ? item.label : item.label!.label;
const truncatedLabel = label.substring(0, 20) + (label.length > 20 ? '...' : '');
const result = await vscode.window.showWarningMessage(
`Are you sure you want to delete the topic "${truncatedLabel}"?`,
{ modal: true },
confirm,
cancel
);
if (result === confirm) {
TopicManager.getInstance().deleteTopic(item.id);
}
};
vscode.commands.registerCommand('devchat-topicview.deleteTopic', topicDeleteCallback);
context.subscriptions.push(
vscode.languages.registerCodeActionsProvider(
{ pattern: '**', scheme: 'file' },
{
provideCodeActions: (document, range, context, token) => {
const deleteAction = new vscode.CodeAction('Delete Item', vscode.CodeActionKind.QuickFix);
deleteAction.command = {
title: 'Delete Item',
command: 'devchat-topicview.deleteTopic',
arguments: [context.diagnostics[0].code],
};
return [deleteAction];
},
},
{ providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }
)
);
vscode.commands.registerCommand('devchat-topicview.addTopic', () => {
const topic = TopicManager.getInstance().createTopic();
TopicManager.getInstance().setCurrentTopic(topic.topicId);
});
vscode.commands.registerCommand('devchat-topicview.deleteSelectedTopic', () => {
const selectedItem = yourTreeDataProvider.selectedItem;
if (selectedItem) {
topicDeleteCallback(selectedItem);
} else {
vscode.window.showErrorMessage('No item selected');
}
});
vscode.commands.registerCommand('devchat-topicview.selectTopic', (item: TopicTreeItem) => {
yourTreeDataProvider.setSelectedItem(item);
TopicManager.getInstance().setCurrentTopic(item.id);
});
vscode.commands.registerCommand('devchat-topicview.reloadTopic', async (item: TopicTreeItem) => {
TopicManager.getInstance().loadTopics();
});
context.subscriptions.push(
vscode.commands.registerCommand('devchat.applyDiffResult', async (data) => {
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);
// 将右边文档的内容替换到左边文档
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();
} else {
vscode.window.showErrorMessage('No file to apply diff result.');
}
})
);
regTopicDeleteCommand(context);
regAddTopicCommand(context);
regDeleteSelectTopicCommand(context);
regSelectTopicCommand(context);
regReloadTopicCommand(context);
regApplyDiffResultCommand(context);
}
exports.activate = activate;

View File

@ -1,141 +1,19 @@
import * as vscode from 'vscode';
import DevChat, { LogOptions, LogEntry } from '../toolwrapper/devchat';
import { MessageHandler } from './messageHandler';
import messageHistory from '../util/messageHistory';
import { regInMessage, regOutMessage } from '../util/reg_messages';
import { checkOpenaiApiKey } from '../contributes/commands';
import ExtensionContextHolder from '../util/extensionContext';
import { TopicManager } from '../topic/topicManager';
import { historyMessagesBase, onApiKeyBase } from './historyMessagesBase';
let isApiSet: boolean | undefined = undefined;
interface LoadHistoryMessages {
command: string;
entries: Array<LogEntry>;
}
function welcomeMessage(): LogEntry {
// create default logEntry to show welcome message
return {
hash: 'message',
parent: '',
user: 'system',
date: '',
request: 'How do I use DevChat?',
response: `
Do you want to write some code or have a question about the project? Simply right-click on your chosen files or code snippets and add them to DevChat. Feel free to ask me anything or let me help you with coding.
Don't forget to check out the "+" button on the left of the input to add more context. To see a list of workflows you can run in the context, just type "/". Happy prompting!
`,
context: []
} as LogEntry;
}
function apiKeyMissedMessage(): LogEntry {
// create default logEntry to show welcome message
return {
hash: 'message',
parent: '',
user: 'system',
date: '',
request: 'Is OPENAI_API_KEY ready?',
response: `
OPENAI_API_KEY is missing from your environment or settings. Kindly input your OpenAI or DevChat key, and I'll ensure DevChat is all set for you.
`,
context: []
} as LogEntry;
}
regInMessage({command: 'historyMessages', options: { skip: 0, maxCount: 0 }});
regOutMessage({command: 'loadHistoryMessages', entries: [{hash: '',user: '',date: '',request: '',response: '',context: [{content: '',role: ''}]}]});
export async function historyMessages(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
const topicId = TopicManager.getInstance().currentTopicId;
let logEntriesFlat: Array<LogEntry> = [];
if (topicId) {
logEntriesFlat = await TopicManager.getInstance().getTopicHistory(topicId);
}
messageHistory.clear();
// TODO handle context
const logEntriesFlatFiltered = logEntriesFlat.map((entry) => {
return {
date: entry.date,
hash: entry.hash,
request: entry.request,
text: entry.response,
user: entry.user,
parentHash: '',
};
});
for (let i = 0; i < logEntriesFlat.length; i++) {
let entryOld = logEntriesFlat[i];
let entryNew = {
date: entryOld.date,
hash: entryOld.hash,
request: entryOld.request,
text: entryOld.response,
user: entryOld.user,
parentHash: '',
};
if (i > 0) {
entryNew.parentHash = logEntriesFlat[i - 1].hash;
}
messageHistory.add(entryNew);
}
const isApiKeyReady = await checkOpenaiApiKey();
isApiSet = true;
if (!isApiKeyReady) {
const startMessage = [ apiKeyMissedMessage() ];
isApiSet = false;
MessageHandler.sendMessage(panel, {
command: 'loadHistoryMessages',
entries: startMessage,
} as LoadHistoryMessages);
return;
}
const loadHistoryMessages: LoadHistoryMessages = {
command: 'loadHistoryMessages',
entries: logEntriesFlat.length>0? logEntriesFlat : [welcomeMessage()],
};
MessageHandler.sendMessage(panel, loadHistoryMessages);
return;
}
export function isValidApiKey(apiKey: string) {
let apiKeyStrim = apiKey.trim();
if (apiKeyStrim.indexOf('sk-') !== 0 && apiKeyStrim.indexOf('DC.') !== 0) {
return false;
}
return true;
}
export async function isWaitForApiKey() {
if (isApiSet === undefined) {
isApiSet = await checkOpenaiApiKey();
}
return !isApiSet;
const historyMessage = historyMessagesBase();
MessageHandler.sendMessage(panel, historyMessage);
}
export async function onApiKey(apiKey: string, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
if (!isValidApiKey(apiKey)) {
MessageHandler.sendMessage(panel, { command: 'receiveMessage', text: 'Your API key is invalid. We support OpenAI and DevChat keys. Please reset the key.', hash: '', user: 'system', date: '', isError: false });
return;
}
isApiSet = true;
const secretStorage: vscode.SecretStorage = ExtensionContextHolder.context?.secrets!;
secretStorage.store("devchat_OPENAI_API_KEY", apiKey);
const welcomeMessageText = welcomeMessage().response;
MessageHandler.sendMessage(panel, { command: 'receiveMessage', text: `Your OPENAI_API_KEY is set. Enjoy DevChat!\n${welcomeMessageText}`, hash: '', user: 'system', date: '', isError: false });
const resMessage = await onApiKeyBase(apiKey);
MessageHandler.sendMessage(panel, resMessage);
}

View File

@ -0,0 +1,134 @@
import { checkOpenaiApiKey } from '../contributes/commandsBase';
import { TopicManager } from '../topic/topicManager';
import { LogEntry } from '../toolwrapper/devchat';
import messageHistory from '../util/messageHistory';
import { UiUtilWrapper } from '../util/uiUtil';
let isApiSet: boolean | undefined = undefined;
interface LoadHistoryMessages {
command: string;
entries: Array<LogEntry>;
}
function welcomeMessage(): LogEntry {
// create default logEntry to show welcome message
return {
hash: 'message',
parent: '',
user: 'system',
date: '',
request: 'How do I use DevChat?',
response: `
Do you want to write some code or have a question about the project? Simply right-click on your chosen files or code snippets and add them to DevChat. Feel free to ask me anything or let me help you with coding.
Don't forget to check out the "+" button on the left of the input to add more context. To see a list of workflows you can run in the context, just type "/". Happy prompting!
`,
context: []
} as LogEntry;
}
function apiKeyMissedMessage(): LogEntry {
// create default logEntry to show welcome message
return {
hash: 'message',
parent: '',
user: 'system',
date: '',
request: 'Is OPENAI_API_KEY ready?',
response: `
OPENAI_API_KEY is missing from your environment or settings. Kindly input your OpenAI or DevChat key, and I'll ensure DevChat is all set for you.
`,
context: []
} as LogEntry;
}
export function isValidApiKey(apiKey: string) {
let apiKeyStrim = apiKey.trim();
if (apiKeyStrim.indexOf('sk-') !== 0 && apiKeyStrim.indexOf('DC.') !== 0) {
return false;
}
return true;
}
export async function isWaitForApiKey() {
if (isApiSet === undefined) {
isApiSet = await checkOpenaiApiKey();
}
return !isApiSet;
}
export async function loadTopicHistoryLogs() : Promise<Array<LogEntry>> {
const topicId = TopicManager.getInstance().currentTopicId;
let logEntriesFlat: Array<LogEntry> = [];
if (topicId) {
logEntriesFlat = await TopicManager.getInstance().getTopicHistory(topicId);
}
return logEntriesFlat;
}
export function updateCurrentMessageHistory(logEntries: Array<LogEntry>): void {
messageHistory.clear();
for (let i = 0; i < logEntries.length; i++) {
let entryOld = logEntries[i];
let entryNew = {
date: entryOld.date,
hash: entryOld.hash,
request: entryOld.request,
text: entryOld.response,
user: entryOld.user,
parentHash: '',
};
if (i > 0) {
entryNew.parentHash = logEntries[i - 1].hash;
}
messageHistory.add(entryNew);
}
}
export async function apiKeyInvalidMessage(): Promise<LoadHistoryMessages|undefined> {
const isApiKeyReady = await checkOpenaiApiKey();
isApiSet = true;
if (!isApiKeyReady) {
const startMessage = [ apiKeyMissedMessage() ];
isApiSet = false;
return {
command: 'loadHistoryMessages',
entries: startMessage,
} as LoadHistoryMessages;
} else {
return undefined;
}
}
export async function historyMessagesBase(): Promise<LoadHistoryMessages> {
const logEntriesFlat = await loadTopicHistoryLogs();
updateCurrentMessageHistory(logEntriesFlat);
const apiKeyMessage = await apiKeyInvalidMessage();
if (apiKeyMessage) {
return apiKeyMessage;
}
return {
command: 'loadHistoryMessages',
entries: logEntriesFlat.length>0? logEntriesFlat : [welcomeMessage()],
} as LoadHistoryMessages;
}
export async function onApiKeyBase(apiKey: string): Promise<{command: string, text: string, hash: string, user: string, date: string, isError: boolean}> {
if (!isValidApiKey(apiKey)) {
return { command: 'receiveMessage', text: 'Your API key is invalid. We support OpenAI and DevChat keys. Please reset the key.', hash: '', user: 'system', date: '', isError: false };
}
isApiSet = true;
UiUtilWrapper.storeSecret("devchat_OPENAI_API_KEY", apiKey);
const welcomeMessageText = welcomeMessage().response;
return { command: 'receiveMessage', text: `Your OPENAI_API_KEY is set. Enjoy DevChat!\n${welcomeMessageText}`, hash: '', user: 'system', date: '', isError: false };
}

View File

@ -5,9 +5,9 @@ import * as vscode from 'vscode';
import '../command/loadCommands';
import '../context/loadContexts';
import { logger } from '../util/logger';
import { on } from 'events';
import { isWaitForApiKey, onApiKey } from './historyMessages';
import { checkOpenaiApiKey } from '../contributes/commands';
import { isWaitForApiKey } from './historyMessagesBase';
import { onApiKey } from './historyMessages';
import { checkOpenaiApiKey } from '../contributes/commandsBase';
export class MessageHandler {
@ -20,6 +20,8 @@ export class MessageHandler {
this.handlers[command] = handler;
}
async handleMessage(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
let isNeedSendResponse = false;
if (message.command === 'sendMessage') {

View File

@ -1,76 +1,12 @@
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import DevChat, { ChatResponse } from '../toolwrapper/devchat';
import CommandManager from '../command/commandManager';
import { logger } from '../util/logger';
import { MessageHandler } from './messageHandler';
import messageHistory from '../util/messageHistory';
import CustomCommands from '../command/customCommand';
import { regInMessage, regOutMessage } from '../util/reg_messages';
import { TopicManager } from '../topic/topicManager';
import { stopDevChatBase, sendMessageBase } from './sendMessageBase';
let _lastMessage: any = undefined;
// Add this function to messageHandler.ts
function parseMessage(message: string): { context: string[]; instruction: string[]; reference: string[]; text: string } {
const contextRegex = /\[context\|(.*?)\]/g;
const instructionRegex = /\[instruction\|(.*?)\]/g;
const referenceRegex = /\[reference\|(.*?)\]/g;
const contextPaths = [];
const instructionPaths = [];
const referencePaths = [];
let match;
// 提取 context
while ((match = contextRegex.exec(message)) !== null) {
contextPaths.push(match[1]);
}
// 提取 instruction
while ((match = instructionRegex.exec(message)) !== null) {
instructionPaths.push(match[1]);
}
// 提取 reference
while ((match = referenceRegex.exec(message)) !== null) {
referencePaths.push(match[1]);
}
// 移除标签,保留纯文本
const text = message
.replace(contextRegex, '')
.replace(instructionRegex, '')
.replace(referenceRegex, '')
.trim();
return { context: contextPaths, instruction: instructionPaths, reference: referencePaths, text };
}
function getInstructionFiles(): string[] {
const instructionFiles: string[] = [];
const customCommands = CustomCommands.getInstance().getCommands();
// visit customCommands, get default command
for (const command of customCommands) {
if (command.default) {
for (const instruction of command.instructions) {
instructionFiles.push(`./.chat/workflows/${command.name}/${instruction}`);
}
}
}
return instructionFiles;
}
const devChat = new DevChat();
let userStop = false;
regInMessage({command: 'sendMessage', text: '', hash: undefined});
regOutMessage({ command: 'receiveMessage', text: 'xxxx', hash: 'xxx', user: 'xxx', date: 'xxx'});
regOutMessage({ command: 'receiveMessagePartial', text: 'xxxx', user: 'xxx', date: 'xxx'});
@ -81,72 +17,12 @@ regOutMessage({ command: 'receiveMessagePartial', text: 'xxxx', user: 'xxx', dat
export async function sendMessage(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
_lastMessage = message;
const newText2 = await CommandManager.getInstance().processText(message.text);
const parsedMessage = parseMessage(newText2);
const chatOptions: any = {};
let parentHash = undefined;
logger.channel()?.info(`request message hash: ${message.hash}`);
if (message.hash) {
const hmessage = messageHistory.find(message.hash);
parentHash = hmessage ? message.parentHash : undefined;
} else {
const hmessage = messageHistory.findLast();
parentHash = hmessage ? hmessage.hash : undefined;
const responseMessage = await sendMessageBase(message, (data: { command: string, text: string, user: string, date: string}) => {
MessageHandler.sendMessage(panel, data);
});
if (responseMessage) {
MessageHandler.sendMessage(panel, responseMessage);
}
if (parentHash) {
chatOptions.parent = parentHash;
}
logger.channel()?.info(`parent hash: ${parentHash}`);
if (parsedMessage.context.length > 0) {
chatOptions.context = parsedMessage.context;
}
chatOptions.header = getInstructionFiles();
if (parsedMessage.instruction.length > 0) {
chatOptions.header = parsedMessage.instruction;
}
if (parsedMessage.reference.length > 0) {
chatOptions.reference = parsedMessage.reference;
}
let partialDataText = '';
const onData = (partialResponse: ChatResponse) => {
const responseText = partialResponse.response.replace(/```\ncommitmsg/g, "```commitmsg");
partialDataText = responseText;
MessageHandler.sendMessage(panel, { command: 'receiveMessagePartial', text: responseText, user: partialResponse.user, date: partialResponse.date }, false);
};
const chatResponse = await devChat.chat(parsedMessage.text, chatOptions, onData);
if (!chatResponse.isError) {
messageHistory.add({request: message.text, text: chatResponse.response, parentHash, hash: chatResponse['prompt-hash'], user: chatResponse.user, date: chatResponse.date });
let topicId = TopicManager.getInstance().currentTopicId;
if (!topicId) {
// create new topic
const topic = TopicManager.getInstance().createTopic();
topicId = topic.topicId;
}
TopicManager.getInstance().updateTopic(topicId!, chatResponse['prompt-hash'], Number(chatResponse.date), message.text, chatResponse.response);
}
let responseText = chatResponse.response.replace(/```\ncommitmsg/g, "```commitmsg");
if (userStop) {
userStop = false;
if (responseText.indexOf('Exit code: undefined') >= 0) {
return;
}
}
if (chatResponse.isError) {
responseText = partialDataText + responseText;
}
MessageHandler.sendMessage(panel, { command: 'receiveMessage', text: responseText, hash: chatResponse['prompt-hash'], user: chatResponse.user, date: chatResponse.date, isError: chatResponse.isError });
return;
}
// regeneration last message again
@ -160,9 +36,7 @@ export async function regeneration(message: any, panel: vscode.WebviewPanel|vsco
regInMessage({command: 'stopDevChat'});
export async function stopDevChat(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
logger.channel()?.info(`Stopping devchat`);
userStop = true;
devChat.stop();
stopDevChatBase(message);
}

View File

@ -0,0 +1,160 @@
import DevChat, { ChatResponse } from '../toolwrapper/devchat';
import CommandManager from '../command/commandManager';
import { logger } from '../util/logger';
import messageHistory from '../util/messageHistory';
import { TopicManager } from '../topic/topicManager';
import CustomCommands from '../command/customCommand';
// Add this function to messageHandler.ts
function parseMessage(message: string): { context: string[]; instruction: string[]; reference: string[]; text: string } {
const contextRegex = /\[context\|(.*?)\]/g;
const instructionRegex = /\[instruction\|(.*?)\]/g;
const referenceRegex = /\[reference\|(.*?)\]/g;
const contextPaths = [];
const instructionPaths = [];
const referencePaths = [];
let match;
// 提取 context
while ((match = contextRegex.exec(message)) !== null) {
contextPaths.push(match[1]);
}
// 提取 instruction
while ((match = instructionRegex.exec(message)) !== null) {
instructionPaths.push(match[1]);
}
// 提取 reference
while ((match = referenceRegex.exec(message)) !== null) {
referencePaths.push(match[1]);
}
// 移除标签,保留纯文本
const text = message
.replace(contextRegex, '')
.replace(instructionRegex, '')
.replace(referenceRegex, '')
.trim();
return { context: contextPaths, instruction: instructionPaths, reference: referencePaths, text };
}
function getInstructionFiles(): string[] {
const instructionFiles: string[] = [];
const customCommands = CustomCommands.getInstance().getCommands();
// visit customCommands, get default command
for (const command of customCommands) {
if (command.default) {
for (const instruction of command.instructions) {
instructionFiles.push(`./.chat/workflows/${command.name}/${instruction}`);
}
}
}
return instructionFiles;
}
const devChat = new DevChat();
let userStop = false;
// 将解析消息的部分提取到一个单独的函数中
async function parseMessageAndSetOptions(message: any, chatOptions: any): Promise<{ context: string[]; instruction: string[]; reference: string[]; text: string }> {
const newText2 = await CommandManager.getInstance().processText(message.text);
const parsedMessage = parseMessage(newText2);
if (parsedMessage.context.length > 0) {
chatOptions.context = parsedMessage.context;
}
chatOptions.header = getInstructionFiles();
if (parsedMessage.instruction.length > 0) {
chatOptions.header = parsedMessage.instruction;
}
if (parsedMessage.reference.length > 0) {
chatOptions.reference = parsedMessage.reference;
}
return parsedMessage;
}
// 将处理父哈希的部分提取到一个单独的函数中
function getParentHash(message: any) {
let parentHash = undefined;
logger.channel()?.info(`request message hash: ${message.hash}`);
if (message.hash) {
const hmessage = messageHistory.find(message.hash);
parentHash = hmessage ? message.parentHash : undefined;
} else {
const hmessage = messageHistory.findLast();
parentHash = hmessage ? hmessage.hash : undefined;
}
logger.channel()?.info(`parent hash: ${parentHash}`);
return parentHash;
}
export async function handleTopic(parentHash:string, message: any, chatResponse: ChatResponse) {
if (!chatResponse.isError) {
messageHistory.add({ request: message.text, text: chatResponse.response, parentHash, hash: chatResponse['prompt-hash'], user: chatResponse.user, date: chatResponse.date });
let topicId = TopicManager.getInstance().currentTopicId;
if (!topicId) {
// create new topic
const topic = TopicManager.getInstance().createTopic();
topicId = topic.topicId;
}
TopicManager.getInstance().updateTopic(topicId!, chatResponse['prompt-hash'], Number(chatResponse.date), message.text, chatResponse.response);
}
}
export async function handlerResponseText(partialDataText: string, chatResponse: ChatResponse) : Promise<string|undefined> {
let responseText = chatResponse.response.replace(/```\ncommitmsg/g, "```commitmsg");
if (userStop) {
userStop = false;
if (responseText.indexOf('Exit code: undefined') >= 0) {
return undefined;
}
}
if (chatResponse.isError) {
responseText = partialDataText + responseText;
}
return responseText;
}
// 重构后的sendMessage函数
export async function sendMessageBase(message: any, handlePartialData: (data: { command: string, text: string, user: string, date: string}) => void): Promise<{ command: string, text: string, hash: string, user: string, date: string, isError: boolean }|undefined> {
const chatOptions: any = {};
const parsedMessage = await parseMessageAndSetOptions(message, chatOptions);
const parentHash = getParentHash(message);
if (parentHash) {
chatOptions.parent = parentHash;
}
let partialDataText = '';
const onData = (partialResponse: ChatResponse) => {
partialDataText = partialResponse.response.replace(/```\ncommitmsg/g, "```commitmsg");
handlePartialData({ command: 'receiveMessagePartial', text: partialDataText!, user: partialResponse.user, date: partialResponse.date });
};
const chatResponse = await devChat.chat(parsedMessage.text, chatOptions, onData);
await handleTopic(parentHash!, message, chatResponse);
const responseText = await handlerResponseText(partialDataText, chatResponse);
if (responseText === undefined) {
return;
}
return { command: 'receiveMessage', text: responseText, hash: chatResponse['prompt-hash'], user: chatResponse.user, date: chatResponse.date, isError: chatResponse.isError };
}
export async function stopDevChatBase(message: any): Promise<void> {
logger.channel()?.info(`Stopping devchat`);
userStop = true;
devChat.stop();
}

View File

@ -1,9 +1,9 @@
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import * as ncp from 'ncp';
import { logger } from '../util/logger';
import { UiUtilWrapper } from '../util/uiUtil';
function copyFileSync(source: string, target: string) {
@ -32,13 +32,11 @@ function copyFileSync(source: string, target: string) {
}
export function createChatDirectoryAndCopyInstructionsSync(extensionUri: vscode.Uri) {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders) {
const workspaceRoot = UiUtilWrapper.workspaceFoldersFirstPath();
if (!workspaceRoot) {
return;
}
const workspaceRoot = workspaceFolders[0].uri.fsPath;
const chatWorkflowsDirPath = path.join(workspaceRoot, '.chat', 'workflows');
const instructionsSrcPath = path.join(extensionUri.fsPath, 'workflows');

View File

@ -9,6 +9,7 @@ import WebviewManager from './webviewManager';
import CustomCommands from '../command/customCommand';
import CommandManager from '../command/commandManager';
import { createChatDirectoryAndCopyInstructionsSync } from '../init/chatConfig';
import { UiUtilWrapper } from '../util/uiUtil';
export default class ChatPanel {
private static _instance: ChatPanel | undefined;

View File

@ -8,6 +8,7 @@ import { createChatDirectoryAndCopyInstructionsSync } from '../init/chatConfig';
import ExtensionContextHolder from '../util/extensionContext';
import CustomCommands from '../command/customCommand';
import { TopicManager } from '../topic/topicManager';
import { UiUtilWrapper } from '../util/uiUtil';
export class DevChatViewProvider implements vscode.WebviewViewProvider {

View File

@ -1,20 +1,8 @@
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { checkOpenAiAPIKey, checkDevChatDependency } from '../contributes/commands';
import { logger } from '../util/logger';
import { TopicManager } from '../topic/topicManager';
import { dependencyCheck } from './statusBarViewBase';
function getExtensionVersion(context: vscode.ExtensionContext): string {
const packageJsonPath = path.join(context.extensionUri.fsPath, 'package.json');
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
const packageJson = JSON.parse(packageJsonContent);
return packageJson.version;
}
export function createStatusBarItem(context: vscode.ExtensionContext): vscode.StatusBarItem {
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
@ -24,70 +12,16 @@ export function createStatusBarItem(context: vscode.ExtensionContext): vscode.St
statusBarItem.command = '';
// add a timer to update the status bar item
let devchatStatus = '';
let apiKeyStatus = '';
const extensionVersion = getExtensionVersion(context);
const secretStorage: vscode.SecretStorage = context.secrets;
setInterval(async () => {
const versionOld = await secretStorage.get("devchat_version_old");
const versionNew = extensionVersion;
const versionChanged = versionOld !== versionNew;
secretStorage.store("devchat_version_old", versionNew!);
// status item has three status type
// 1. not in a folder
// 2. dependence is invalid
// 3. ready
if (devchatStatus === '' || devchatStatus === 'waiting install devchat') {
let bOk = true;
let devChat: string | undefined = UiUtilWrapper.getConfiguration('DevChat', 'DevChatPath');
if (!devChat) {
bOk = false;
}
if (!bOk) {
bOk = checkDevChatDependency();
}
if (bOk && versionChanged) {
bOk = false;
}
if (bOk) {
devchatStatus = 'ready';
TopicManager.getInstance().loadTopics();
} else {
if (devchatStatus === '') {
devchatStatus = 'not ready';
}
}
}
if (devchatStatus === 'not ready') {
// auto install devchat
const terminal = vscode.window.createTerminal("DevChat Install");
terminal.sendText(`python ${context.extensionUri.fsPath + "/tools/install.py"}`);
terminal.show();
devchatStatus = 'waiting install devchat';
}
setInterval(async () => {
const [devchatStatus, apiKeyStatus] = await dependencyCheck();
if (devchatStatus !== 'ready') {
statusBarItem.text = `$(warning)DevChat`;
statusBarItem.tooltip = `${devchatStatus}`;
statusBarItem.command = '';
statusBarItem.command = undefined;
// set statusBarItem warning color
return;
}
// check api key
if (apiKeyStatus === '' || apiKeyStatus === 'please set api key') {
const bOk = await checkOpenAiAPIKey();
if (bOk) {
apiKeyStatus = 'ready';
} else {
apiKeyStatus = 'please set api key';
}
}
if (apiKeyStatus !== 'ready') {
statusBarItem.text = `$(warning)DevChat`;
statusBarItem.tooltip = `${apiKeyStatus}`;

View File

@ -0,0 +1,81 @@
import * as fs from 'fs';
import * as path from 'path';
import { logger } from "../util/logger";
import { UiUtilWrapper } from "../util/uiUtil";
import { TopicManager } from "../topic/topicManager";
import { checkDevChatDependency } from "../contributes/commandsBase";
import { checkOpenaiApiKey } from "../contributes/commandsBase";
function getExtensionVersion(): string {
const packageJsonPath = path.join(UiUtilWrapper.extensionPath(), 'package.json');
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
const packageJson = JSON.parse(packageJsonContent);
return packageJson.version;
}
let devchatStatus = '';
let apiKeyStatus = '';
let isVersionChangeCompare: boolean|undefined = undefined;
export async function dependencyCheck(): Promise<[string, string]> {
let versionChanged = false;
if (isVersionChangeCompare === undefined) {
const versionOld = await UiUtilWrapper.secretStorageGet("DevChatVersionOld");
const versionNew = getExtensionVersion();
const versionChanged = versionOld !== versionNew;
UiUtilWrapper.storeSecret("DevChatVersionOld", versionNew!);
isVersionChangeCompare = true;
logger.channel()?.info(`versionOld: ${versionOld}, versionNew: ${versionNew}, versionChanged: ${versionChanged}`);
}
// status item has three status type
// 1. not in a folder
// 2. dependence is invalid
// 3. ready
if (devchatStatus === '' || devchatStatus === 'waiting install devchat') {
let bOk = true;
let devChat: string | undefined = UiUtilWrapper.getConfiguration('DevChat', 'DevChatPath');
if (!devChat) {
bOk = false;
}
if (!bOk) {
bOk = checkDevChatDependency();
}
if (bOk && versionChanged) {
bOk = false;
}
if (bOk) {
devchatStatus = 'ready';
TopicManager.getInstance().loadTopics();
} else {
if (devchatStatus === '') {
devchatStatus = 'not ready';
}
}
}
if (devchatStatus === 'not ready') {
// auto install devchat
UiUtilWrapper.runTerminal('DevChat Install', `python ${UiUtilWrapper.extensionPath() + "/tools/install.py"}`);
devchatStatus = 'waiting install devchat';
isVersionChangeCompare = true;
}
// check api key
if (apiKeyStatus === '' || apiKeyStatus === 'please set api key') {
const bOk = await checkOpenaiApiKey();
if (bOk) {
apiKeyStatus = 'ready';
} else {
apiKeyStatus = 'please set api key';
}
}
return [devchatStatus, apiKeyStatus];
}

View File

@ -84,7 +84,7 @@ class DevChat {
return args;
}
async getOpenAiApiKey(): Promise<string | undefined> {
async getOpenaiApiKey(): Promise<string | undefined> {
let openaiApiKey = await UiUtilWrapper.secretStorageGet("devchat_OPENAI_API_KEY");
if (!openaiApiKey) {
openaiApiKey = UiUtilWrapper.getConfiguration('DevChat', 'API_KEY');
@ -153,8 +153,9 @@ class DevChat {
openAiApiBase = "https://xw4ymuy6qj.ap-southeast-1.awsapprunner.com/api/v1";
}
if (vscode.workspace.getConfiguration('DevChat').get('API_ENDPOINT')) {
openAiApiBase = vscode.workspace.getConfiguration('DevChat').get('API_ENDPOINT');
if (UiUtilWrapper.getConfiguration('DevChat', 'API_ENDPOINT')) {
openAiApiBase = UiUtilWrapper.getConfiguration('DevChat', 'API_ENDPOINT');
}
const openAiApiBaseObject = openAiApiBase ? { OPENAI_API_BASE: openAiApiBase } : {};

View File

@ -4,6 +4,7 @@ import * as fs from 'fs';
import { logger } from "../util/logger";
import { CommandRun } from "../util/commonUtil";
import { UiUtilWrapper } from "../util/uiUtil";
interface DtmResponse {
status: number;

View File

@ -5,6 +5,7 @@ import * as fs from 'fs';
import DevChat, { LogEntry, LogOptions } from '../toolwrapper/devchat';
import { loadTopicList } from './loadTopics';
import { UiUtilWrapper } from '../util/uiUtil';
export class Topic {
name: string | undefined;

View File

@ -1,3 +1,4 @@
import ExtensionContextHolder from './extensionContext';
export interface UiUtil {
languageId(uri: string): Promise<string>;
@ -6,6 +7,9 @@ export interface UiUtil {
secretStorageGet(key: string): Promise<string | undefined>;
writeFile(uri: string, content: string): Promise<void>;
showInputBox(option: object): Promise<string | undefined>;
storeSecret(key: string, value: string): Promise<void>;
extensionPath(): string;
runTerminal(terminalName:string, command: string): void;
}
@ -31,10 +35,19 @@ export class UiUtilVscode implements UiUtil {
vscode.workspace.fs.writeFile(vscode.Uri.file(uri), Buffer.from(content));
}
public async showInputBox(option: object): Promise<string | undefined> {
return vscode.window.showInputBox({
prompt: 'Input your custom command',
placeHolder: 'for example: ls -l'
});
return vscode.window.showInputBox(option);
}
public async storeSecret(key: string, value: string): Promise<void> {
const secretStorage: vscode.SecretStorage = ExtensionContextHolder.context!.secrets;
await secretStorage.store(key, value);
}
public extensionPath(): string {
return ExtensionContextHolder.context!.extensionUri.fsPath;
}
public runTerminal(terminalName: string, command: string): void {
const terminal = vscode.window.createTerminal(terminalName);
terminal.sendText(command);
terminal.show();
}
}
@ -62,4 +75,14 @@ export class UiUtilWrapper {
public static async showInputBox(option: object): Promise<string | undefined> {
return this._uiUtil?.showInputBox(option);
}
}
public static async storeSecret(key: string, value: string): Promise<void> {
return this._uiUtil?.storeSecret(key, value);
}
public static extensionPath(): string {
return this._uiUtil?.extensionPath()!;
}
public static runTerminal(terminalName: string, command: string): void {
this._uiUtil?.runTerminal(terminalName, command);
}
}

View File

View File

@ -12,7 +12,9 @@
"rootDir": "src",
"strict": true,
"jsx": "react",
"esModuleInterop": true
"esModuleInterop": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
},
"exclude": ["test"]
}