Merge pull request #358 from devchat-ai/add_new_ide_services
Support codelens for function and add new ide services
This commit is contained in:
commit
3ab349c6c4
@ -667,6 +667,11 @@
|
|||||||
"command": "DevChat.InstallCommandPython",
|
"command": "DevChat.InstallCommandPython",
|
||||||
"title": "Install Python for Commands",
|
"title": "Install Python for Commands",
|
||||||
"category": "DevChat"
|
"category": "DevChat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "DevChat.Chat",
|
||||||
|
"title": "Chat with DevChat",
|
||||||
|
"category": "DevChat"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"menus": {
|
"menus": {
|
||||||
@ -749,6 +754,10 @@
|
|||||||
{
|
{
|
||||||
"command": "devchat.askForFile_chinese",
|
"command": "devchat.askForFile_chinese",
|
||||||
"when": "false"
|
"when": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "DevChat.Chat",
|
||||||
|
"when": "false"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"explorer/context": [
|
"explorer/context": [
|
||||||
|
@ -18,6 +18,7 @@ import { sendCommandListByDevChatRun, updateChatModels } from '../handler/workfl
|
|||||||
import DevChat from "../toolwrapper/devchat";
|
import DevChat from "../toolwrapper/devchat";
|
||||||
import { createEnvByConda, createEnvByMamba } from '../util/python_installer/app_install';
|
import { createEnvByConda, createEnvByMamba } from '../util/python_installer/app_install';
|
||||||
import { installRequirements } from '../util/python_installer/package_install';
|
import { installRequirements } from '../util/python_installer/package_install';
|
||||||
|
import { chatWithDevChat } from '../handler/chatHandler';
|
||||||
|
|
||||||
|
|
||||||
function registerOpenChatPanelCommand(context: vscode.ExtensionContext) {
|
function registerOpenChatPanelCommand(context: vscode.ExtensionContext) {
|
||||||
@ -340,6 +341,15 @@ export function registerInstallCommandsPython(context: vscode.ExtensionContext)
|
|||||||
context.subscriptions.push(disposable);
|
context.subscriptions.push(disposable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function registerDevChatChatCommand(context: vscode.ExtensionContext) {
|
||||||
|
let disposable = vscode.commands.registerCommand('DevChat.Chat', async (message: string) => {
|
||||||
|
ensureChatPanel(context);
|
||||||
|
chatWithDevChat(ExtensionContextHolder.provider?.view()!, message);
|
||||||
|
});
|
||||||
|
|
||||||
|
context.subscriptions.push(disposable);
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
registerOpenChatPanelCommand,
|
registerOpenChatPanelCommand,
|
||||||
registerAddContextCommand,
|
registerAddContextCommand,
|
||||||
|
@ -16,7 +16,8 @@ import {
|
|||||||
regPythonPathCommand,
|
regPythonPathCommand,
|
||||||
registerInstallCommandsCommand,
|
registerInstallCommandsCommand,
|
||||||
registerUpdateChatModelsCommand,
|
registerUpdateChatModelsCommand,
|
||||||
registerInstallCommandsPython
|
registerInstallCommandsPython,
|
||||||
|
registerDevChatChatCommand
|
||||||
} from './contributes/commands';
|
} from './contributes/commands';
|
||||||
import { regLanguageContext } from './contributes/context';
|
import { regLanguageContext } from './contributes/context';
|
||||||
import { regDevChatView, regTopicView } from './contributes/views';
|
import { regDevChatView, regTopicView } from './contributes/views';
|
||||||
@ -29,6 +30,7 @@ import { UiUtilWrapper } from './util/uiUtil';
|
|||||||
import { UiUtilVscode } from './util/uiUtil_vscode';
|
import { UiUtilVscode } from './util/uiUtil_vscode';
|
||||||
import { ApiKeyManager } from './util/apiKey';
|
import { ApiKeyManager } from './util/apiKey';
|
||||||
import { startRpcServer } from './ide_services/services';
|
import { startRpcServer } from './ide_services/services';
|
||||||
|
import { registerCodeLensProvider } from './panel/codeLens';
|
||||||
|
|
||||||
async function isProviderHasSetted() {
|
async function isProviderHasSetted() {
|
||||||
try {
|
try {
|
||||||
@ -216,6 +218,7 @@ async function activate(context: vscode.ExtensionContext) {
|
|||||||
await configUpdateTo1115();
|
await configUpdateTo1115();
|
||||||
|
|
||||||
regLanguageContext();
|
regLanguageContext();
|
||||||
|
registerCodeLensProvider(context);
|
||||||
|
|
||||||
regDevChatView(context);
|
regDevChatView(context);
|
||||||
regTopicView(context);
|
regTopicView(context);
|
||||||
@ -241,6 +244,7 @@ async function activate(context: vscode.ExtensionContext) {
|
|||||||
regApplyDiffResultCommand(context);
|
regApplyDiffResultCommand(context);
|
||||||
|
|
||||||
regPythonPathCommand(context);
|
regPythonPathCommand(context);
|
||||||
|
registerDevChatChatCommand(context);
|
||||||
|
|
||||||
startRpcServer();
|
startRpcServer();
|
||||||
}
|
}
|
||||||
|
6
src/handler/chatHandler.ts
Normal file
6
src/handler/chatHandler.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { MessageHandler } from './messageHandler';
|
||||||
|
|
||||||
|
|
||||||
|
export async function chatWithDevChat(panel, message: string) {
|
||||||
|
MessageHandler.sendMessage(panel!, { command: 'chatWithDevChat', 'message': message });
|
||||||
|
}
|
@ -1,9 +1,17 @@
|
|||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import * as url from 'url';
|
import * as vscode from 'vscode';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
|
||||||
import * as querystring from 'querystring';
|
import * as querystring from 'querystring';
|
||||||
import { logger } from '../util/logger';
|
import { logger } from '../util/logger';
|
||||||
import { UiUtilWrapper } from '../util/uiUtil';
|
import { UiUtilWrapper } from '../util/uiUtil';
|
||||||
|
|
||||||
|
import { createEnvByConda, createEnvByMamba } from '../util/python_installer/app_install';
|
||||||
|
import { installRequirements } from '../util/python_installer/package_install';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const functionRegistry: any = {
|
const functionRegistry: any = {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
@ -12,6 +20,72 @@ const functionRegistry: any = {
|
|||||||
"handler": async () => {
|
"handler": async () => {
|
||||||
return await UiUtilWrapper.getLSPBrigePort();
|
return await UiUtilWrapper.getLSPBrigePort();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
"/update_slash_commands": {
|
||||||
|
"keys": [],
|
||||||
|
"handler": async () => {
|
||||||
|
vscode.commands.executeCommand('DevChat.InstallCommands');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
"/open_folder": {
|
||||||
|
"keys": ["folder"],
|
||||||
|
"handler": async (folder: string) => {
|
||||||
|
// open folder by vscode
|
||||||
|
const folderPathParsed = folder.replace('\\', '/');
|
||||||
|
// Updated Uri.parse to Uri.file
|
||||||
|
const folderUri = vscode.Uri.file(folderPathParsed);
|
||||||
|
vscode.commands.executeCommand(`vscode.openFolder`, folderUri);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
"/install_python_env": {
|
||||||
|
"keys": ["command_name", "requirements_file"],
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
"handler": async (command_name: string, requirements_file: string) => {
|
||||||
|
// 1. install python >= 3.11
|
||||||
|
logger.channel()?.info(`create env for python ...`);
|
||||||
|
logger.channel()?.info(`try to create env by mamba ...`);
|
||||||
|
let pythonCommand = await createEnvByMamba(command_name, "", "3.11.4");
|
||||||
|
|
||||||
|
if (!pythonCommand || pythonCommand === "") {
|
||||||
|
logger.channel()?.info(`create env by mamba failed, try to create env by conda ...`);
|
||||||
|
pythonCommand = await createEnvByConda(command_name, "", "3.11.4");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pythonCommand || pythonCommand === "") {
|
||||||
|
logger.channel()?.error(`create virtual python env failed, you need create it by yourself with command: "conda create -n devchat-commands python=3.11.4"`);
|
||||||
|
logger.channel()?.show();
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. install requirements.txt
|
||||||
|
// run command: pip install -r {requirementsFile}
|
||||||
|
let isInstalled = false;
|
||||||
|
// try 3 times
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
let otherSource: string | undefined = undefined;
|
||||||
|
if (i>1) {
|
||||||
|
otherSource = 'https://pypi.tuna.tsinghua.edu.cn/simple/';
|
||||||
|
}
|
||||||
|
isInstalled = await installRequirements(pythonCommand, requirements_file, otherSource);
|
||||||
|
if (isInstalled) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
logger.channel()?.info(`Install packages failed, try again: ${i + 1}`);
|
||||||
|
}
|
||||||
|
if (!isInstalled) {
|
||||||
|
logger.channel()?.error(`Install packages failed, you can install it with command: "${pythonCommand} -m pip install -r ${requirements_file}"`);
|
||||||
|
logger.channel()?.show();
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return pythonCommand.trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
182
src/panel/codeLens.ts
Normal file
182
src/panel/codeLens.ts
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { has } from 'mobx';
|
||||||
|
|
||||||
|
interface FunctionDefinition {
|
||||||
|
name: string;
|
||||||
|
containerName: string | null;
|
||||||
|
range: vscode.Range;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CodeLensRegistration = {
|
||||||
|
elementType: string;
|
||||||
|
objectName: string;
|
||||||
|
promptGenerator: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class CodeLensManager {
|
||||||
|
private static instance: CodeLensManager;
|
||||||
|
private registrations: CodeLensRegistration[] = [];
|
||||||
|
private configFilePath: string;
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
this.configFilePath = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.chat/ideconfig.json');
|
||||||
|
this.loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): CodeLensManager {
|
||||||
|
if (!CodeLensManager.instance) {
|
||||||
|
CodeLensManager.instance = new CodeLensManager();
|
||||||
|
}
|
||||||
|
return CodeLensManager.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadConfig(): void {
|
||||||
|
if (!fs.existsSync(this.configFilePath)) {
|
||||||
|
this.initializeConfig();
|
||||||
|
} else {
|
||||||
|
const data = fs.readFileSync(this.configFilePath, 'utf8');
|
||||||
|
this.registrations = JSON.parse(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeConfig(): void {
|
||||||
|
this.registrations = [
|
||||||
|
// {
|
||||||
|
// elementType: 'function',
|
||||||
|
// objectName: 'generate unit tests',
|
||||||
|
// promptGenerator: '/test generate unit tests for {__filename__} {__functionName__}'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// elementType: 'inner_function',
|
||||||
|
// objectName: 'generate comment',
|
||||||
|
// promptGenerator: 'generate comment for \n ```code\n{__functionCode__}\n```\n'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// elementType: 'function',
|
||||||
|
// objectName: 'generate comment',
|
||||||
|
// promptGenerator: 'generate comment for \n ```code\n{__functionCode__}\n```\n'
|
||||||
|
// }
|
||||||
|
];
|
||||||
|
this.saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveConfig(): void {
|
||||||
|
const configDir = path.dirname(this.configFilePath);
|
||||||
|
if (!fs.existsSync(configDir)) {
|
||||||
|
fs.mkdirSync(configDir, { recursive: true });
|
||||||
|
}
|
||||||
|
fs.writeFileSync(this.configFilePath, JSON.stringify(this.registrations, null, 2), 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRegistrations(): CodeLensRegistration[] {
|
||||||
|
return this.registrations;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function getFunctionDefinitions(document: vscode.TextDocument, inner_function: boolean = false): Promise<FunctionDefinition[]> {
|
||||||
|
const symbols: vscode.DocumentSymbol[] | undefined = await vscode.commands.executeCommand(
|
||||||
|
'vscode.executeDocumentSymbolProvider',
|
||||||
|
document.uri
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!symbols) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractFunctions(symbol: vscode.DocumentSymbol, containerName: string | null, hasInFunction: boolean = false): FunctionDefinition[] {
|
||||||
|
console.log('==> symbol range:', symbol.range, symbol.name, symbol.kind);
|
||||||
|
let functions: FunctionDefinition[] = [];
|
||||||
|
if (symbol.kind === vscode.SymbolKind.Function || symbol.kind === vscode.SymbolKind.Method) {
|
||||||
|
if (!inner_function || (inner_function && hasInFunction)) {
|
||||||
|
functions.push({
|
||||||
|
name: symbol.name,
|
||||||
|
containerName: containerName,
|
||||||
|
range: symbol.range
|
||||||
|
});
|
||||||
|
}
|
||||||
|
hasInFunction = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inner_function) {
|
||||||
|
if (symbol.children && symbol.children.length > 0) {
|
||||||
|
symbol.children.forEach(child => {
|
||||||
|
functions = functions.concat(extractFunctions(child, symbol.name, hasInFunction));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return functions;
|
||||||
|
}
|
||||||
|
|
||||||
|
let functionSymbols: FunctionDefinition[] = [];
|
||||||
|
symbols.forEach(symbol => {
|
||||||
|
functionSymbols = functionSymbols.concat(extractFunctions(symbol, null));
|
||||||
|
});
|
||||||
|
|
||||||
|
return functionSymbols;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionTestCodeLensProvider implements vscode.CodeLensProvider {
|
||||||
|
// The provideCodeLenses method should have the correct signature
|
||||||
|
async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.CodeLens[]> {
|
||||||
|
const lenses: vscode.CodeLens[] = [];
|
||||||
|
const functionDefinitions = await getFunctionDefinitions(document);
|
||||||
|
const innerFunctionDefinitions = await getFunctionDefinitions(document, true);
|
||||||
|
|
||||||
|
const matchElements = {
|
||||||
|
'function': functionDefinitions,
|
||||||
|
'inner_function': innerFunctionDefinitions
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [elementType, elements] of Object.entries(matchElements)) {
|
||||||
|
elements.forEach((funcDef) => {
|
||||||
|
const range = new vscode.Range(
|
||||||
|
new vscode.Position(funcDef.range.start.line, 0),
|
||||||
|
new vscode.Position(funcDef.range.end.line, 10000)
|
||||||
|
);
|
||||||
|
|
||||||
|
const codelenRegisters: CodeLensRegistration[] = CodeLensManager.getInstance().getRegistrations();
|
||||||
|
// Iterate over codelenRegisters with 'of' instead of 'in'
|
||||||
|
for (const codelenRegister of codelenRegisters) {
|
||||||
|
if (codelenRegister.elementType !== elementType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read range content in document
|
||||||
|
const functionCode = document.getText(range);
|
||||||
|
|
||||||
|
// Fix the string replacement syntax and closing parentheses
|
||||||
|
const prompt = codelenRegister.promptGenerator
|
||||||
|
.replace('{__filename__}', document.uri.fsPath)
|
||||||
|
.replace('{__functionName__}', funcDef.name)
|
||||||
|
.replace('{__functionRange__}', `[${range.start.line}, ${range.end.line}]`)
|
||||||
|
.replace('{__functionCode__}', functionCode); // Fixed syntax
|
||||||
|
|
||||||
|
const lens = new vscode.CodeLens(range, {
|
||||||
|
title: codelenRegister.objectName,
|
||||||
|
command: "DevChat.Chat",
|
||||||
|
// arguments: [document.uri.fsPath, range, funcDef.name] // Commented out as it's not used
|
||||||
|
arguments: [prompt]
|
||||||
|
});
|
||||||
|
|
||||||
|
lenses.push(lens);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return lenses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function registerCodeLensProvider(context) {
|
||||||
|
const provider = new FunctionTestCodeLensProvider();
|
||||||
|
const disposable = vscode.languages.registerCodeLensProvider("*", provider);
|
||||||
|
|
||||||
|
context.subscriptions.push(disposable);
|
||||||
|
}
|
@ -247,10 +247,6 @@ class DevChat {
|
|||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
"command_python": UiUtilWrapper.getConfiguration('DevChat', 'PythonForCommands') || "python3",
|
"command_python": UiUtilWrapper.getConfiguration('DevChat', 'PythonForCommands') || "python3",
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
"DEVCHATPYTHON": UiUtilWrapper.getConfiguration("DevChat", "PythonForChat") || "python3",
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
"PYTHONLIBPATH": UiUtilWrapper.extensionPath() + "/tools/site-packages",
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
"PYTHONPATH": UiUtilWrapper.extensionPath() + "/tools/site-packages",
|
"PYTHONPATH": UiUtilWrapper.extensionPath() + "/tools/site-packages",
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
"OPENAI_API_KEY": llmModelData.api_key,
|
"OPENAI_API_KEY": llmModelData.api_key,
|
||||||
|
@ -137,6 +137,9 @@ const InputMessage = observer((props: any) => {
|
|||||||
messageUtil.registerHandler('regCommandList', (message: { result: object[]}) => {
|
messageUtil.registerHandler('regCommandList', (message: { result: object[]}) => {
|
||||||
input.updateCommands(message.result);
|
input.updateCommands(message.result);
|
||||||
});
|
});
|
||||||
|
messageUtil.registerHandler('chatWithDevChat', (message: {command: string, message: string}) => {
|
||||||
|
chat.commonMessage(message.message, []);
|
||||||
|
});
|
||||||
messageUtil.registerHandler('appendContext', (message: { command: string; context: string }) => {
|
messageUtil.registerHandler('appendContext', (message: { command: string; context: string }) => {
|
||||||
// context is a temp file path
|
// context is a temp file path
|
||||||
const match = /\|([^]+?)\]/.exec(message.context);
|
const match = /\|([^]+?)\]/.exec(message.context);
|
||||||
|
2
tools
2
tools
@ -1 +1 @@
|
|||||||
Subproject commit d157dfc834ebd5e40be62a32c0406999374bbff6
|
Subproject commit fb4d4565e8346020d4a42064db79068e2912f38c
|
Loading…
x
Reference in New Issue
Block a user