move Spring Cli commands implementation to language server
This commit is contained in:
@@ -14,6 +14,8 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jgit.diff.Edit;
|
||||
import org.eclipse.jgit.diff.EditList;
|
||||
import org.eclipse.lsp4j.AnnotatedTextEdit;
|
||||
import org.eclipse.lsp4j.CreateFile;
|
||||
import org.eclipse.lsp4j.DeleteFile;
|
||||
@@ -25,8 +27,6 @@ import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.openrewrite.Result;
|
||||
import org.eclipse.jgit.diff.Edit;
|
||||
import org.eclipse.jgit.diff.EditList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
@@ -72,10 +72,10 @@ public class ORDocUtils {
|
||||
|
||||
}
|
||||
|
||||
public static Optional<TextDocumentEdit> computeTextDocEdit(TextDocument doc, Result result, String changeAnnotationId) {
|
||||
TextDocument newDoc = new TextDocument(null, LanguageId.PLAINTEXT, 0, result.getAfter().printAll());
|
||||
public static Optional<TextDocumentEdit> computeTextDocEdit(TextDocument doc, String oldContent, String newContent, String changeAnnotationId) {
|
||||
TextDocument newDoc = new TextDocument(null, LanguageId.PLAINTEXT, 0, newContent);
|
||||
|
||||
EditList diff = JGitUtils.getDiff(result.getBefore().printAll(), newDoc.get());
|
||||
EditList diff = JGitUtils.getDiff(oldContent, newDoc.get());
|
||||
if (!diff.isEmpty()) {
|
||||
TextDocumentEdit edit = new TextDocumentEdit();
|
||||
edit.setTextDocument(new VersionedTextDocumentIdentifier(doc.getUri(), doc.getVersion()));
|
||||
@@ -117,6 +117,7 @@ public class ORDocUtils {
|
||||
log.error("Diff conversion failed", ex);
|
||||
}
|
||||
}
|
||||
System.out.println("edits "+ edit);
|
||||
return Optional.of(edit);
|
||||
}
|
||||
return Optional.empty();
|
||||
@@ -139,7 +140,7 @@ public class ORDocUtils {
|
||||
edit.setEdits(List.of(te));
|
||||
return Optional.of(edit);
|
||||
}
|
||||
return Optional.empty();
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static int getStartOfLine(IDocument doc, int lineNumber) {
|
||||
@@ -163,31 +164,56 @@ public class ORDocUtils {
|
||||
for (Result result : results) {
|
||||
if (result.getBefore() == null) {
|
||||
String docUri = result.getAfter().getSourcePath().toUri().toASCIIString();
|
||||
CreateFile ro = new CreateFile();
|
||||
ro.setUri(docUri);
|
||||
we.getDocumentChanges().add(Either.forRight(ro));
|
||||
|
||||
TextDocumentEdit te = new TextDocumentEdit();
|
||||
te.setTextDocument(new VersionedTextDocumentIdentifier(docUri, 0));
|
||||
Position cursor = new Position(0,0);
|
||||
te.setEdits(List.of(new AnnotatedTextEdit(new Range(cursor, cursor), result.getAfter().printAll(), changeAnnotationId)));
|
||||
we.getDocumentChanges().add(Either.forLeft(te));
|
||||
createNewFileEdit(docUri, result.getAfter().printAll(), changeAnnotationId, we);
|
||||
} else if (result.getAfter() == null) {
|
||||
String docUri = result.getBefore().getSourcePath().toUri().toASCIIString();
|
||||
we.getDocumentChanges().add(Either.forRight(new DeleteFile(docUri)));
|
||||
createDeleteFileEdit(docUri, we);
|
||||
} else {
|
||||
String docUri = result.getBefore().getSourcePath().toUri().toASCIIString();
|
||||
TextDocument doc = documents.getLatestSnapshot(docUri);
|
||||
if (doc == null) {
|
||||
doc = new TextDocument(docUri, null, 0, result.getBefore().printAll());
|
||||
ORDocUtils.computeTextDocEdit(doc, result, changeAnnotationId).ifPresent(te -> we.getDocumentChanges().add(Either.forLeft(te)));
|
||||
} else {
|
||||
ORDocUtils.computeTextDocEdit(doc, result, changeAnnotationId).ifPresent(te -> we.getDocumentChanges().add(Either.forLeft(te)));
|
||||
}
|
||||
System.out.println(result.getBefore().getSourcePath().toString());
|
||||
createUpdateFileEdit(documents, docUri, result.getBefore().printAll(), result.getAfter().printAll(), changeAnnotationId, we);
|
||||
}
|
||||
|
||||
}
|
||||
return Optional.of(we);
|
||||
}
|
||||
|
||||
public static void createWorkspaceEdit(SimpleTextDocumentService documents, String docUri, String oldContent, String newContent, String changeAnnotationId, WorkspaceEdit we) {
|
||||
if(oldContent == null) {
|
||||
createNewFileEdit(docUri, newContent, changeAnnotationId, we);
|
||||
} else if (newContent == null) {
|
||||
createDeleteFileEdit(docUri, we);
|
||||
} else {
|
||||
createUpdateFileEdit(documents, docUri, oldContent, newContent, changeAnnotationId, we);
|
||||
}
|
||||
}
|
||||
|
||||
private static void createNewFileEdit(String docUri, String newContent, String changeAnnotationId,
|
||||
WorkspaceEdit we) {
|
||||
CreateFile ro = new CreateFile();
|
||||
ro.setUri(docUri);
|
||||
we.getDocumentChanges().add(Either.forRight(ro));
|
||||
|
||||
TextDocumentEdit te = new TextDocumentEdit();
|
||||
te.setTextDocument(new VersionedTextDocumentIdentifier(docUri, 0));
|
||||
Position cursor = new Position(0,0);
|
||||
te.setEdits(List.of(new AnnotatedTextEdit(new Range(cursor, cursor), newContent, changeAnnotationId)));
|
||||
we.getDocumentChanges().add(Either.forLeft(te));
|
||||
}
|
||||
|
||||
private static void createDeleteFileEdit(String docUri, WorkspaceEdit we) {
|
||||
we.getDocumentChanges().add(Either.forRight(new DeleteFile(docUri)));
|
||||
}
|
||||
|
||||
private static void createUpdateFileEdit(SimpleTextDocumentService documents, String docUri, String oldContent,
|
||||
String newContent, String changeAnnotationId, WorkspaceEdit we) {
|
||||
TextDocument doc = documents.getLatestSnapshot(docUri);
|
||||
if (doc == null) {
|
||||
doc = new TextDocument(docUri, null, 0, oldContent);
|
||||
ORDocUtils.computeTextDocEdit(doc, oldContent, newContent, changeAnnotationId).ifPresent(te -> we.getDocumentChanges().add(Either.forLeft(te)));
|
||||
} else {
|
||||
ORDocUtils.computeTextDocEdit(doc, oldContent, newContent, changeAnnotationId).ifPresent(te -> we.getDocumentChanges().add(Either.forLeft(te)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -148,6 +148,10 @@
|
||||
<groupId>org.eclipse.lsp4j</groupId>
|
||||
<artifactId>org.eclipse.lsp4j.jsonrpc</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>xml-apis</groupId>
|
||||
<artifactId>xml-apis</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
@@ -201,6 +205,31 @@
|
||||
<artifactId>cron-utils</artifactId>
|
||||
<version>9.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.commonmark</groupId>
|
||||
<artifactId>commonmark</artifactId>
|
||||
<version>0.22.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-model</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- <dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
<version>2.3.1</version>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -49,6 +49,8 @@ import org.springframework.ide.vscode.boot.java.beans.DependsOnDefinitionProvide
|
||||
import org.springframework.ide.vscode.boot.java.beans.NamedDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.QualifierDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ResourceDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.conditionalonresource.ConditionalOnResourceDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.ResponseModifier;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.DataQueryParameterDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtDataQuerySemanticTokensProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
|
||||
@@ -72,7 +74,6 @@ import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.value.ValueDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.conditionalonresource.ConditionalOnResourceDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.jdt.ls.JavaProjectsService;
|
||||
import org.springframework.ide.vscode.boot.jdt.ls.JdtLsProjectCache;
|
||||
import org.springframework.ide.vscode.boot.metadata.AdHocSpringPropertyIndexProvider;
|
||||
@@ -417,4 +418,8 @@ public class BootLanguageServerBootApp {
|
||||
return new ModulithService(server, projectFinder, projectObserver, springIndex, reconciler, config);
|
||||
}
|
||||
|
||||
@Bean ResponseModifier responseModifier() {
|
||||
return new ResponseModifier();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import org.springframework.ide.vscode.boot.java.beans.NamedReferencesProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ProfileReferencesProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.QualifierReferencesProvider;
|
||||
import org.springframework.ide.vscode.boot.java.conditionals.ConditionalsLiveHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.CopilotAgentCommandHandler;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.ResponseModifier;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeLensEngine;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaDocumentHighlightEngine;
|
||||
@@ -110,6 +112,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
private final SpringLiveChangeDetectionWatchdog liveChangeDetectionWatchdog;
|
||||
private final ProjectObserver projectObserver;
|
||||
private final CompilationUnitCache cuCache;
|
||||
private final ResponseModifier responseModifier;
|
||||
|
||||
private JavaProjectFinder projectFinder;
|
||||
private BootJavaHoverProvider hoverProvider;
|
||||
@@ -121,10 +124,11 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
private JdtSemanticTokensHandler semanticTokensHandler;
|
||||
private JdtInlayHintsHandler inlayHintsHandler;
|
||||
private SpelSemanticTokens spelSemanticTokens;
|
||||
|
||||
|
||||
public BootJavaLanguageServerComponents(ApplicationContext appContext) {
|
||||
this.server = appContext.getBean(SimpleLanguageServer.class);
|
||||
this.serverParams = appContext.getBean(BootLanguageServerParams.class);
|
||||
this.responseModifier = appContext.getBean(ResponseModifier.class);
|
||||
|
||||
projectFinder = serverParams.projectFinder;
|
||||
projectObserver = serverParams.projectObserver;
|
||||
@@ -166,6 +170,8 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
// create and handle commands
|
||||
new SpringProcessCommandHandler(server, liveDataService, liveDataLocalProcessConnector, appContext.getBeansOfType(SpringProcessConnectorRemote.class).values());
|
||||
|
||||
new CopilotAgentCommandHandler(server, projectFinder,responseModifier);
|
||||
|
||||
docSymbolProvider = params -> springSymbolIndex.getSymbols(params.getTextDocument().getUri());
|
||||
|
||||
workspaceService.onWorkspaceSymbol(new BootJavaWorkspaceSymbolHandler(springSymbolIndex,
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.InMemoryExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.RecipeRun;
|
||||
import org.openrewrite.Result;
|
||||
import org.openrewrite.SourceFile;
|
||||
import org.openrewrite.internal.InMemoryLargeSourceSet;
|
||||
import org.openrewrite.maven.MavenParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public abstract class AbstractInjectMavenActionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractInjectMavenActionHandler.class);
|
||||
|
||||
protected final TemplateEngine templateEngine;
|
||||
|
||||
protected final Map<String, Object> model;
|
||||
|
||||
protected final Path cwd;
|
||||
|
||||
public AbstractInjectMavenActionHandler(TemplateEngine templateEngine, Map<String, Object> model, Path cwd) {
|
||||
this.templateEngine = templateEngine;
|
||||
this.model = model;
|
||||
this.cwd = cwd;
|
||||
}
|
||||
|
||||
protected static ExecutionContext getExecutionContext() {
|
||||
Consumer<Throwable> onError = e -> {
|
||||
logger.error("error in javaParser execution", e);
|
||||
};
|
||||
return new InMemoryExecutionContext(onError);
|
||||
}
|
||||
|
||||
protected String getTextToUse(String text, String actionName) {
|
||||
if (!StringUtils.hasText(text)) {
|
||||
throw new SpringCliException(actionName + " action does not have a value in the 'text:' field.");
|
||||
}
|
||||
if (this.templateEngine != null) {
|
||||
text = this.templateEngine.process(text, model);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Path getPomPath() {
|
||||
Path pomPath = cwd.resolve("pom.xml");
|
||||
if (Files.notExists(pomPath)) {
|
||||
throw new SpringCliException("Could not find pom.xml in " + this.cwd
|
||||
+ ". Make sure you are running the command in the directory that contains a pom.xml file");
|
||||
}
|
||||
return pomPath;
|
||||
}
|
||||
|
||||
public void exec() {
|
||||
Path pomPath = getPomPath();
|
||||
List<Result> resultList = run().getChangeset().getAllResults();
|
||||
try {
|
||||
for (Result result : resultList) {
|
||||
// write updated file.
|
||||
try (BufferedWriter sourceFileWriter = Files.newBufferedWriter(pomPath, StandardCharsets.UTF_8)) {
|
||||
sourceFileWriter.write(result.getAfter().printAllTrimmed());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new SpringCliException("Error writing to " + pomPath.toAbsolutePath(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
public RecipeRun run() {
|
||||
List<Path> paths = new ArrayList<>();
|
||||
paths.add(getPomPath());
|
||||
MavenParser mavenParser = MavenParser.builder().build();
|
||||
List<SourceFile> parsedPomFiles = mavenParser.parse(paths, cwd, getExecutionContext()).toList();
|
||||
return createRecipe().run(new InMemoryLargeSourceSet(parsedPomFiles), getExecutionContext());
|
||||
}
|
||||
|
||||
protected abstract Recipe createRecipe();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.ExecuteCommandParams;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.j2objc.annotations.Weak;
|
||||
|
||||
public class CopilotAgentCommandHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CopilotAgentCommandHandler.class);
|
||||
|
||||
private static final String CMD_COPILOT_AGENT_ENHANCERESPONSE = "sts/copilot/agent/enhanceResponse";
|
||||
|
||||
private static final String CMD_COPILOT_AGENT_LSPEDITS = "sts/copilot/agent/lspEdits";
|
||||
|
||||
private final SimpleLanguageServer server;
|
||||
private final JavaProjectFinder projectFinder;
|
||||
private final ResponseModifier responseModifier;
|
||||
|
||||
|
||||
public CopilotAgentCommandHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder, ResponseModifier responseModifier) {
|
||||
this.server = server;
|
||||
this.projectFinder = projectFinder;
|
||||
this.responseModifier = responseModifier;
|
||||
registerCommands();
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
server.onCommand(CMD_COPILOT_AGENT_ENHANCERESPONSE, (params) -> {
|
||||
return enhanceResponseHandler(params);
|
||||
});
|
||||
log.info("Registered command handler: {}",CMD_COPILOT_AGENT_ENHANCERESPONSE);
|
||||
|
||||
server.onCommand(CMD_COPILOT_AGENT_LSPEDITS, params -> {
|
||||
try {
|
||||
return createLspEdits(params);
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<Object> enhanceResponseHandler(ExecuteCommandParams params) {
|
||||
log.info("Command Handler: ");
|
||||
String response = ((JsonElement) params.getArguments().get(0)).getAsString();
|
||||
String modifiedResp = responseModifier.modify(response);
|
||||
return CompletableFuture.completedFuture(modifiedResp);
|
||||
}
|
||||
|
||||
private CompletableFuture<WorkspaceEdit> createLspEdits(ExecuteCommandParams params) throws IOException {
|
||||
log.info("Command Handler for lsp edits: ");
|
||||
String docURI = ((JsonElement) params.getArguments().get(0)).getAsString();
|
||||
String path = ((JsonElement) params.getArguments().get(0)).getAsString();
|
||||
String content = ((JsonElement) params.getArguments().get(2)).getAsString();
|
||||
TextDocument doc = server.getTextDocumentService().getLatestSnapshot("file:///Users/vudayani/Desktop/spring-petclinic/README-ai-spring-petclinic.md");
|
||||
|
||||
IJavaProject project = this.projectFinder.find(new TextDocumentIdentifier(docURI)).get();
|
||||
System.out.println("Project path "+project.getLocationUri() + " "+project.getLocationUri().getPath().toString());
|
||||
List<ProjectArtifact> projectArtifacts = computeProjectArtifacts(content);
|
||||
ProjectArtifactEditGenerator editGenerator = new ProjectArtifactEditGenerator(server.getTextDocumentService(), projectArtifacts,
|
||||
Paths.get(project.getLocationUri()), docURI);
|
||||
// Paths.get(project.getLocationUri()), docURI);
|
||||
WorkspaceEdit we = editGenerator.process().getResult();
|
||||
System.out.println("Final: \n "+ we.toString());
|
||||
return CompletableFuture.completedFuture(we);
|
||||
}
|
||||
|
||||
List<ProjectArtifact> computeProjectArtifacts(String response) {
|
||||
ProjectArtifactCreator projectArtifactCreator = new ProjectArtifactCreator();
|
||||
List<ProjectArtifact> projectArtifacts = projectArtifactCreator.create(response);
|
||||
return projectArtifacts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.config.DeclarativeRecipe;
|
||||
import org.openrewrite.maven.MavenParser;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.recipe.AddDependencyRecipeFactory;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.recipe.AddManagedDependencyRecipeFactory;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.recipe.AddPluginRecipeFactory;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.recipe.InjectTextMavenRepositoryRecipe;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.util.MavenBuildPluginReader;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.util.MavenDependencyReader;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.util.MavenRepositoryReader;
|
||||
|
||||
public class InjectMavenActionHandler extends AbstractInjectMavenActionHandler {
|
||||
|
||||
private List<InjectMavenDependency> dependencies;
|
||||
|
||||
private List<InjectMavenBuildPlugin> buildPlugins;
|
||||
|
||||
private List<InjectMavenRepository> repositories;
|
||||
|
||||
private List<InjectMavenDependencyManagement> dependencyManagements;
|
||||
|
||||
public InjectMavenActionHandler(TemplateEngine templateEngine, Map<String, Object> model, Path cwd) {
|
||||
super(templateEngine, model, cwd);
|
||||
this.dependencies = new ArrayList<>();
|
||||
this.buildPlugins = new ArrayList<>();
|
||||
this.repositories = new ArrayList<>();
|
||||
this.dependencyManagements = new ArrayList<>();
|
||||
}
|
||||
|
||||
public boolean injectBuildPlugin(InjectMavenBuildPlugin buildPlugin) {
|
||||
return buildPlugins.add(buildPlugin);
|
||||
}
|
||||
|
||||
public boolean injectDependency(InjectMavenDependency dependency) {
|
||||
return dependencies.add(dependency);
|
||||
}
|
||||
|
||||
public boolean injectRepository(InjectMavenRepository repository) {
|
||||
return repositories.add(repository);
|
||||
}
|
||||
|
||||
public boolean injectDependencyManagement(InjectMavenDependencyManagement dependencyManagement) {
|
||||
return dependencyManagements.add(dependencyManagement);
|
||||
}
|
||||
|
||||
protected Recipe createRecipe() {
|
||||
DeclarativeRecipe aggregateRecipe = new DeclarativeRecipe("spring.cli.ai.MavenUpdates",
|
||||
"Add Pom changes from AI", "", Collections.emptySet(), null, null, false, Collections.emptyList());
|
||||
MavenParser mavenParser = MavenParser.builder().build();
|
||||
for (InjectMavenDependency d : dependencies) {
|
||||
String text = getTextToUse(d.getText(), "Inject Maven Dependency");
|
||||
MavenDependencyReader mavenDependencyReader = new MavenDependencyReader();
|
||||
String[] mavenDependencies = mavenDependencyReader.parseMavenSection(text);
|
||||
for (String md : mavenDependencies) {
|
||||
aggregateRecipe.getRecipeList().add(new AddDependencyRecipeFactory().create(md));
|
||||
}
|
||||
}
|
||||
for (InjectMavenBuildPlugin p : buildPlugins) {
|
||||
String text = getTextToUse(p.getText(), "Inject Maven Build Plugin");
|
||||
MavenBuildPluginReader mavenBuildPluginReader = new MavenBuildPluginReader();
|
||||
String[] buildPlugins = mavenBuildPluginReader.parseMavenSection(text);
|
||||
for (String mp : buildPlugins) {
|
||||
aggregateRecipe.getRecipeList().add(new AddPluginRecipeFactory().create(mp));
|
||||
}
|
||||
}
|
||||
for (InjectMavenRepository r : repositories) {
|
||||
String text = getTextToUse(r.getText(), "Inject Maven Repository");
|
||||
MavenRepositoryReader mavenRepositoryReader = new MavenRepositoryReader();
|
||||
String[] mavenRepositories = mavenRepositoryReader.parseMavenSection(text);
|
||||
for (String mr : mavenRepositories) {
|
||||
aggregateRecipe.getRecipeList().add(new InjectTextMavenRepositoryRecipe(mr));
|
||||
}
|
||||
}
|
||||
for (InjectMavenDependencyManagement dm : dependencyManagements) {
|
||||
String text = getTextToUse(dm.getText(), "Inject Maven Dependency Management");
|
||||
MavenDependencyReader mavenDependencyReader = new MavenDependencyReader();
|
||||
String[] mavenDependencyManagements = mavenDependencyReader.parseMavenSection(text);
|
||||
for (String mdm : mavenDependencyManagements) {
|
||||
aggregateRecipe.getRecipeList().add(new AddManagedDependencyRecipeFactory().create(mdm));
|
||||
}
|
||||
}
|
||||
return aggregateRecipe;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Mark Pollack
|
||||
*/
|
||||
public class InjectMavenBuildPlugin {
|
||||
|
||||
private String text;
|
||||
|
||||
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
|
||||
public InjectMavenBuildPlugin(@JsonProperty("text") String text) {
|
||||
this.text = Objects.requireNonNull(text);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InjectMavenBuildPlugin{" + "text='" + text + '\'' + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Mark Pollack
|
||||
*/
|
||||
public class InjectMavenDependency {
|
||||
|
||||
private String text;
|
||||
|
||||
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
|
||||
public InjectMavenDependency(@JsonProperty("text") String text) {
|
||||
this.text = Objects.requireNonNull(text);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InjectMavenDependency{" + "text='" + text + '\'' + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Mark Pollack
|
||||
*/
|
||||
public class InjectMavenDependencyManagement {
|
||||
|
||||
private String text;
|
||||
|
||||
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
|
||||
public InjectMavenDependencyManagement(@JsonProperty("text") String text) {
|
||||
this.text = Objects.requireNonNull(text);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InjectMavenDependency{" + "text='" + text + '\'' + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Mark Pollack
|
||||
*/
|
||||
public class InjectMavenRepository {
|
||||
|
||||
private String text;
|
||||
|
||||
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
|
||||
public InjectMavenRepository(@JsonProperty("text") String text) {
|
||||
this.text = Objects.requireNonNull(text);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InjectMavenRepository{" + "text='" + text + '\'' + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.commonmark.node.AbstractVisitor;
|
||||
import org.commonmark.node.FencedCodeBlock;
|
||||
|
||||
public class MarkdownResponseVisitor extends AbstractVisitor {
|
||||
|
||||
private List<ProjectArtifact> projectArtifacts = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void visit(FencedCodeBlock fencedCodeBlock) {
|
||||
String info = fencedCodeBlock.getInfo();
|
||||
String code = fencedCodeBlock.getLiteral();
|
||||
if (info.equalsIgnoreCase("java")) {
|
||||
addJavaCode(code);
|
||||
}
|
||||
if (info.equalsIgnoreCase("xml")) {
|
||||
addMavenDependencies(code);
|
||||
}
|
||||
if (info.equalsIgnoreCase("properties")) {
|
||||
addApplicationProperties(code);
|
||||
}
|
||||
if (info.equalsIgnoreCase("html")) {
|
||||
addHtml(code);
|
||||
}
|
||||
if (info.isBlank()) {
|
||||
// sometimes the response doesn't contain
|
||||
if (code.contains("package")) {
|
||||
addJavaCode(code);
|
||||
}
|
||||
else if (code.contains("<dependency>")) {
|
||||
addMavenDependencies(code);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void addHtml(String code) {
|
||||
addIfNotPresent(new ProjectArtifact(ProjectArtifactType.HTML, code));
|
||||
}
|
||||
|
||||
private void addApplicationProperties(String code) {
|
||||
addIfNotPresent(new ProjectArtifact(ProjectArtifactType.APPLICATION_PROPERTIES, code));
|
||||
}
|
||||
|
||||
private void addMavenDependencies(String code) {
|
||||
addIfNotPresent(new ProjectArtifact(ProjectArtifactType.MAVEN_DEPENDENCIES, code));
|
||||
}
|
||||
|
||||
private void addJavaCode(String code) {
|
||||
if (code.contains("@SpringBootApplication") && code.contains("SpringApplication.run")) {
|
||||
addIfNotPresent(new ProjectArtifact(ProjectArtifactType.MAIN_CLASS, code));
|
||||
}
|
||||
else if (code.contains("@Test")) {
|
||||
addIfNotPresent(new ProjectArtifact(ProjectArtifactType.TEST_CODE, code));
|
||||
}
|
||||
else {
|
||||
addIfNotPresent(new ProjectArtifact(ProjectArtifactType.SOURCE_CODE, code));
|
||||
}
|
||||
}
|
||||
|
||||
private void addIfNotPresent(ProjectArtifact projectArtifact) {
|
||||
// TODO - investigate why Node has duplicate entries
|
||||
if (!this.projectArtifacts.contains(projectArtifact)) {
|
||||
this.projectArtifacts.add(projectArtifact);
|
||||
}
|
||||
else {
|
||||
// System.out.println("duplicate project artifact :(");
|
||||
}
|
||||
}
|
||||
|
||||
public List<ProjectArtifact> getProjectArtifacts() {
|
||||
return this.projectArtifacts;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ProcessArtifactResult<T> {
|
||||
|
||||
List<ProjectArtifact> notProcessedArtifacts = new ArrayList<>();
|
||||
|
||||
private T result;
|
||||
|
||||
public List<ProjectArtifact> getNotProcessedArtifacts() {
|
||||
return notProcessedArtifacts;
|
||||
}
|
||||
|
||||
public void addToNotProcessed(ProjectArtifact projectArtifact) {
|
||||
notProcessedArtifacts.add(projectArtifact);
|
||||
}
|
||||
|
||||
public T getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
void setResult(T result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ProjectArtifact {
|
||||
|
||||
private ProjectArtifactType artifactType;
|
||||
|
||||
private String text;
|
||||
|
||||
public ProjectArtifact(ProjectArtifactType artifactType, String text) {
|
||||
this.artifactType = artifactType;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public ProjectArtifactType getArtifactType() {
|
||||
return artifactType;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ProjectArtifact that = (ProjectArtifact) o;
|
||||
return artifactType == that.artifactType && Objects.equals(text, that.text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(artifactType, text);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
|
||||
public class ProjectArtifactCreator {
|
||||
|
||||
public List<ProjectArtifact> create(String response) {
|
||||
Parser parser = Parser.builder().build();
|
||||
Node node = parser.parse(response);
|
||||
|
||||
MarkdownResponseVisitor markdownResponseVisitor = new MarkdownResponseVisitor();
|
||||
node.accept(markdownResponseVisitor);
|
||||
|
||||
return markdownResponseVisitor.getProjectArtifacts();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.maven.model.Dependency;
|
||||
import org.apache.maven.model.Model;
|
||||
import org.eclipse.lsp4j.ChangeAnnotation;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.openrewrite.Result;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.util.ClassNameExtractor;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.util.MavenDependencyReader;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.util.PomReader;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.rewrite.ORDocUtils;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class ProjectArtifactEditGenerator {
|
||||
|
||||
private final List<ProjectArtifact> projectArtifacts;
|
||||
|
||||
private final Path projectPath;
|
||||
|
||||
private final String readmeFileName;
|
||||
|
||||
private final Pattern compiledGroupIdPattern;
|
||||
|
||||
private final Pattern compiledArtifactIdPattern;
|
||||
|
||||
private final SimpleTextDocumentService simpleTextDocumentService;
|
||||
|
||||
public ProjectArtifactEditGenerator(SimpleTextDocumentService simpleTextDocumentService, List<ProjectArtifact> projectArtifacts, Path projectPath, String readmeFileName) {
|
||||
this.simpleTextDocumentService = simpleTextDocumentService;
|
||||
this.projectArtifacts = projectArtifacts;
|
||||
this.projectPath = projectPath;
|
||||
this.readmeFileName = readmeFileName;
|
||||
compiledGroupIdPattern = Pattern.compile("<groupId>(.*?)</groupId>");
|
||||
compiledArtifactIdPattern = Pattern.compile("<artifactId>(.*?)</artifactId>");
|
||||
}
|
||||
|
||||
public ProcessArtifactResult<WorkspaceEdit> process() throws IOException {
|
||||
return processArtifacts(projectArtifacts, projectPath);
|
||||
}
|
||||
|
||||
private ProcessArtifactResult<WorkspaceEdit> processArtifacts(List<ProjectArtifact> projectArtifacts,
|
||||
Path projectPath) throws IOException {
|
||||
ProcessArtifactResult<WorkspaceEdit> processArtifactResult = new ProcessArtifactResult<>();
|
||||
String changeAnnotationId = UUID.randomUUID().toString();
|
||||
WorkspaceEdit we = new WorkspaceEdit();
|
||||
ChangeAnnotation changeAnnotation = new ChangeAnnotation();
|
||||
changeAnnotation.setNeedsConfirmation(true);
|
||||
we.setDocumentChanges(new ArrayList<>());
|
||||
we.setChangeAnnotations(Map.of(changeAnnotationId, changeAnnotation));
|
||||
for (ProjectArtifact projectArtifact : projectArtifacts) {
|
||||
// try {
|
||||
ProjectArtifactType artifactType = projectArtifact.getArtifactType();
|
||||
switch (artifactType) {
|
||||
case SOURCE_CODE:
|
||||
writeSourceCode(projectArtifact, projectPath, changeAnnotationId, we);
|
||||
break;
|
||||
case TEST_CODE:
|
||||
writeTestCode(projectArtifact, projectPath, changeAnnotationId, we);
|
||||
break;
|
||||
case MAVEN_DEPENDENCIES:
|
||||
writeMavenDependencies(projectArtifact, projectPath,changeAnnotationId, we);
|
||||
break;
|
||||
case APPLICATION_PROPERTIES:
|
||||
writeApplicationProperties(projectArtifact, projectPath, changeAnnotationId, we);
|
||||
break;
|
||||
case MAIN_CLASS:
|
||||
updateMainApplicationClassAnnotations(projectArtifact, projectPath,
|
||||
changeAnnotationId, we);
|
||||
break;
|
||||
case HTML:
|
||||
writeHtml(projectArtifact, projectPath, changeAnnotationId, we);
|
||||
break;
|
||||
default:
|
||||
processArtifactResult.addToNotProcessed(projectArtifact);
|
||||
break;
|
||||
}
|
||||
}
|
||||
processArtifactResult.setResult(we);
|
||||
return processArtifactResult;
|
||||
}
|
||||
|
||||
private void writeSourceCode(ProjectArtifact projectArtifact, Path projectPath,
|
||||
String changeAnnotationId, WorkspaceEdit we) throws IOException {
|
||||
String packageName = this.calculatePackageForArtifact(projectArtifact);
|
||||
ClassNameExtractor classNameExtractor = new ClassNameExtractor();
|
||||
Optional<String> className = classNameExtractor.extractClassName(projectArtifact.getText());
|
||||
if (className.isPresent()) {
|
||||
Path output = resolveSourceFile(projectPath, packageName, className.get() + ".java");
|
||||
ORDocUtils.createWorkspaceEdit(simpleTextDocumentService, output.toUri().toASCIIString(), getFileContent(output), projectArtifact.getText(), changeAnnotationId, we);
|
||||
}
|
||||
}
|
||||
|
||||
private String getFileContent(Path file) throws IOException {
|
||||
TextDocument doc = simpleTextDocumentService.getLatestSnapshot(file.toUri().toASCIIString());
|
||||
if (doc != null) {
|
||||
return doc.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void writeTestCode(ProjectArtifact projectArtifact, Path projectPath,
|
||||
String changeAnnotationId, WorkspaceEdit we) throws IOException {
|
||||
// TODO parameterize better to reduce code duplication
|
||||
String packageName = this.calculatePackageForArtifact(projectArtifact);
|
||||
ClassNameExtractor classNameExtractor = new ClassNameExtractor();
|
||||
Optional<String> className = classNameExtractor.extractClassName(projectArtifact.getText());
|
||||
if (className.isPresent()) {
|
||||
Path output = resolveTestFile(projectPath, packageName, className.get() + ".java");
|
||||
ORDocUtils.createWorkspaceEdit(simpleTextDocumentService, output.toUri().toASCIIString(), getFileContent(output), projectArtifact.getText(), changeAnnotationId, we);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeMavenDependencies(ProjectArtifact projectArtifact, Path projectPath, String changeAnnotationId, WorkspaceEdit we) {
|
||||
PomReader pomReader = new PomReader();
|
||||
Path currentProjectPomPath = this.projectPath.resolve("pom.xml");
|
||||
if (Files.notExists(currentProjectPomPath)) {
|
||||
throw new SpringCliException("Could not find pom.xml in " + this.projectPath
|
||||
+ ". Make sure you are running the command in the project's root directory.");
|
||||
}
|
||||
Model currentModel = pomReader.readPom(currentProjectPomPath.toFile());
|
||||
List<Dependency> currentDependencies = currentModel.getDependencies();
|
||||
|
||||
MavenDependencyReader mavenDependencyReader = new MavenDependencyReader();
|
||||
// projectArtifact.getText() contains a list of <dependency> elements
|
||||
String[] mavenDependencies = mavenDependencyReader.parseMavenSection(projectArtifact.getText());
|
||||
|
||||
InjectMavenActionHandler injectMavenActionHandler = new InjectMavenActionHandler(null, new HashMap<>(),
|
||||
projectPath);
|
||||
|
||||
for (String candidateDependencyText : mavenDependencies) {
|
||||
if (!candidateDependencyAlreadyPresent(getProjectDependency(candidateDependencyText),
|
||||
currentDependencies)) {
|
||||
injectMavenActionHandler.injectDependency(new InjectMavenDependency(candidateDependencyText));
|
||||
}
|
||||
}
|
||||
|
||||
List<Result> res = injectMavenActionHandler.run().getChangeset().getAllResults();
|
||||
if(!res.isEmpty()) {
|
||||
WorkspaceEdit workspaceEdit = ORDocUtils.createWorkspaceEdit(simpleTextDocumentService, res, changeAnnotationId).get();
|
||||
we.getDocumentChanges().addAll(workspaceEdit.getDocumentChanges());
|
||||
}
|
||||
// return res.isEmpty() ? Collections.emptyList() :
|
||||
// ORDocUtils.createWorkspaceEdit(simpleTextDocumentService, res, changeAnnotationId).get();
|
||||
// convertToEdits(res, changeAnnotationId);
|
||||
}
|
||||
|
||||
// private List<Lsp.ChangeOperation> convertToEdits(List<Result> allResults, String changeAnnotationId) {
|
||||
// List<Lsp.ChangeOperation> edits = new ArrayList<>();
|
||||
// for (Result res : allResults) {
|
||||
// Path p = (res.getBefore() == null) ? res.getAfter().getSourcePath() : res.getBefore().getSourcePath();
|
||||
// String uri = p.toUri().toASCIIString();
|
||||
// ConversionUtils
|
||||
// .computeTextDocEdit(uri, res.getBefore().printAll(), res.getAfter().printAll(), changeAnnotationId)
|
||||
// .ifPresent(edits::add);
|
||||
// }
|
||||
// return edits;
|
||||
// }
|
||||
|
||||
private boolean candidateDependencyAlreadyPresent(ProjectDependency toMergeDependency,
|
||||
List<Dependency> currentDependencies) {
|
||||
String candidateGroupId = toMergeDependency.getGroupId();
|
||||
String candidateArtifactId = toMergeDependency.getArtifactId();
|
||||
boolean candidateDependencyAlreadyPresent = false;
|
||||
for (Dependency currentDependency : currentDependencies) {
|
||||
String currentGroupId = currentDependency.getGroupId();
|
||||
String currentArtifactId = currentDependency.getArtifactId();
|
||||
if (candidateGroupId.equals(currentGroupId) && candidateArtifactId.equals(currentArtifactId)) {
|
||||
candidateDependencyAlreadyPresent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return candidateDependencyAlreadyPresent;
|
||||
|
||||
}
|
||||
|
||||
private ProjectDependency getProjectDependency(String xml) {
|
||||
String groupId = null;
|
||||
String artifactId = null;
|
||||
try {
|
||||
groupId = extractValue(xml, this.compiledGroupIdPattern);
|
||||
artifactId = extractValue(xml, this.compiledArtifactIdPattern);
|
||||
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new SpringCliException("Exception processing dependency: " + xml, ex);
|
||||
}
|
||||
if (groupId == null || artifactId == null) {
|
||||
throw new SpringCliException("Could not process dependency: " + xml);
|
||||
}
|
||||
return new ProjectDependency(groupId, artifactId);
|
||||
}
|
||||
|
||||
private static String extractValue(String xml, Pattern compiledPattern) {
|
||||
Matcher matcher = compiledPattern.matcher(xml);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void writeApplicationProperties(ProjectArtifact projectArtifact, Path projectPath,
|
||||
String changeAnnotationId, WorkspaceEdit we) throws IOException {
|
||||
Path applicationPropertiesPath = projectPath.resolve("src")
|
||||
.resolve("main")
|
||||
.resolve("resources")
|
||||
.resolve("application.properties");
|
||||
|
||||
Properties srcProperties = new Properties();
|
||||
Properties destProperties = new Properties();
|
||||
srcProperties.load(IOUtils.toInputStream(projectArtifact.getText(), StandardCharsets.UTF_8));
|
||||
if (Files.exists(applicationPropertiesPath)) {
|
||||
destProperties.load(new FileInputStream(applicationPropertiesPath.toFile()));
|
||||
}
|
||||
Properties mergedProperties = PropertyFileUtils.mergeProperties(srcProperties, destProperties);
|
||||
|
||||
StringWriter sw = new StringWriter();
|
||||
mergedProperties.store(sw, "updated by spring ai add");
|
||||
sw.flush();
|
||||
String newContent = sw.getBuffer().toString();
|
||||
ORDocUtils.createWorkspaceEdit(simpleTextDocumentService, applicationPropertiesPath.toUri().toASCIIString(), getFileContent(applicationPropertiesPath), newContent, changeAnnotationId, we);
|
||||
}
|
||||
|
||||
private void updateMainApplicationClassAnnotations(ProjectArtifact projectArtifact,
|
||||
Path projectPath, String changeAnnotationId, WorkspaceEdit we) {
|
||||
// TODO mer
|
||||
// return Collections.emptyList();
|
||||
}
|
||||
|
||||
private void writeHtml(ProjectArtifact projectArtifact, Path projectPath,
|
||||
String changeAnnotationId, WorkspaceEdit we) throws IOException {
|
||||
String html = projectArtifact.getText();
|
||||
String fileName = extractFilenameFromComment(html);
|
||||
if (fileName != null) {
|
||||
Path htmlFile = projectPath.resolve(fileName);
|
||||
ORDocUtils.createWorkspaceEdit(simpleTextDocumentService, fileName, getFileContent(htmlFile), projectArtifact.getText(), changeAnnotationId, we);
|
||||
}
|
||||
}
|
||||
|
||||
private String calculatePackageForArtifact(ProjectArtifact projectArtifact) {
|
||||
String packageToUse = "com.example.ai";
|
||||
try (BufferedReader reader = new BufferedReader(new StringReader(projectArtifact.getText()))) {
|
||||
String firstLine = reader.readLine();
|
||||
if (firstLine.contains("package")) {
|
||||
String regex = "^package\\s+([a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*);";
|
||||
Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
|
||||
Matcher matcher = pattern.matcher(firstLine);
|
||||
// Find the package statement and extract the package name
|
||||
String packageName = "";
|
||||
if (matcher.find()) {
|
||||
packageToUse = matcher.group(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new SpringCliException(
|
||||
"Could not parse package name from Project Artifact: " + projectArtifact.getText(), ex);
|
||||
}
|
||||
return packageToUse;
|
||||
}
|
||||
|
||||
private static String extractFilenameFromComment(String content) {
|
||||
String commentPattern = "<!--\\s*filename:\\s*(\\S+\\.html)\\s*-->";
|
||||
Pattern pattern = Pattern.compile(commentPattern);
|
||||
Matcher matcher = pattern.matcher(content);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Path resolveSourceFile(Path projectPath, String packageName, String fileName) {
|
||||
Path sourceDirectory = projectPath.resolve("src").resolve("main").resolve("java");
|
||||
return resolvePackage(sourceDirectory, packageName).resolve(fileName);
|
||||
}
|
||||
|
||||
public Path resolveTestFile(Path projectPath, String packageName, String fileName) {
|
||||
Path sourceDirectory = projectPath.resolve("src").resolve("test").resolve("java");
|
||||
return resolvePackage(sourceDirectory, packageName).resolve(fileName);
|
||||
}
|
||||
|
||||
private static Path resolvePackage(Path directory, String packageName) {
|
||||
return directory.resolve(packageName.replace('.', '/'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
/**
|
||||
* The type of artifacts returned from an AI generated response to create code
|
||||
*/
|
||||
public enum ProjectArtifactType {
|
||||
|
||||
APPLICATION_PROPERTIES,
|
||||
|
||||
MAIN_CLASS,
|
||||
|
||||
SOURCE_CODE,
|
||||
|
||||
TEST_CODE,
|
||||
|
||||
MAVEN_DEPENDENCIES,
|
||||
|
||||
HTML
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
public class ProjectDependency {
|
||||
|
||||
private final String groupId;
|
||||
|
||||
private final String artifactId;
|
||||
|
||||
public ProjectDependency(String groupId, String artifactId) {
|
||||
this.groupId = groupId;
|
||||
this.artifactId = artifactId;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public String getArtifactId() {
|
||||
return artifactId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProjectDependency{" + "groupId='" + groupId + '\'' + ", artifactId='" + artifactId + '\'' + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
public final class PropertyFileUtils {
|
||||
|
||||
private PropertyFileUtils() {
|
||||
}
|
||||
|
||||
public static Properties getPropertyFile() {
|
||||
File homeDir = new File(System.getProperty("user.home"));
|
||||
File propertyFile = new File(homeDir, ".openai");
|
||||
Properties props = new Properties();
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(propertyFile);
|
||||
props.load(in);
|
||||
return props;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new SpringCliException("Could not load property file ~/.openai", ex);
|
||||
}
|
||||
finally {
|
||||
IOUtils.closeQuietly(in);
|
||||
}
|
||||
}
|
||||
|
||||
public static Properties mergeProperties(Properties... properties) {
|
||||
Properties mergedProperties = new Properties();
|
||||
for (Properties property : properties) {
|
||||
mergedProperties.putAll(property);
|
||||
}
|
||||
return mergedProperties;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
|
||||
public class ResponseModifier {
|
||||
|
||||
public String modify(String response) {
|
||||
System.out.println("In response modifier !!");
|
||||
return modifyMsyqlDependency(modifyJavax(response));
|
||||
}
|
||||
|
||||
private String modifyJavax(String response) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("*Note: The code provided is just an example and may not be suitable for production use.*");
|
||||
sb.append(System.lineSeparator());
|
||||
sb.append(System.lineSeparator());
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
|
||||
sb.append("Generated on " + LocalDateTime.now().format(formatter));
|
||||
sb.append(System.lineSeparator());
|
||||
sb.append(System.lineSeparator());
|
||||
sb.append(response);
|
||||
String code = sb.toString();
|
||||
|
||||
if (!code.contains("import javax")) {
|
||||
return code;
|
||||
}
|
||||
return replaceImportStatements(code);
|
||||
}
|
||||
|
||||
private static String replaceImportStatements(String content) {
|
||||
String[] lines = content.split(System.lineSeparator());
|
||||
StringBuilder updatedContent = new StringBuilder();
|
||||
|
||||
for (String line : lines) {
|
||||
if (line.trim().startsWith("import ")) {
|
||||
String packageName = extractPackageName(line);
|
||||
if (!packageName.contains("javax.sql.")) {
|
||||
packageName = packageName.replace("javax", "jakarta");
|
||||
}
|
||||
line = "import " + packageName + ";";
|
||||
}
|
||||
updatedContent.append(line).append(System.lineSeparator());
|
||||
}
|
||||
return updatedContent.toString();
|
||||
}
|
||||
|
||||
private static String extractPackageName(String importStatement) {
|
||||
return importStatement.replace("import", "").replace(";", "").trim();
|
||||
}
|
||||
|
||||
private String modifyMsyqlDependency(String response) {
|
||||
if (!response.contains("<artifactId>mysql-connector-java</artifactId>")) {
|
||||
return response;
|
||||
}
|
||||
else {
|
||||
String s1 = response.replace("<groupId>mysql</groupId>", "<groupId>com.mysql</groupId>");
|
||||
String s2 = s1.replace("<artifactId>mysql-connector-java</artifactId>",
|
||||
"<artifactId>mysql-connector-j</artifactId>");
|
||||
return s2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class SpringCliException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Instantiates a new {@code UpException}.
|
||||
* @param message the message
|
||||
*/
|
||||
public SpringCliException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new {@code UpException}.
|
||||
* @param message the message
|
||||
* @param cause the cause
|
||||
*/
|
||||
public SpringCliException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface TemplateEngine {
|
||||
|
||||
String process(String template, Map context);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
/**
|
||||
* @author Fabian Krüger
|
||||
*/
|
||||
public class AbstractRecipeFactory {
|
||||
|
||||
private static XmlMapper mapper = new XmlMapper();
|
||||
|
||||
@org.jetbrains.annotations.Nullable
|
||||
protected static String getNullOrTextValue(JsonNode jsonNode, String field) {
|
||||
return (jsonNode.get(field) != null) ? AbstractRecipeFactory.getTextValue(jsonNode, field) : null;
|
||||
}
|
||||
|
||||
protected static String getTextValue(JsonNode jsonNode, String field) {
|
||||
try {
|
||||
JsonNode xmlNode = jsonNode.get(field);
|
||||
return xmlNode.textValue();
|
||||
}
|
||||
catch (NullPointerException npe) {
|
||||
throw new RecipeCreationException(
|
||||
"Could not get text value for field '%s' from: \n%s".formatted(field, jsonNode.toPrettyString()));
|
||||
}
|
||||
}
|
||||
|
||||
protected static JsonNode getJsonNode(String mavenDependencySnippet) throws JsonProcessingException {
|
||||
JsonNode jsonNode = mapper.readTree(mavenDependencySnippet);
|
||||
return jsonNode;
|
||||
}
|
||||
|
||||
protected String getTextOrDefaultValue(JsonNode jsonNode, String version, String defaultValue) {
|
||||
String nullOrTextValue = getNullOrTextValue(jsonNode, version);
|
||||
return (nullOrTextValue != null) ? nullOrTextValue : defaultValue;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.TreeVisitor;
|
||||
import org.openrewrite.internal.lang.Nullable;
|
||||
import org.openrewrite.maven.AddDependencyVisitor;
|
||||
import org.openrewrite.maven.table.MavenMetadataFailures;
|
||||
|
||||
/**
|
||||
* Alternative to {@link org.openrewrite.maven.AddDependency} not doing any version
|
||||
* checks. It uses the {@link AddDependencyVisitor} and bypasses all checks and other code
|
||||
* in {@link org.openrewrite.maven.AddManagedDependency}.
|
||||
*
|
||||
* @author Fabian Krüger
|
||||
*/
|
||||
public class AddDependencyRecipe extends Recipe {
|
||||
|
||||
private final String groupId;
|
||||
|
||||
private final String artifactId;
|
||||
|
||||
private final String version;
|
||||
|
||||
private final String scope;
|
||||
|
||||
private final String type;
|
||||
|
||||
private final @Nullable String classifier;
|
||||
|
||||
private final @Nullable Boolean optional;
|
||||
|
||||
private final Pattern familyRegex;
|
||||
|
||||
private final MavenMetadataFailures metadataFailures;
|
||||
|
||||
public AddDependencyRecipe(String groupId, String artifactId, String version, String scope, String type,
|
||||
@Nullable String classifier, @Nullable Boolean optional, Pattern familyRegex,
|
||||
MavenMetadataFailures metadataFailures) {
|
||||
this.groupId = groupId;
|
||||
this.artifactId = artifactId;
|
||||
this.version = version;
|
||||
this.scope = scope;
|
||||
this.type = type;
|
||||
this.classifier = classifier;
|
||||
this.optional = optional;
|
||||
this.familyRegex = familyRegex;
|
||||
this.metadataFailures = metadataFailures;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Add dependency '%s:%s'".formatted(groupId, artifactId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TreeVisitor<?, ExecutionContext> getVisitor() {
|
||||
AddDependencyVisitor addDependencyVisitor = new AddDependencyVisitor(groupId, artifactId, version, null, scope,
|
||||
true, type, classifier, optional, familyRegex, metadataFailures);
|
||||
return addDependencyVisitor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2021-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openrewrite.internal.lang.Nullable;
|
||||
import org.openrewrite.maven.AddDependency;
|
||||
import org.openrewrite.maven.table.MavenMetadataFailures;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
* @author Fabian Krüger
|
||||
*/
|
||||
public class AddDependencyRecipeFactory extends AbstractRecipeFactory {
|
||||
|
||||
/**
|
||||
* Create {@link AddDependency} recipe from Maven dependency XML snippet.
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* <dependency>
|
||||
* <groupId>groupId</groupId>
|
||||
* <artifactId>artifactId</artifactId>
|
||||
* <version>${some.version}</version>
|
||||
* <classifier>classifier</classifier>
|
||||
* <scope>scope</scope>
|
||||
* <type>pom</type>
|
||||
* <optional>true</optional>
|
||||
* </dependency>
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public AddDependencyRecipe create(String mavenDependency) {
|
||||
try {
|
||||
JsonNode jsonNode = getJsonNode(mavenDependency);
|
||||
String groupId = getTextValue(jsonNode, "groupId");
|
||||
String artifactId = getTextValue(jsonNode, "artifactId");
|
||||
String version = getTextOrDefaultValue(jsonNode, "version", "latest");
|
||||
@Nullable
|
||||
String scope = getNullOrTextValue(jsonNode, "scope");
|
||||
@Nullable
|
||||
String type = getNullOrTextValue(jsonNode, "type");
|
||||
@Nullable
|
||||
String classifier = getNullOrTextValue(jsonNode, "classifier");
|
||||
@Nullable
|
||||
Boolean optional = Boolean.parseBoolean(getNullOrTextValue(jsonNode, "optional"));
|
||||
@Nullable
|
||||
String familyPattern = null;
|
||||
Pattern familyRegex = (familyPattern != null) ? Pattern.compile(familyPattern) : null;
|
||||
MavenMetadataFailures metadataFailures = null;
|
||||
|
||||
AddDependencyRecipe recipe = new AddDependencyRecipe(groupId, artifactId, version, scope, type, classifier,
|
||||
optional, familyRegex, metadataFailures);
|
||||
return recipe;
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.TreeVisitor;
|
||||
import org.openrewrite.maven.AddManagedDependencyVisitor;
|
||||
|
||||
/**
|
||||
* Alternative to {@link org.openrewrite.maven.AddManagedDependency} that doesn't verify
|
||||
* the provided version. It uses the {@link AddManagedDependencyVisitor} and bypasses all
|
||||
* checks and other code in {@link org.openrewrite.maven.AddManagedDependency}.
|
||||
*
|
||||
* @author Fabian Krüger
|
||||
*/
|
||||
public class AddManagedDependencyRecipe extends Recipe {
|
||||
|
||||
private final String groupId;
|
||||
|
||||
private final String artifactId;
|
||||
|
||||
private final String version;
|
||||
|
||||
private final String scope;
|
||||
|
||||
private final String type;
|
||||
|
||||
private final String classifier;
|
||||
|
||||
public AddManagedDependencyRecipe(String groupId, String artifactId, String version, String scope, String type,
|
||||
String classifier) {
|
||||
this.groupId = groupId;
|
||||
this.artifactId = artifactId;
|
||||
this.version = version;
|
||||
this.scope = scope;
|
||||
this.type = type;
|
||||
this.classifier = classifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Add managed dependency '%s:%s'".formatted(groupId, artifactId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TreeVisitor<?, ExecutionContext> getVisitor() {
|
||||
return new AddManagedDependencyVisitor(groupId, artifactId, version, scope, type, classifier);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.apache.maven.model.Dependency;
|
||||
import org.openrewrite.internal.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Fabian Krüger
|
||||
*/
|
||||
public class AddManagedDependencyRecipeFactory extends AbstractRecipeFactory {
|
||||
|
||||
/**
|
||||
* Create a {@link AddManagedDependencyRecipe} from a Maven dependency XML snippet.
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* <dependency>
|
||||
* <groupId>groupId</groupId>
|
||||
* <artifactId>artifactId</artifactId>
|
||||
* <version>${some.version}</version>
|
||||
* <classifier>classifier</classifier>
|
||||
* <type>pom</type>
|
||||
* </dependency>
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public AddManagedDependencyRecipe create(String mavenDependencySnippet) {
|
||||
try {
|
||||
JsonNode jsonNode = getJsonNode(mavenDependencySnippet);
|
||||
String groupId = getTextValue(jsonNode, "groupId");
|
||||
String artifactId = getTextValue(jsonNode, "artifactId");
|
||||
@Nullable
|
||||
String version = getNullOrTextValue(jsonNode, "version");
|
||||
@Nullable
|
||||
String scope = getNullOrTextValue(jsonNode, "scope");
|
||||
@Nullable
|
||||
String classifier = getNullOrTextValue(jsonNode, "classifier");
|
||||
@Nullable
|
||||
String type = getNullOrTextValue(jsonNode, "type");
|
||||
AddManagedDependencyRecipe addManagedDependency = new AddManagedDependencyRecipe(groupId, artifactId,
|
||||
version, scope, type, classifier);
|
||||
return addManagedDependency;
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link AddManagedDependencyRecipe} from a {@link Dependency}.
|
||||
*/
|
||||
public AddManagedDependencyRecipe create(Dependency dependency) {
|
||||
String groupId = dependency.getGroupId();
|
||||
String artifactId = dependency.getArtifactId();
|
||||
String version = dependency.getVersion();
|
||||
String scope = dependency.getScope();
|
||||
String type = dependency.getType();
|
||||
String classifier = dependency.getClassifier();
|
||||
AddManagedDependencyRecipe addManagedDependency = new AddManagedDependencyRecipe(groupId, artifactId, version,
|
||||
scope, type, classifier);
|
||||
return addManagedDependency;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.openrewrite.internal.lang.Nullable;
|
||||
import org.openrewrite.maven.AddPlugin;
|
||||
|
||||
/**
|
||||
* @author Fabian Krüger
|
||||
*/
|
||||
public class AddPluginRecipeFactory extends AbstractRecipeFactory {
|
||||
|
||||
/**
|
||||
* Create {@link AddPlugin} from Maven plugin XML snippet.
|
||||
*/
|
||||
public AddPlugin create(String buildPlugin) {
|
||||
try {
|
||||
JsonNode jsonNode = getJsonNode(buildPlugin);
|
||||
String groupId = getTextValue(jsonNode, "groupId");
|
||||
String artifactId = getTextValue(jsonNode, "artifactId");
|
||||
@Nullable
|
||||
String version = getNullOrTextValue(jsonNode, "version");
|
||||
@Nullable
|
||||
String configuration = getNullOrTextValue(jsonNode, "configuration");
|
||||
@Nullable
|
||||
String dependencies = getNullOrTextValue(jsonNode, "dependencies");
|
||||
@Nullable
|
||||
String executions = getNullOrTextValue(jsonNode, "executions");
|
||||
@Nullable
|
||||
String filePattern = getNullOrTextValue(jsonNode, "filePattern");
|
||||
AddPlugin addPlugin = new AddPlugin(groupId, artifactId, version, configuration, dependencies, executions,
|
||||
filePattern);
|
||||
return addPlugin;
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.TreeVisitor;
|
||||
import org.openrewrite.maven.MavenIsoVisitor;
|
||||
import org.openrewrite.xml.AddToTagVisitor;
|
||||
import org.openrewrite.xml.XPathMatcher;
|
||||
import org.openrewrite.xml.tree.Xml;
|
||||
import org.openrewrite.xml.tree.Xml.Tag;
|
||||
|
||||
public class InjectTextMavenRepositoryRecipe extends Recipe {
|
||||
|
||||
private static final XPathMatcher REPOS_MATCHER = new XPathMatcher("/project/repositories");
|
||||
|
||||
private String text;
|
||||
|
||||
public InjectTextMavenRepositoryRecipe(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Add Repository";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return getDisplayName();
|
||||
}
|
||||
|
||||
public TreeVisitor<?, ExecutionContext> getVisitor() {
|
||||
return new MavenIsoVisitor<ExecutionContext>() {
|
||||
public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) {
|
||||
Xml.Tag root = document.getRoot();
|
||||
if (!root.getChild("repositories").isPresent()) {
|
||||
document = (Xml.Document) (new AddToTagVisitor(root, Tag.build("<repositories/>")))
|
||||
.visitNonNull(document, ctx, this.getCursor().getParentOrThrow());
|
||||
}
|
||||
|
||||
return super.visitDocument(document, ctx);
|
||||
}
|
||||
|
||||
public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
|
||||
Xml.Tag repositories = super.visitTag(tag, ctx);
|
||||
if (REPOS_MATCHER.matches(this.getCursor())) {
|
||||
Xml.Tag repositoryTag = Tag.build(text);
|
||||
repositories = (Xml.Tag) (new AddToTagVisitor(repositories, repositoryTag))
|
||||
.visitNonNull(repositories, ctx, this.getCursor().getParentOrThrow());
|
||||
this.maybeUpdateModel();
|
||||
}
|
||||
return repositories;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
/**
|
||||
* @author Fabian Krüger
|
||||
*/
|
||||
public class RecipeCreationException extends RuntimeException {
|
||||
|
||||
public RecipeCreationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.boot.java.copilot.recipe;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import org.openrewrite.Result;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.SpringCliException;
|
||||
|
||||
/**
|
||||
* Utilities for recipe execution
|
||||
*/
|
||||
public final class RecipeUtils {
|
||||
|
||||
private RecipeUtils() {
|
||||
}
|
||||
|
||||
public static void writeResults(String recipeName, Path path, List<Result> resultList) {
|
||||
try {
|
||||
for (Result result : resultList) {
|
||||
try (BufferedWriter sourceFileWriter = Files.newBufferedWriter(path)) {
|
||||
sourceFileWriter.write(result.getAfter().printAllTrimmed());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new SpringCliException("Could not write recipe results to path = " + path, ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.copilot.SpringCliException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.ErrorHandler;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.SAXParseException;
|
||||
|
||||
public abstract class AbstractMavenReader {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractMavenReader.class);
|
||||
|
||||
protected String sectionName;
|
||||
|
||||
/**
|
||||
* Reads the Document and populates the provided array with individual dependency text
|
||||
* values for each dependency element.
|
||||
* @param document The Document to parse
|
||||
* @param dependencies a list to populate with each dependency text
|
||||
* @throws TransformerException if the element can't be converted to text
|
||||
*/
|
||||
protected void parseDocument(Document document, List<String> dependencies, String tagName)
|
||||
throws TransformerException {
|
||||
Element root = document.getDocumentElement();
|
||||
NodeList nodeList = root.getElementsByTagName(tagName);
|
||||
|
||||
TransformerFactory tf = TransformerFactory.newInstance();
|
||||
Transformer transformer = tf.newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
|
||||
for (int i = 0; i < nodeList.getLength(); i++) {
|
||||
Node dependencyNode = nodeList.item(i);
|
||||
if (dependencyNode.getNodeType() == Node.ELEMENT_NODE) {
|
||||
Element element = (Element) dependencyNode;
|
||||
StringWriter writer = new StringWriter();
|
||||
transformer.transform(new DOMSource(element), new StreamResult(writer));
|
||||
String xml = writer.toString();
|
||||
dependencies.add(xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Document buildDocument(ErrorHandler handler, InputStream stream)
|
||||
throws ParserConfigurationException, SAXException, IOException {
|
||||
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
dbf.setNamespaceAware(true);
|
||||
DocumentBuilder parser = dbf.newDocumentBuilder();
|
||||
parser.setErrorHandler(handler);
|
||||
return parser.parse(stream);
|
||||
}
|
||||
|
||||
protected abstract String massageText(String text);
|
||||
|
||||
public String[] parseMavenSection(String text) {
|
||||
String textToUse = massageText(text);
|
||||
ErrorHandler errorHandler = new SimpleErrorHandler(logger);
|
||||
List<String> dependencies = new ArrayList<>();
|
||||
try {
|
||||
InputStream inputStream = IOUtils.toInputStream(textToUse, StandardCharsets.UTF_8);
|
||||
Document document = buildDocument(errorHandler, inputStream);
|
||||
parseDocument(document, dependencies, this.sectionName);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new SpringCliException("Cannot parse maven " + this.sectionName + " from string: " + text, ex);
|
||||
}
|
||||
catch (SAXException ex) {
|
||||
throw new SpringCliException("Invalid XML in maven " + this.sectionName + " from string: " + text, ex);
|
||||
}
|
||||
catch (ParserConfigurationException ex) {
|
||||
throw new SpringCliException("Internal error parsing maven " + this.sectionName + " from string:" + text,
|
||||
ex);
|
||||
}
|
||||
catch (TransformerException ex) {
|
||||
throw new SpringCliException("Internal error transforming Node to text from string:" + text, ex);
|
||||
}
|
||||
return dependencies.toArray(new String[0]);
|
||||
}
|
||||
|
||||
protected class SimpleErrorHandler implements ErrorHandler {
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
public SimpleErrorHandler(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(SAXParseException ex) throws SAXException {
|
||||
logger.warn("Ignored XML validation warning", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(SAXParseException ex) throws SAXException {
|
||||
throw ex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fatalError(SAXParseException ex) throws SAXException {
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ClassNameExtractor {
|
||||
|
||||
private String[] patternStrings = { "(?<=\\bclass\\s)\\w+", "(?<=\\binterface\\s)\\w+", "(?<=\\b@interface\\s)\\w+",
|
||||
"(?<=\\benum\\s)\\w+" };
|
||||
|
||||
private List<Pattern> patterns = new ArrayList<>();
|
||||
|
||||
public ClassNameExtractor() {
|
||||
for (String patternString : patternStrings) {
|
||||
patterns.add(Pattern.compile(patternString));
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<String> extractClassName(String code) {
|
||||
for (Pattern pattern : patterns) {
|
||||
Matcher matcher = pattern.matcher(code);
|
||||
if (matcher.find()) {
|
||||
return Optional.of(matcher.group());
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot.util;
|
||||
|
||||
public class MavenBuildPluginReader extends AbstractMavenReader {
|
||||
|
||||
public MavenBuildPluginReader() {
|
||||
this.sectionName = "plugin";
|
||||
}
|
||||
|
||||
protected String massageText(String text) {
|
||||
if (text.contains("<plugins>")) {
|
||||
return text;
|
||||
}
|
||||
else {
|
||||
return "<plugins>" + text + "</plugins>";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot.util;
|
||||
|
||||
public class MavenDependencyReader extends AbstractMavenReader {
|
||||
|
||||
public MavenDependencyReader() {
|
||||
this.sectionName = "dependency";
|
||||
}
|
||||
|
||||
protected String massageText(String text) {
|
||||
if (text.contains("<dependencies>")) {
|
||||
return text;
|
||||
}
|
||||
else {
|
||||
return "<dependencies>" + text + "</dependencies>";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot.util;
|
||||
|
||||
public class MavenRepositoryReader extends AbstractMavenReader {
|
||||
|
||||
public MavenRepositoryReader() {
|
||||
this.sectionName = "repository";
|
||||
}
|
||||
|
||||
protected String massageText(String text) {
|
||||
if (text.contains("<repositories>")) {
|
||||
return text;
|
||||
}
|
||||
else {
|
||||
return "<repositories>" + text + "</repositories>";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.springframework.ide.vscode.boot.java.copilot.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.apache.maven.model.Model;
|
||||
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
|
||||
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class PomReader {
|
||||
|
||||
/**
|
||||
* Returns a parsed POM.
|
||||
*/
|
||||
public Model readPom(File file) {
|
||||
File pom = file;
|
||||
if (file.isDirectory()) {
|
||||
pom = new File(file, "pom.xml");
|
||||
}
|
||||
if (!pom.exists()) {
|
||||
return null;
|
||||
}
|
||||
String fileText = "";
|
||||
try (Reader reader = new FileReader(pom)) {
|
||||
if (file.isFile()) {
|
||||
fileText = new String(Files.readAllBytes(file.toPath()));
|
||||
}
|
||||
MavenXpp3Reader xpp3Reader = new MavenXpp3Reader();
|
||||
return xpp3Reader.read(reader);
|
||||
}
|
||||
catch (XmlPullParserException | IOException ex) {
|
||||
if (file.isFile() && fileText.length() == 0) {
|
||||
throw new IllegalStateException("File [" + pom.getAbsolutePath() + "] is empty", ex);
|
||||
}
|
||||
throw new IllegalStateException("Failed to read file: " + pom.getAbsolutePath(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import { startTestJarSupport } from "./test-jar-launch";
|
||||
import { startPropertiesConversionSupport } from "./convert-props-yaml";
|
||||
import { activateCopilotFeatures } from "./copilot";
|
||||
import * as springBootAgent from './copilot/springBootAgent';
|
||||
import { SpringCli } from './copilot/springCli';
|
||||
import { applyLspEdit } from "./copilot/guideApply";
|
||||
import { isLlmApiReady } from "./copilot/util";
|
||||
import CopilotRequest, { logger } from "./copilot/copilotRequest";
|
||||
@@ -38,7 +37,6 @@ const JPA_QUERY_PROPERTIES_LANGUAGE_ID = "jpa-query-properties";
|
||||
|
||||
const STOP_ASKING = "Stop Asking";
|
||||
|
||||
export const SPRINGCLI = new SpringCli();
|
||||
/** Called when extension is activated */
|
||||
export function activate(context: ExtensionContext): Thenable<ExtensionAPI> {
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Uri, workspace, window } from "vscode";
|
||||
import { SPRINGCLI } from "../Main";
|
||||
import { getTargetGuideMardown } from "./util";
|
||||
import { Uri, workspace, window, commands } from "vscode";
|
||||
import { getTargetGuideMardown, readResponseFromFile } from "./util";
|
||||
import { createConverter } from "vscode-languageclient/lib/common/protocolConverter";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
|
||||
const CONVERTER = createConverter(undefined, true, true);
|
||||
@@ -13,11 +13,13 @@ export async function applyLspEdit(uri: Uri) {
|
||||
if (!uri) {
|
||||
uri = await getTargetGuideMardown();
|
||||
}
|
||||
const lspEdit = await SPRINGCLI.guideLspEdit(uri);
|
||||
|
||||
const fileContent = (await readResponseFromFile(uri)).toString();
|
||||
const lspEdit = await commands.executeCommand("sts/copilot/agent/lspEdits", uri.toString(), path.dirname(uri.fsPath), fileContent);
|
||||
const workspaceEdit = await CONVERTER.asWorkspaceEdit(lspEdit);
|
||||
console.log(lspEdit);
|
||||
|
||||
await Promise.all(workspaceEdit.entries().map(async ([uri, edits]) => {
|
||||
console.log(edits);
|
||||
if (fs.existsSync(uri.fsPath)) {
|
||||
const doc = await workspace.openTextDocument(uri.fsPath);
|
||||
await window.showTextDocument(doc);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { CancellationToken, chat, ChatContext, ChatRequest, ChatResponseStream,
|
||||
import { systemBoot2Prompt, systemBoot3Prompt, systemPrompt } from "./system-ai-prompt";
|
||||
import { userPrompt } from "./user-ai-prompt";
|
||||
import { getWorkspaceRoot, writeResponseToFile } from "./util";
|
||||
import { SPRINGCLI } from "../Main";
|
||||
|
||||
const PARTICIPANT_ID = 'springboot.agent';
|
||||
const SYSTEM_PROMPT = systemPrompt;
|
||||
@@ -77,7 +76,7 @@ export default class SpringBootChatAgent {
|
||||
} else {
|
||||
// modify the response from copilot LLM i.e. make response Boot 3 compliant if necessary
|
||||
if (bootProjInfo.springBootVersion.startsWith('3')) {
|
||||
const enhancedResponse = await SPRINGCLI.enhanceResponse(targetMarkdownUri, selectedProject.fsPath);
|
||||
const enhancedResponse = await commands.executeCommand("sts/copilot/agent/enhanceResponse", response) as string;
|
||||
await writeResponseToFile(enhancedResponse, bootProjInfo.name, selectedProject.fsPath);
|
||||
}
|
||||
documentContent = await workspace.fs.readFile(targetMarkdownUri);
|
||||
|
||||
@@ -1,121 +1,121 @@
|
||||
import { ProgressLocation, Uri, window, workspace } from "vscode";
|
||||
import cp from "child_process";
|
||||
import * as vscode from 'vscode';
|
||||
import { homedir } from "os";
|
||||
import { getWorkspaceRoot, getWorkspaceRootPath } from "./util";
|
||||
import path from "path";
|
||||
import { WorkspaceEdit } from "vscode-languageclient";
|
||||
// import { ProgressLocation, Uri, window, workspace } from "vscode";
|
||||
// import cp from "child_process";
|
||||
// import * as vscode from 'vscode';
|
||||
// import { homedir } from "os";
|
||||
// import { getWorkspaceRoot, getWorkspaceRootPath } from "./util";
|
||||
// import path from "path";
|
||||
// import { WorkspaceEdit } from "vscode-languageclient";
|
||||
|
||||
export const CANCELLED = "Cancelled";
|
||||
// export const CANCELLED = "Cancelled";
|
||||
|
||||
export class SpringCli {
|
||||
// export class SpringCli {
|
||||
|
||||
get executable(): string {
|
||||
return workspace.getConfiguration("spring-cli").get("executable") || "spring";
|
||||
}
|
||||
// get executable(): string {
|
||||
// return workspace.getConfiguration("spring-cli").get("executable") || "spring";
|
||||
// }
|
||||
|
||||
guideLspEdit(uri: Uri, cwd?: string): Promise<WorkspaceEdit> {
|
||||
const args = [
|
||||
"guide",
|
||||
"apply",
|
||||
"--lsp-edit",
|
||||
"--file",
|
||||
uri.fsPath
|
||||
];
|
||||
return this.fetchJson("Applying guide", uri.fsPath, args, cwd || path.dirname(uri.fsPath), true);
|
||||
}
|
||||
// guideLspEdit(uri: Uri, cwd?: string): Promise<WorkspaceEdit> {
|
||||
// const args = [
|
||||
// "guide",
|
||||
// "apply",
|
||||
// "--lsp-edit",
|
||||
// "--file",
|
||||
// uri.fsPath
|
||||
// ];
|
||||
// return this.fetchJson("Applying guide", uri.fsPath, args, cwd || path.dirname(uri.fsPath), true);
|
||||
// }
|
||||
|
||||
enhanceResponse(uri: Uri, cwd: string): Thenable<string> {
|
||||
const args = [
|
||||
"ai",
|
||||
"enhance-response",
|
||||
"--file",
|
||||
uri.fsPath
|
||||
];
|
||||
return this.exec("Spring cli ai", "Enhance response", args, cwd);
|
||||
}
|
||||
// enhanceResponse(uri: Uri, cwd: string): Thenable<string> {
|
||||
// const args = [
|
||||
// "ai",
|
||||
// "enhance-response",
|
||||
// "--file",
|
||||
// uri.fsPath
|
||||
// ];
|
||||
// return this.exec("Spring cli ai", "Enhance response", args, cwd);
|
||||
// }
|
||||
|
||||
private async executeCommand(args: string[], cwd?: string): Promise<string> {
|
||||
const processOpts = { cwd: cwd || (await 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);
|
||||
const dataChunks: string[] = [];
|
||||
process.stdout.on("data", s => dataChunks.push(s));
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
process.on("exit", (code) => {
|
||||
if (code) {
|
||||
reject(`Failed to execute command: ${dataChunks.join()}`);
|
||||
} else {
|
||||
resolve(dataChunks.join());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
// private async executeCommand(args: string[], cwd?: string): Promise<string> {
|
||||
// const processOpts = { cwd: cwd || (await 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);
|
||||
// const dataChunks: string[] = [];
|
||||
// process.stdout.on("data", s => dataChunks.push(s));
|
||||
// return new Promise<string>((resolve, reject) => {
|
||||
// process.on("exit", (code) => {
|
||||
// if (code) {
|
||||
// reject(`Failed to execute command: ${dataChunks.join()}`);
|
||||
// } else {
|
||||
// resolve(dataChunks.join());
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
private async exec<T>(title: string, message: string, args: string[], cwd?: string): Promise<T> {
|
||||
return vscode.window.withProgress({
|
||||
location: vscode.ProgressLocation.Window,
|
||||
cancellable: true,
|
||||
title,
|
||||
}, async (progress, cancellation) => {
|
||||
// private async exec<T>(title: string, message: string, args: string[], cwd?: string): Promise<T> {
|
||||
// return vscode.window.withProgress({
|
||||
// location: vscode.ProgressLocation.Window,
|
||||
// cancellable: true,
|
||||
// title,
|
||||
// }, async (progress, cancellation) => {
|
||||
|
||||
if (message) {
|
||||
progress.report({ message });
|
||||
}
|
||||
// if (message) {
|
||||
// progress.report({ message });
|
||||
// }
|
||||
|
||||
return new Promise<T>(async (resolve, reject) => {
|
||||
if (cancellation.isCancellationRequested) {
|
||||
reject("Cancelled");
|
||||
}
|
||||
try {
|
||||
const output: string = await this.executeCommand(args, cwd);
|
||||
resolve(output as T);
|
||||
} catch (error) {
|
||||
console.error(`Error: ${error}`);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
// return new Promise<T>(async (resolve, reject) => {
|
||||
// if (cancellation.isCancellationRequested) {
|
||||
// reject("Cancelled");
|
||||
// }
|
||||
// try {
|
||||
// const output: string = await this.executeCommand(args, cwd);
|
||||
// resolve(output as T);
|
||||
// } catch (error) {
|
||||
// console.error(`Error: ${error}`);
|
||||
// reject(error);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
private async fetchJson<T>(title: string, message: string, args: string[], cwd?: string, omitJsonParam?: boolean): Promise<T> {
|
||||
// private async fetchJson<T>(title: string, message: string, args: string[], cwd?: string, omitJsonParam?: boolean): Promise<T> {
|
||||
|
||||
return window.withProgress({
|
||||
location: ProgressLocation.Window,
|
||||
cancellable: true,
|
||||
title
|
||||
}, (progress, cancellation) => {
|
||||
// return window.withProgress({
|
||||
// location: ProgressLocation.Window,
|
||||
// cancellable: true,
|
||||
// title
|
||||
// }, (progress, cancellation) => {
|
||||
|
||||
if (message) {
|
||||
progress.report({ message });
|
||||
}
|
||||
// if (message) {
|
||||
// progress.report({ message });
|
||||
// }
|
||||
|
||||
return new Promise<T>(async (resolve, reject) => {
|
||||
if (cancellation.isCancellationRequested) {
|
||||
reject(CANCELLED);
|
||||
}
|
||||
const processOpts = { cwd: cwd || getWorkspaceRootPath()?.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(" ")} ${omitJsonParam ? "" : "--json"}`, processOpts);
|
||||
cancellation.onCancellationRequested(() => process.kill());
|
||||
const dataChunks: string[] = [];
|
||||
process.stdout.on("data", s => dataChunks.push(s));
|
||||
process.on("exit", (code) => {
|
||||
if (code) {
|
||||
if (cancellation.isCancellationRequested) {
|
||||
reject(CANCELLED);
|
||||
} else {
|
||||
reject(`Failed to fetch data: ${dataChunks.join()}`);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
resolve(JSON.parse(dataChunks.join()) as T);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
// return new Promise<T>(async (resolve, reject) => {
|
||||
// if (cancellation.isCancellationRequested) {
|
||||
// reject(CANCELLED);
|
||||
// }
|
||||
// const processOpts = { cwd: cwd || getWorkspaceRootPath()?.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(" ")} ${omitJsonParam ? "" : "--json"}`, processOpts);
|
||||
// cancellation.onCancellationRequested(() => process.kill());
|
||||
// const dataChunks: string[] = [];
|
||||
// process.stdout.on("data", s => dataChunks.push(s));
|
||||
// process.on("exit", (code) => {
|
||||
// if (code) {
|
||||
// if (cancellation.isCancellationRequested) {
|
||||
// reject(CANCELLED);
|
||||
// } else {
|
||||
// reject(`Failed to fetch data: ${dataChunks.join()}`);
|
||||
// }
|
||||
// } else {
|
||||
// try {
|
||||
// resolve(JSON.parse(dataChunks.join()) as T);
|
||||
// } catch (error) {
|
||||
// reject(error);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
|
||||
@@ -3,10 +3,6 @@ import { Uri, WorkspaceFolder, version, window, workspace } from "vscode";
|
||||
import fs from "fs";
|
||||
import { SemVer } from "semver";
|
||||
|
||||
export function getExecutable(): string {
|
||||
return workspace.getConfiguration("spring-cli").get("executable") || "spring";
|
||||
}
|
||||
|
||||
export async function getWorkspaceRoot(): Promise<Uri | undefined> {
|
||||
if (workspace.workspaceFolders && workspace.workspaceFolders.length) {
|
||||
if (workspace.workspaceFolders.length === 1) {
|
||||
@@ -86,6 +82,10 @@ export async function writeResponseToFile(response: string, appName: string, sel
|
||||
}
|
||||
}
|
||||
|
||||
export async function readResponseFromFile(uri: Uri) {
|
||||
return workspace.fs.readFile(uri);
|
||||
}
|
||||
|
||||
export function isLlmApiReady(): boolean {
|
||||
return version.includes('insider') && new SemVer(version).compare(new SemVer("1.90.0-insider")) >= 0;
|
||||
}
|
||||
Reference in New Issue
Block a user