2023-05-04 07:27:26 +08:00
|
|
|
import * as vscode from 'vscode';
|
2023-05-19 20:07:34 +08:00
|
|
|
import * as fs from 'fs';
|
2023-05-10 18:30:17 +08:00
|
|
|
|
2023-05-05 21:27:40 +08:00
|
|
|
import {
|
2023-05-28 08:32:12 +08:00
|
|
|
checkOpenaiApiKey,
|
2023-05-16 14:24:24 +08:00
|
|
|
checkDevChatDependency,
|
2023-05-11 10:27:54 +08:00
|
|
|
checkDependencyPackage,
|
|
|
|
registerOpenChatPanelCommand,
|
|
|
|
registerAddContextCommand,
|
|
|
|
registerAskForCodeCommand,
|
|
|
|
registerAskForFileCommand,
|
2023-05-05 21:27:40 +08:00
|
|
|
} from './contributes/commands';
|
2023-04-27 14:07:46 +08:00
|
|
|
|
2023-05-05 21:27:40 +08:00
|
|
|
import ExtensionContextHolder from './util/extensionContext';
|
2023-05-09 08:52:07 +08:00
|
|
|
import { logger } from './util/logger';
|
2023-05-16 14:35:01 +08:00
|
|
|
import { DevChatViewProvider } from './panel/devchatView';
|
|
|
|
import path from 'path';
|
2023-05-18 15:25:46 +08:00
|
|
|
import { FilePairManager } from './util/diffFilePairs';
|
2023-05-24 13:04:15 +08:00
|
|
|
import { Topic, TopicManager } from './topic/topicManager';
|
2023-05-16 14:35:01 +08:00
|
|
|
|
2023-05-04 11:50:45 +08:00
|
|
|
|
2023-05-24 13:04:15 +08:00
|
|
|
class TopicTreeItem extends vscode.TreeItem {
|
|
|
|
id: string;
|
|
|
|
date: number | undefined;
|
|
|
|
constructor(label: string, id: string, date: number | undefined, collapsibleState: vscode.TreeItemCollapsibleState) {
|
|
|
|
super(label, collapsibleState);
|
|
|
|
this.id = id;
|
|
|
|
this.date = date;
|
|
|
|
this.iconPath = new vscode.ThemeIcon('symbol-variable');
|
|
|
|
this.contextValue = 'yourTreeItem'; // 添加这一行
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class TopicTreeDataProvider implements vscode.TreeDataProvider<TopicTreeItem> {
|
|
|
|
private _onDidChangeTreeData: vscode.EventEmitter<TopicTreeItem | undefined | null | void> = new vscode.EventEmitter<TopicTreeItem | undefined | null | void>();
|
|
|
|
readonly onDidChangeTreeData: vscode.Event<TopicTreeItem | undefined | null | void> = this._onDidChangeTreeData.event;
|
|
|
|
|
|
|
|
public selectedItem: TopicTreeItem | null = null;
|
|
|
|
private items: TopicTreeItem[] = [];
|
|
|
|
|
|
|
|
// reg listeners to TopicManager in constructor
|
|
|
|
constructor() {
|
|
|
|
TopicManager.getInstance().addOnCreateTopicListener(this.addItem.bind(this));
|
|
|
|
TopicManager.getInstance().addOnDeleteTopicListener(this.onDeleteTopic.bind(this));
|
|
|
|
TopicManager.getInstance().addOnReloadTopicsListener(this.onReloadTopics.bind(this));
|
|
|
|
TopicManager.getInstance().addOnUpdateTopicListener(this.onUpdateTopics.bind(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort items
|
|
|
|
private sortItems() {
|
|
|
|
this.items.sort((a, b) => {
|
|
|
|
if (a.date && b.date) {
|
|
|
|
return b.date - a.date;
|
|
|
|
} else if (!a.date) {
|
|
|
|
return -1;
|
|
|
|
} else if (!b.date) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
onUpdateTopics(topicId: string) {
|
|
|
|
const items = this.items.filter(i => i.id === topicId);
|
|
|
|
const topic = TopicManager.getInstance().getTopic(topicId);
|
|
|
|
items.map((item) => {
|
|
|
|
item.label = topic?.name;
|
|
|
|
item.date = topic?.lastUpdated;
|
|
|
|
});
|
|
|
|
this.sortItems();
|
|
|
|
this._onDidChangeTreeData.fire();
|
|
|
|
}
|
|
|
|
|
|
|
|
onReloadTopics(topics: Topic[]) {
|
|
|
|
const items = topics.map((topic) => {
|
|
|
|
return new TopicTreeItem(topic.name ? topic.name : "new topic", topic.topicId, topic.lastUpdated, vscode.TreeItemCollapsibleState.None);
|
|
|
|
});
|
|
|
|
this.items = items;
|
|
|
|
this.sortItems();
|
|
|
|
this._onDidChangeTreeData.fire();
|
|
|
|
}
|
|
|
|
|
|
|
|
onDeleteTopic(topicId: string) {
|
|
|
|
this.items = this.items.filter(i => i.id !== topicId);
|
|
|
|
this.sortItems();
|
|
|
|
this._onDidChangeTreeData.fire();
|
|
|
|
}
|
|
|
|
|
|
|
|
setSelectedItem(item: TopicTreeItem): void {
|
|
|
|
this.selectedItem = item;
|
|
|
|
}
|
|
|
|
|
|
|
|
getChildren(element?: TopicTreeItem): vscode.ProviderResult<TopicTreeItem[]> {
|
|
|
|
return this.items;
|
|
|
|
}
|
|
|
|
|
|
|
|
getTreeItem(element: TopicTreeItem): vscode.TreeItem | Thenable<vscode.TreeItem> {
|
|
|
|
element.command = {
|
|
|
|
title: 'Select Item',
|
|
|
|
command: 'devchat-topicview.selectTopic',
|
|
|
|
arguments: [element],
|
|
|
|
};
|
|
|
|
return element;
|
|
|
|
}
|
|
|
|
|
|
|
|
reload(): void {
|
|
|
|
const topicList = TopicManager.getInstance().getTopicList();
|
|
|
|
this.onReloadTopics(topicList);
|
|
|
|
}
|
|
|
|
|
|
|
|
addItem(topic: Topic): void {
|
|
|
|
const newItem = new TopicTreeItem(topic.name ? topic.name : "new topic", topic.topicId, topic.lastUpdated, vscode.TreeItemCollapsibleState.None);
|
|
|
|
this.items.push(newItem);
|
|
|
|
this.sortItems();
|
|
|
|
this._onDidChangeTreeData.fire();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
deleteItem(item: TopicTreeItem): void {
|
|
|
|
this.items = this.items.filter(i => i !== item);
|
|
|
|
this.sortItems();
|
|
|
|
this._onDidChangeTreeData.fire();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-19 20:07:34 +08:00
|
|
|
function getExtensionVersion(context: vscode.ExtensionContext): string {
|
2023-05-24 13:04:15 +08:00
|
|
|
const packageJsonPath = path.join(context.extensionUri.fsPath, 'package.json');
|
2023-05-19 20:07:34 +08:00
|
|
|
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
|
|
|
|
const packageJson = JSON.parse(packageJsonContent);
|
|
|
|
|
|
|
|
return packageJson.version;
|
|
|
|
}
|
|
|
|
|
2023-05-04 11:50:45 +08:00
|
|
|
|
2023-05-04 07:27:26 +08:00
|
|
|
function activate(context: vscode.ExtensionContext) {
|
2023-05-11 10:27:54 +08:00
|
|
|
ExtensionContextHolder.context = context;
|
2023-05-19 20:07:34 +08:00
|
|
|
const extensionVersion = getExtensionVersion(context);
|
2023-05-11 10:27:54 +08:00
|
|
|
logger.init(context);
|
2023-05-04 11:50:45 +08:00
|
|
|
|
2023-05-16 10:40:57 +08:00
|
|
|
const secretStorage: vscode.SecretStorage = context.secrets;
|
|
|
|
vscode.commands.registerCommand('DevChat.OPENAI_API_KEY', async () => {
|
|
|
|
const passwordInput: string = await vscode.window.showInputBox({
|
2023-05-24 13:04:15 +08:00
|
|
|
password: true,
|
|
|
|
title: "OPENAI_API_KEY"
|
2023-05-16 10:40:57 +08:00
|
|
|
}) ?? '';
|
2023-05-24 13:04:15 +08:00
|
|
|
|
2023-05-16 10:40:57 +08:00
|
|
|
secretStorage.store("devchat_OPENAI_API_KEY", passwordInput);
|
|
|
|
});
|
|
|
|
|
|
|
|
const currentLocale = vscode.env.language;
|
|
|
|
if (currentLocale === 'zh-cn' || currentLocale === 'zh-tw') {
|
2023-05-24 13:04:15 +08:00
|
|
|
vscode.commands.executeCommand('setContext', 'isChineseLocale', true);
|
|
|
|
} else {
|
2023-05-16 10:40:57 +08:00
|
|
|
vscode.commands.executeCommand('setContext', 'isChineseLocale', false);
|
|
|
|
}
|
|
|
|
|
2023-05-11 10:27:54 +08:00
|
|
|
registerOpenChatPanelCommand(context);
|
|
|
|
registerAddContextCommand(context);
|
|
|
|
registerAskForCodeCommand(context);
|
|
|
|
registerAskForFileCommand(context);
|
2023-05-16 14:24:24 +08:00
|
|
|
|
|
|
|
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
|
|
|
|
|
|
|
|
// 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 = '';
|
2023-05-28 14:30:39 +08:00
|
|
|
let isVersionChangeCompare: boolean|undefined = undefined;
|
2023-05-16 14:24:24 +08:00
|
|
|
setInterval(async () => {
|
2023-05-28 14:03:03 +08:00
|
|
|
const versionOld = await secretStorage.get("DevChatVersionOld");
|
2023-05-19 20:07:34 +08:00
|
|
|
const versionNew = extensionVersion;
|
|
|
|
const versionChanged = versionOld !== versionNew;
|
2023-05-28 14:03:03 +08:00
|
|
|
await secretStorage.store("DevChatVersionOld", versionNew!);
|
|
|
|
|
2023-05-16 14:24:24 +08:00
|
|
|
// status item has three status type
|
|
|
|
// 1. not in a folder
|
|
|
|
// 2. dependence is invalid
|
|
|
|
// 3. ready
|
2023-05-19 09:20:48 +08:00
|
|
|
if (devchatStatus === '' || devchatStatus === 'waiting install devchat') {
|
2023-05-18 22:25:29 +08:00
|
|
|
let bOk = true;
|
2023-05-24 13:04:15 +08:00
|
|
|
let devChat: string | undefined = vscode.workspace.getConfiguration('DevChat').get('DevChatPath');
|
2023-05-18 22:25:29 +08:00
|
|
|
if (!devChat) {
|
|
|
|
bOk = false;
|
|
|
|
}
|
|
|
|
|
2023-05-19 20:07:34 +08:00
|
|
|
if (!bOk) {
|
2023-05-18 23:02:19 +08:00
|
|
|
bOk = checkDevChatDependency();
|
|
|
|
}
|
2023-05-28 14:30:39 +08:00
|
|
|
if (bOk && versionChanged && !isVersionChangeCompare) {
|
|
|
|
logger.channel()?.info(`versionOld: ${versionOld}, versionNew: ${versionNew}, versionChanged: ${versionChanged}`);
|
2023-05-19 20:07:34 +08:00
|
|
|
bOk = false;
|
|
|
|
}
|
2023-05-18 23:02:19 +08:00
|
|
|
|
2023-05-16 14:24:24 +08:00
|
|
|
if (bOk) {
|
|
|
|
devchatStatus = 'ready';
|
2023-05-24 13:04:15 +08:00
|
|
|
TopicManager.getInstance().loadTopics();
|
2023-05-16 14:24:24 +08:00
|
|
|
} else {
|
|
|
|
if (devchatStatus === '') {
|
|
|
|
devchatStatus = 'not ready';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (devchatStatus === 'not ready') {
|
|
|
|
// auto install devchat
|
|
|
|
const terminal = vscode.window.createTerminal("DevChat Install");
|
2023-05-24 13:04:15 +08:00
|
|
|
terminal.sendText(`python ${context.extensionUri.fsPath + "/tools/install.py"}`);
|
2023-05-16 14:24:24 +08:00
|
|
|
terminal.show();
|
2023-05-19 09:20:48 +08:00
|
|
|
devchatStatus = 'waiting install devchat';
|
2023-05-28 14:30:39 +08:00
|
|
|
isVersionChangeCompare = true;
|
2023-05-16 14:24:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (devchatStatus !== 'ready') {
|
|
|
|
statusBarItem.text = `$(warning)DevChat`;
|
|
|
|
statusBarItem.tooltip = `${devchatStatus}`;
|
2023-05-28 09:58:53 +08:00
|
|
|
statusBarItem.command = undefined;
|
2023-05-16 14:24:24 +08:00
|
|
|
// set statusBarItem warning color
|
2023-05-24 13:04:15 +08:00
|
|
|
return;
|
2023-05-16 14:24:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// check api key
|
|
|
|
if (apiKeyStatus === '' || apiKeyStatus === 'please set api key') {
|
2023-05-28 08:32:12 +08:00
|
|
|
const bOk = await checkOpenaiApiKey();
|
2023-05-16 14:24:24 +08:00
|
|
|
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';
|
2023-05-24 13:04:15 +08:00
|
|
|
return;
|
2023-05-16 14:24:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
statusBarItem.text = `$(pass)DevChat`;
|
|
|
|
statusBarItem.tooltip = `ready to chat`;
|
2023-05-16 17:49:13 +08:00
|
|
|
statusBarItem.command = 'devcaht.onStatusBarClick';
|
2023-05-16 14:24:24 +08:00
|
|
|
}, 3000);
|
|
|
|
|
|
|
|
// Add the status bar item to the status bar
|
|
|
|
statusBarItem.show();
|
|
|
|
context.subscriptions.push(statusBarItem);
|
2023-05-16 14:35:01 +08:00
|
|
|
|
|
|
|
// Register the command
|
|
|
|
context.subscriptions.push(
|
2023-05-24 13:04:15 +08:00
|
|
|
vscode.commands.registerCommand('devcaht.onStatusBarClick', async () => {
|
2023-05-16 17:49:13 +08:00
|
|
|
await vscode.commands.executeCommand('devchat-view.focus');
|
2023-05-16 14:35:01 +08:00
|
|
|
})
|
|
|
|
);
|
|
|
|
|
2023-05-16 17:49:13 +08:00
|
|
|
ExtensionContextHolder.provider = new DevChatViewProvider(context);
|
2023-05-16 14:35:01 +08:00
|
|
|
context.subscriptions.push(
|
2023-05-16 17:49:13 +08:00
|
|
|
vscode.window.registerWebviewViewProvider('devchat-view', ExtensionContextHolder.provider, {
|
|
|
|
webviewOptions: { retainContextWhenHidden: true }
|
|
|
|
})
|
2023-05-16 14:35:01 +08:00
|
|
|
);
|
2023-05-18 15:25:46 +08:00
|
|
|
|
2023-05-24 13:04:15 +08:00
|
|
|
const yourTreeDataProvider = new TopicTreeDataProvider();
|
|
|
|
const yourTreeView = vscode.window.createTreeView('devchat-topicview', {
|
|
|
|
treeDataProvider: yourTreeDataProvider,
|
|
|
|
});
|
|
|
|
context.subscriptions.push(yourTreeView);
|
|
|
|
|
2023-05-28 21:46:44 +08:00
|
|
|
const topicDeleteCallback = async (item: TopicTreeItem) => {
|
|
|
|
const confirm = 'Delete';
|
|
|
|
const cancel = 'Cancel';
|
2023-05-28 22:01:32 +08:00
|
|
|
const label = typeof item.label === 'string' ? item.label : item.label!.label;
|
|
|
|
const truncatedLabel = label.substring(0, 20) + (label.length > 20 ? '...' : '');
|
2023-05-28 21:46:44 +08:00
|
|
|
const result = await vscode.window.showWarningMessage(
|
2023-05-28 22:01:32 +08:00
|
|
|
`Are you sure you want to delete the topic "${truncatedLabel}"?`,
|
2023-05-28 21:46:44 +08:00
|
|
|
{ modal: true },
|
|
|
|
confirm,
|
|
|
|
cancel
|
|
|
|
);
|
|
|
|
|
|
|
|
if (result === confirm) {
|
|
|
|
TopicManager.getInstance().deleteTopic(item.id);
|
|
|
|
}
|
2023-05-28 22:01:32 +08:00
|
|
|
};
|
2023-05-28 21:46:44 +08:00
|
|
|
vscode.commands.registerCommand('devchat-topicview.deleteTopic', topicDeleteCallback);
|
2023-05-24 13:04:15 +08:00
|
|
|
|
|
|
|
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) {
|
2023-05-28 21:46:44 +08:00
|
|
|
topicDeleteCallback(selectedItem);
|
2023-05-24 13:04:15 +08:00
|
|
|
} 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();
|
|
|
|
});
|
|
|
|
|
2023-05-18 15:25:46 +08:00
|
|
|
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.');
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
2023-04-14 08:05:41 +08:00
|
|
|
}
|
2023-04-21 06:44:26 +08:00
|
|
|
exports.activate = activate;
|