359 lines
11 KiB
TypeScript
Raw Normal View History

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 {
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';
import { Topic, TopicManager } from './topic/topicManager';
2023-05-16 14:35:01 +08:00
2023-05-04 11:50:45 +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 {
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
const secretStorage: vscode.SecretStorage = context.secrets;
vscode.commands.registerCommand('DevChat.OPENAI_API_KEY', async () => {
const passwordInput: string = await vscode.window.showInputBox({
password: true,
title: "OPENAI_API_KEY"
}) ?? '';
secretStorage.store("devchat_OPENAI_API_KEY", passwordInput);
});
const currentLocale = vscode.env.language;
if (currentLocale === 'zh-cn' || currentLocale === 'zh-tw') {
vscode.commands.executeCommand('setContext', 'isChineseLocale', true);
} else {
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 = '';
let isVersionChangeCompare: boolean|undefined = undefined;
2023-05-16 14:24:24 +08:00
setInterval(async () => {
const versionOld = await secretStorage.get("DevChatVersionOld");
2023-05-19 20:07:34 +08:00
const versionNew = extensionVersion;
const versionChanged = versionOld !== versionNew;
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') {
let bOk = true;
let devChat: string | undefined = vscode.workspace.getConfiguration('DevChat').get('DevChatPath');
if (!devChat) {
bOk = false;
}
2023-05-19 20:07:34 +08:00
if (!bOk) {
2023-05-18 23:02:19 +08:00
bOk = checkDevChatDependency();
}
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';
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");
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';
isVersionChangeCompare = true;
2023-05-16 14:24:24 +08:00
}
if (devchatStatus !== 'ready') {
statusBarItem.text = `$(warning)DevChat`;
statusBarItem.tooltip = `${devchatStatus}`;
statusBarItem.command = undefined;
2023-05-16 14:24:24 +08:00
// set statusBarItem warning color
return;
2023-05-16 14:24:24 +08:00
}
// check api key
if (apiKeyStatus === '' || apiKeyStatus === 'please set api key') {
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';
return;
2023-05-16 14:24:24 +08:00
}
statusBarItem.text = `$(pass)DevChat`;
statusBarItem.tooltip = `ready to chat`;
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(
vscode.commands.registerCommand('devcaht.onStatusBarClick', async () => {
await vscode.commands.executeCommand('devchat-view.focus');
2023-05-16 14:35:01 +08:00
})
);
ExtensionContextHolder.provider = new DevChatViewProvider(context);
2023-05-16 14:35:01 +08:00
context.subscriptions.push(
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
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();
});
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
}
exports.activate = activate;