[vscode-spring-cli] Basic AI command support
This commit is contained in:
@@ -31,6 +31,11 @@
|
||||
"when": "resourceFilename == pom.xml",
|
||||
"command": "vscode-spring-cli.boot.add",
|
||||
"group": "Spring-CLI"
|
||||
},
|
||||
{
|
||||
"when": "resourceFilename =~ /README-\\S+.md/",
|
||||
"command": "vscode-spring-cli.guide.apply",
|
||||
"group": "Spring-CLI"
|
||||
}
|
||||
],
|
||||
"explorer/context": [
|
||||
@@ -38,6 +43,11 @@
|
||||
"when": "resourceFilename == pom.xml",
|
||||
"command": "vscode-spring-cli.boot.add",
|
||||
"group": "Spring-CLI"
|
||||
},
|
||||
{
|
||||
"when": "resourceFilename =~ /README-\\S+.md/",
|
||||
"command": "vscode-spring-cli.guide.apply",
|
||||
"group": "Spring-CLI"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -91,6 +101,16 @@
|
||||
"command": "vscode-spring-cli.command.execute",
|
||||
"title": "Execute User Defined Command",
|
||||
"category": "Spring CLI"
|
||||
},
|
||||
{
|
||||
"command": "vscode-spring-cli.ai.add",
|
||||
"title": "Add from AI",
|
||||
"category": "Spring CLI"
|
||||
},
|
||||
{
|
||||
"command": "vscode-spring-cli.guide.apply",
|
||||
"title": "Apply Guide",
|
||||
"category": "Spring CLI"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
|
||||
20
vscode-extensions/vscode-spring-cli/src/ai.ts
Normal file
20
vscode-extensions/vscode-spring-cli/src/ai.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Uri, commands, window } from "vscode";
|
||||
import { CLI } from "./extension";
|
||||
import { enterText, getTargetPomXml } from "./utils";
|
||||
import path from "path";
|
||||
import { handleGuideApply } from "./guide";
|
||||
|
||||
export async function handleAiAdd(pom: Uri) {
|
||||
if (!pom) {
|
||||
pom = await getTargetPomXml();
|
||||
}
|
||||
const question = await enterText({
|
||||
title: "Question",
|
||||
prompt: "Enter question to LLM",
|
||||
});
|
||||
const uri = await CLI.aiAdd(question, path.dirname(pom.fsPath));
|
||||
await commands.executeCommand("markdown.showPreview", uri);
|
||||
if ("Yes" === await window.showInformationMessage(`Apply guide '${path.basename(uri.fsPath)}' to the project?`, "Yes", "No")) {
|
||||
handleGuideApply(uri);
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,9 @@ export async function handleBootAdd(pom?: Uri): Promise<void> {
|
||||
}
|
||||
if (metadata.catalog) {
|
||||
await CLI.bootAdd(metadata);
|
||||
const doc = await workspace.openTextDocument(path.join(metadata.targetFolder, `README-${metadata.catalog}.md`));
|
||||
await window.showTextDocument(doc);
|
||||
// const doc = await workspace.openTextDocument(path.join(metadata.targetFolder, `README-${metadata.catalog}.md`));
|
||||
// await window.showTextDocument(doc);
|
||||
await commands.executeCommand("markdown.showPreview", Uri.file(path.join(metadata.targetFolder, `README-${metadata.catalog}.md`)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BootAddMetadata, BootNewMetadata, Project, ProjectCatalog, CommandAddMetadata, CommandRemoveMetadata, CommandInfo, CommandExecuteMetadata } from "./cli-types";
|
||||
import { CancellationToken, ProcessExecution, ProgressLocation, ProviderResult, Task, TaskProvider, TaskScope, Uri, tasks, window, workspace } from "vscode";
|
||||
import { CancellationToken, ProcessExecution, ProgressLocation, ProviderResult, ShellExecution, Task, TaskProvider, TaskScope, Uri, tasks, window, workspace } from "vscode";
|
||||
import cp from "child_process";
|
||||
import { homedir } from "os";
|
||||
import { getWorkspaceRoot } from "./utils";
|
||||
@@ -27,6 +27,63 @@ export class Cli {
|
||||
return this.fetchJson("Fetching Available Catalogs...", undefined, ["project-catalog", "list-available"]);
|
||||
}
|
||||
|
||||
guideApply(uri: Uri, cwd?: string) {
|
||||
const args = [
|
||||
"guide",
|
||||
"apply",
|
||||
"--file",
|
||||
uri.fsPath
|
||||
];
|
||||
return this.exec("Applying guide", uri.fsPath, args, cwd || path.dirname(uri.fsPath));
|
||||
}
|
||||
|
||||
aiAdd(question: string, cwd: string): Thenable<Uri> {
|
||||
|
||||
const args = [
|
||||
"ai",
|
||||
"add",
|
||||
"--preview",
|
||||
"true",
|
||||
"--description",
|
||||
`"${question}"`
|
||||
];
|
||||
|
||||
return window.withProgress({
|
||||
location: ProgressLocation.Window,
|
||||
cancellable: true,
|
||||
title: "Add from AI",
|
||||
}, (progress, cancellation) => {
|
||||
|
||||
progress.report({ message: question });
|
||||
|
||||
return new Promise<Uri>(async (resolve, reject) => {
|
||||
if (cancellation.isCancellationRequested) {
|
||||
reject("Cancelled");
|
||||
}
|
||||
const processOpts = { cwd: cwd || getWorkspaceRoot()?.fsPath || homedir() };
|
||||
const process = this.executable.endsWith(".jar") ? await cp.exec(`java -jar ${this.executable} ${args.join(" ")}`, processOpts) : await cp.exec(`${this.executable} ${args.join(" ")}`, processOpts);
|
||||
cancellation.onCancellationRequested(() => process.kill());
|
||||
const errorMessageChunks = [];
|
||||
let guideFileName;
|
||||
process.stdout.on("data", s => {
|
||||
const res = /README-\S+.md/.exec(s);
|
||||
if (res.length) {
|
||||
guideFileName = res[0].trim();
|
||||
}
|
||||
});
|
||||
process.stderr.on("data", s => errorMessageChunks.push(s))
|
||||
process.on("exit", (code) => {
|
||||
if (code) {
|
||||
reject(`Failed to get response from LLM. ${errorMessageChunks.join()}`);
|
||||
} else {
|
||||
resolve(Uri.file(path.join(cwd, guideFileName)));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
commandAdd(metadata: CommandAddMetadata, cwd?: string) {
|
||||
const args = [
|
||||
"command",
|
||||
@@ -216,6 +273,8 @@ export class Cli {
|
||||
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
const processOpts = { cwd: cwd || getWorkspaceRoot()?.fsPath || homedir() };
|
||||
|
||||
// const process = this.executable.endsWith(".jar") ? new ShellExecution("java", [ "-jar", this.executable, ...args], processOpts) : new ShellExecution(this.executable, args, processOpts)
|
||||
const process = this.executable.endsWith(".jar") ? new ProcessExecution("java", [ "-jar", this.executable, ...args], processOpts) : new ProcessExecution(this.executable, args, processOpts);
|
||||
const task = new Task({ type: SPRING_CLI_TASK_TYPE }, cwd ? workspace.getWorkspaceFolder(Uri.file(cwd)) : TaskScope.Global, `${title}: ${message}`, SPRING_CLI_TASK_TYPE, process);
|
||||
const taskExecution = await tasks.executeTask(task);
|
||||
@@ -225,7 +284,8 @@ export class Cli {
|
||||
const cancelListener = cancellation.onCancellationRequested(() => {
|
||||
cancelListener.dispose();
|
||||
reject();
|
||||
})
|
||||
});
|
||||
|
||||
const listener = tasks.onDidEndTaskProcess(e => {
|
||||
if (e.execution === taskExecution) {
|
||||
listener.dispose();
|
||||
@@ -254,7 +314,7 @@ export class Cli {
|
||||
reject("Cancelled");
|
||||
}
|
||||
const processOpts = { cwd: cwd || getWorkspaceRoot()?.fsPath || homedir() };
|
||||
const process = this.executable.endsWith(".jar") ? await cp.exec(`java -jar ${this.executable} ${args.join(" ")} --json`, processOpts) : await cp.exec(`${this.executable} ${args.join(" ")}`, processOpts);
|
||||
const process = this.executable.endsWith(".jar") ? await cp.exec(`java -jar ${this.executable} ${args.join(" ")} --json`, processOpts) : await cp.exec(`${this.executable} ${args.join(" ")} --json`, processOpts);
|
||||
cancellation.onCancellationRequested(() => process.kill());
|
||||
const dataChunks: string[] = [];
|
||||
process.stdout.on("data", s => dataChunks.push(s));
|
||||
|
||||
@@ -5,31 +5,37 @@ import { handleCatalogAdd, handleCatalogRemove } from "./project-catalog";
|
||||
import { handleProjectAdd, handleProjectRemove } from "./project";
|
||||
import { handleCommandAdd, handleCommandExecute, handleCommandNew, handleCommandRemove } from "./command";
|
||||
import { ExtensionContext, commands, tasks } from "vscode";
|
||||
import { handleAiAdd } from "./ai";
|
||||
import { handleGuideApply } from "./guide";
|
||||
|
||||
export const CLI = new Cli();
|
||||
|
||||
export async function activate(context: ExtensionContext): Promise<void> {
|
||||
|
||||
context.subscriptions.push(...[
|
||||
|
||||
]);
|
||||
context.subscriptions.push(commands.registerCommand('vscode-spring-cli.boot.new', handleBootNew));
|
||||
context.subscriptions.push(commands.registerCommand('vscode-spring-cli.boot.add', handleBootAdd));
|
||||
|
||||
context.subscriptions.push(commands.registerCommand('vscode-spring-cli.project-catalog.add', handleCatalogAdd));
|
||||
context.subscriptions.push(commands.registerCommand('vscode-spring-cli.project-catalog.remove', handleCatalogRemove));
|
||||
|
||||
context.subscriptions.push(commands.registerCommand('vscode-spring-cli.project.add', handleProjectAdd));
|
||||
context.subscriptions.push(commands.registerCommand('vscode-spring-cli.project.remove', handleProjectRemove));
|
||||
|
||||
context.subscriptions.push(commands.registerCommand('vscode-spring-cli.command.add', handleCommandAdd));
|
||||
context.subscriptions.push(commands.registerCommand('vscode-spring-cli.command.remove', handleCommandRemove));
|
||||
context.subscriptions.push(commands.registerCommand('vscode-spring-cli.command.new', handleCommandNew));
|
||||
context.subscriptions.push(commands.registerCommand('vscode-spring-cli.command.execute', handleCommandExecute));
|
||||
|
||||
context.subscriptions.push(tasks.registerTaskProvider(SPRING_CLI_TASK_TYPE, new CliTaskProvider(CLI)));
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand('vscode-spring-cli.boot.new', handleBootNew),
|
||||
commands.registerCommand('vscode-spring-cli.boot.add', handleBootAdd),
|
||||
|
||||
commands.registerCommand('vscode-spring-cli.project-catalog.add', handleCatalogAdd),
|
||||
commands.registerCommand('vscode-spring-cli.project-catalog.remove', handleCatalogRemove),
|
||||
|
||||
commands.registerCommand('vscode-spring-cli.project.add', handleProjectAdd),
|
||||
commands.registerCommand('vscode-spring-cli.project.remove', handleProjectRemove),
|
||||
|
||||
commands.registerCommand('vscode-spring-cli.command.add', handleCommandAdd),
|
||||
commands.registerCommand('vscode-spring-cli.command.remove', handleCommandRemove),
|
||||
commands.registerCommand('vscode-spring-cli.command.new', handleCommandNew),
|
||||
commands.registerCommand('vscode-spring-cli.command.execute', handleCommandExecute),
|
||||
|
||||
commands.registerCommand('vscode-spring-cli.ai.add', handleAiAdd),
|
||||
|
||||
commands.registerCommand('vscode-spring-cli.guide.apply', handleGuideApply),
|
||||
|
||||
tasks.registerTaskProvider(SPRING_CLI_TASK_TYPE, new CliTaskProvider(CLI))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export async function deactivate(): Promise<void> {
|
||||
}
|
||||
|
||||
|
||||
10
vscode-extensions/vscode-spring-cli/src/guide.ts
Normal file
10
vscode-extensions/vscode-spring-cli/src/guide.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Uri } from "vscode";
|
||||
import { getTargetGuideMardown } from "./utils";
|
||||
import { CLI } from "./extension";
|
||||
|
||||
export async function handleGuideApply(uri: Uri) {
|
||||
if (!uri) {
|
||||
uri = await getTargetGuideMardown();
|
||||
}
|
||||
return CLI.guideApply(uri);
|
||||
}
|
||||
@@ -67,12 +67,12 @@ function getWorkspaceFolderName(file: Uri): string {
|
||||
}
|
||||
|
||||
export async function getTargetPomXml(): Promise<Uri> {
|
||||
if (window.activeTextEditor) {
|
||||
const activeUri = window.activeTextEditor.document.uri;
|
||||
if ("pom.xml" === path.basename(activeUri.path).toLowerCase()) {
|
||||
return activeUri;
|
||||
}
|
||||
}
|
||||
// if (window.activeTextEditor) {
|
||||
// const activeUri = window.activeTextEditor.document.uri;
|
||||
// if ("pom.xml" === path.basename(activeUri.path).toLowerCase()) {
|
||||
// return activeUri;
|
||||
// }
|
||||
// }
|
||||
|
||||
const candidates: Uri[] = await workspace.findFiles("**/pom.xml");
|
||||
if (candidates.length > 0) {
|
||||
@@ -88,6 +88,29 @@ export async function getTargetPomXml(): Promise<Uri> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function getTargetGuideMardown(): Promise<Uri> {
|
||||
if (window.activeTextEditor) {
|
||||
const activeUri = window.activeTextEditor.document.uri;
|
||||
if (/README-\S+.md/.test(path.basename(activeUri.path).toLowerCase())) {
|
||||
return activeUri;
|
||||
}
|
||||
}
|
||||
|
||||
const candidates: Uri[] = await workspace.findFiles("**/README-*.md");
|
||||
if (candidates.length > 0) {
|
||||
if (candidates.length === 1) {
|
||||
return candidates[0];
|
||||
} else {
|
||||
return await window.showQuickPick(
|
||||
candidates.map((c: Uri) => ({ value: c, label: getRelativePathToWorkspaceFolder(c), description: getWorkspaceFolderName(c) })),
|
||||
{ placeHolder: "Select the target project." },
|
||||
).then(res => res && res.value);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
export function mapProjectToQuickPick(project: Project): QuickPickItem {
|
||||
return {
|
||||
label: project.name,
|
||||
@@ -103,3 +126,5 @@ export function getWorkspaceRoot(): Uri | undefined {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user