Skip to content

Commit

Permalink
Integrate needsConfirmation
Browse files Browse the repository at this point in the history
  • Loading branch information
BoykoAlex committed Dec 14, 2023
1 parent 44310b3 commit 1539beb
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

import org.eclipse.lsp4j.AnnotatedTextEdit;
import org.eclipse.lsp4j.ChangeAnnotation;
import org.eclipse.lsp4j.CreateFile;
import org.eclipse.lsp4j.DeleteFile;
import org.eclipse.lsp4j.Position;
Expand Down Expand Up @@ -72,7 +76,7 @@ public static Optional<DocumentEdits> computeEdits(IDocument doc, Result result)

}

public static Optional<TextDocumentEdit> computeTextDocEdit(TextDocument doc, Result result) {
public static Optional<TextDocumentEdit> computeTextDocEdit(TextDocument doc, Result result, String changeAnnotationId) {
TextDocument newDoc = new TextDocument(null, LanguageId.PLAINTEXT, 0, result.getAfter().printAll());

EditList diff = JGitUtils.getDiff(result.getBefore().printAll(), newDoc.get());
Expand All @@ -85,26 +89,29 @@ public static Optional<TextDocumentEdit> computeTextDocEdit(TextDocument doc, Re
try {
switch(e.getType()) {
case DELETE:
TextEdit textEdit = new TextEdit();
AnnotatedTextEdit textEdit = new AnnotatedTextEdit();
int start = doc.getLineOffset(e.getBeginA());
int end = getStartOfLine(doc, e.getEndA());
textEdit.setRange(new Range(doc.toPosition(start), doc.toPosition(end)));
textEdit.setNewText("");
textEdit.setAnnotationId(changeAnnotationId);
textEdits.add(textEdit);
break;
case INSERT:
textEdit = new TextEdit();
textEdit = new AnnotatedTextEdit();
Position position = doc.toPosition(doc.getLineOffset(e.getBeginA()));
textEdit.setRange(new Range(position, position));
textEdit.setNewText(newDoc.textBetween(newDoc.getLineOffset(e.getBeginB()), getStartOfLine(newDoc, e.getEndB())));
textEdit.setAnnotationId(changeAnnotationId);
textEdits.add(textEdit);
break;
case REPLACE:
textEdit = new TextEdit();
textEdit = new AnnotatedTextEdit();
start = doc.getLineOffset(e.getBeginA());
end = getStartOfLine(doc, e.getEndA());
textEdit.setRange(new Range(doc.toPosition(start), doc.toPosition(end)));
textEdit.setNewText(newDoc.textBetween(newDoc.getLineOffset(e.getBeginB()), getStartOfLine(newDoc, e.getEndB())));
textEdit.setAnnotationId(changeAnnotationId);
textEdits.add(textEdit);
break;
case EMPTY:
Expand Down Expand Up @@ -151,12 +158,14 @@ private static int getStartOfLine(IDocument doc, int lineNumber) {
return 0;
}

public static Optional<WorkspaceEdit> createWorkspaceEdit(Path absoluteProjectDir, SimpleTextDocumentService documents, List<Result> results) {
public static Optional<WorkspaceEdit> createWorkspaceEdit(Path absoluteProjectDir, SimpleTextDocumentService documents, List<Result> results, ChangeAnnotation changeAnnotation) {
if (results.isEmpty()) {
return Optional.empty();
}
WorkspaceEdit we = new WorkspaceEdit();
we.setDocumentChanges(new ArrayList<>());
final String changeAnnotationId = UUID.randomUUID().toString();
we.setChangeAnnotations(Map.of(changeAnnotationId, changeAnnotation));
for (Result result : results) {
if (result.getBefore() == null) {
String docUri = absoluteProjectDir.resolve(result.getAfter().getSourcePath()).toUri().toASCIIString();
Expand All @@ -167,7 +176,7 @@ public static Optional<WorkspaceEdit> createWorkspaceEdit(Path absoluteProjectDi
TextDocumentEdit te = new TextDocumentEdit();
te.setTextDocument(new VersionedTextDocumentIdentifier(docUri, 0));
Position cursor = new Position(0,0);
te.setEdits(List.of(new TextEdit(new Range(cursor, cursor), result.getAfter().printAll())));
te.setEdits(List.of(new AnnotatedTextEdit(new Range(cursor, cursor), result.getAfter().printAll(), changeAnnotationId)));
we.getDocumentChanges().add(Either.forLeft(te));
} else if (result.getAfter() == null) {
String docUri = absoluteProjectDir.resolve(result.getBefore().getSourcePath()).toUri().toASCIIString();
Expand All @@ -177,9 +186,9 @@ public static Optional<WorkspaceEdit> createWorkspaceEdit(Path absoluteProjectDi
TextDocument doc = documents.getLatestSnapshot(docUri);
if (doc == null) {
doc = new TextDocument(docUri, null, 0, result.getBefore().printAll());
ORDocUtils.computeTextDocEdit(doc, result).ifPresent(te -> we.getDocumentChanges().add(Either.forLeft(te)));
ORDocUtils.computeTextDocEdit(doc, result, changeAnnotationId).ifPresent(te -> we.getDocumentChanges().add(Either.forLeft(te)));
} else {
ORDocUtils.computeTextDocEdit(doc, result).ifPresent(te -> we.getDocumentChanges().add(Either.forLeft(te)));
ORDocUtils.computeTextDocEdit(doc, result, changeAnnotationId).ifPresent(te -> we.getDocumentChanges().add(Either.forLeft(te)));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.stream.Collectors;

import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
import org.eclipse.lsp4j.ChangeAnnotation;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.openrewrite.InMemoryExecutionContext;
Expand Down Expand Up @@ -282,6 +283,7 @@ private void registerCommands() {
return recipes().thenCompose(recipes -> {
String uri = ((JsonElement) params.getArguments().get(0)).getAsString();
JsonElement recipesJson = ((JsonElement) params.getArguments().get(1));
boolean needsConfirmation = params.getArguments().size() > 2 ? ((JsonElement) params.getArguments().get(2)).getAsBoolean() : false;

RecipeDescriptor d = serializationGson.fromJson(recipesJson, RecipeDescriptor.class);

Expand All @@ -295,14 +297,14 @@ private void registerCommands() {
|| params.getWorkDoneToken().getLeft() == null
? (r.getName() == null ? UUID.randomUUID().toString() : r.getName())
: params.getWorkDoneToken().getLeft();
return apply(r, uri, progressToken);
return apply(r, uri, progressToken, needsConfirmation);
} else {
String progressToken = params.getWorkDoneToken() == null
|| params.getWorkDoneToken().getLeft() == null
? (aggregateRecipe.getName() == null ? UUID.randomUUID().toString()
: aggregateRecipe.getName())
: params.getWorkDoneToken().getLeft();
return apply(aggregateRecipe, uri, progressToken);
return apply(aggregateRecipe, uri, progressToken, needsConfirmation);
}
});
});
Expand All @@ -314,23 +316,24 @@ private void registerCommands() {

server.onCommand(CMD_REWRITE_RECIPE_EXECUTE, params -> {
String recipeId = ((JsonElement) params.getArguments().get(0)).getAsString();
boolean needsConfirmation = params.getArguments().size() > 1 ? ((JsonElement) params.getArguments().get(1)).getAsBoolean() : false;
return getRecipe(recipeId).thenCompose(optRecipe -> {
Recipe r = optRecipe.orElseThrow(() -> new IllegalArgumentException("No such recipe exists with name " + recipeId));
final String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? r.getName() : params.getWorkDoneToken().getLeft();
String uri = ((JsonElement) params.getArguments().get(1)).getAsString();
return apply(r, uri, progressToken);
return apply(r, uri, progressToken, needsConfirmation);
});
});
}

CompletableFuture<Object> apply(Recipe r, String uri, String progressToken) {
CompletableFuture<Object> apply(Recipe r, String uri, String progressToken, boolean needsConfirmation) {
final IndefiniteProgressTask progressTask = server.getProgressService().createIndefiniteProgressTask(progressToken, r.getDisplayName(), "Initiated...");
return CompletableFuture.supplyAsync(() -> {
return projectFinder.find(new TextDocumentIdentifier(uri));
}).thenCompose(p -> {
if (p.isPresent()) {
try {
Optional<WorkspaceEdit> edit = computeWorkspaceEdit(r, p.get(), progressTask);
Optional<WorkspaceEdit> edit = computeWorkspaceEdit(r, p.get(), progressTask, needsConfirmation);
return CompletableFuture.completedFuture(edit).thenCompose(we -> {
if (we.isPresent()) {
progressTask.progressEvent("Applying document changes...");
Expand Down Expand Up @@ -358,7 +361,7 @@ CompletableFuture<Object> apply(Recipe r, String uri, String progressToken) {
});
}

private Optional<WorkspaceEdit> computeWorkspaceEdit(Recipe r, IJavaProject project, IndefiniteProgressTask progressTask) {
private Optional<WorkspaceEdit> computeWorkspaceEdit(Recipe r, IJavaProject project, IndefiniteProgressTask progressTask, boolean needsConfirmation) {
Path absoluteProjectDir = Paths.get(project.getLocationUri());
progressTask.progressEvent("Parsing files...");
ProjectParser projectParser = createRewriteProjectParser(project,
Expand All @@ -374,7 +377,9 @@ private Optional<WorkspaceEdit> computeWorkspaceEdit(Recipe r, IJavaProject proj
progressTask.progressEvent("Computing changes...");
RecipeRun reciperun = r.run(new InMemoryLargeSourceSet(sources), new InMemoryExecutionContext(e -> log.error("Recipe execution failed", e)));
List<Result> results = reciperun.getChangeset().getAllResults();
return ORDocUtils.createWorkspaceEdit(absoluteProjectDir, server.getTextDocumentService(), results);
ChangeAnnotation changeAnnotation = new ChangeAnnotation("Apply Recipe '" + r.getDisplayName() + "'");
changeAnnotation.setNeedsConfirmation(needsConfirmation);
return ORDocUtils.createWorkspaceEdit(absoluteProjectDir, server.getTextDocumentService(), results, changeAnnotation);
}

private void reportParseErrors(List<ParseError> parseErrors) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import org.eclipse.lsp4j.ChangeAnnotation;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.ResourceOperation;
import org.eclipse.lsp4j.TextDocumentEdit;
Expand Down Expand Up @@ -120,22 +122,26 @@ public CompletableFuture<WorkspaceEdit> createEdit(JsonElement o) {
return null;
}

private WorkspaceEdit applyRecipe(Recipe r, IJavaProject project, List<CompilationUnit> cus) {
private WorkspaceEdit applyRecipe(Recipe r, IJavaProject project, List<CompilationUnit> cus, boolean needsConfirmation) {
List<SourceFile> sources = cus.stream().map(cu -> (SourceFile) cu).collect(Collectors.toList());
RecipeRun reciperun = r.run(new InMemoryLargeSourceSet(sources), new InMemoryExecutionContext());
List<Result> results = reciperun.getChangeset().getAllResults();
final String changeAnnotationId = UUID.randomUUID().toString();
List<Either<TextDocumentEdit, ResourceOperation>> edits = results.stream().filter(res -> res.getAfter() != null).map(res -> {
URI docUri = res.getAfter().getSourcePath().isAbsolute() ? res.getAfter().getSourcePath().toUri() : project.getLocationUri().resolve(res.getAfter().getSourcePath().toString());
TextDocument doc = server.getTextDocumentService().getLatestSnapshot(docUri.toASCIIString());
if (doc == null) {
doc = new TextDocument(docUri.toASCIIString(), LanguageId.JAVA, 0, res.getBefore() == null ? "" : res.getBefore().printAll());
}
return ORDocUtils.computeTextDocEdit(doc, res);
return ORDocUtils.computeTextDocEdit(doc, res, changeAnnotationId);
}).filter(e -> e.isPresent()).map(e -> e.get()).map(e -> Either.<TextDocumentEdit, ResourceOperation>forLeft(e)).collect(Collectors.toList());
if (edits.isEmpty()) {
return null;
}
WorkspaceEdit workspaceEdit = new WorkspaceEdit();
ChangeAnnotation changeAnnotation = new ChangeAnnotation("Applying Recipe '" + r.getDisplayName() + "'");
changeAnnotation.setNeedsConfirmation(needsConfirmation);
workspaceEdit.setChangeAnnotations(Map.of(changeAnnotationId, changeAnnotation));
workspaceEdit.setDocumentChanges(edits);
return workspaceEdit;
}
Expand All @@ -155,7 +161,7 @@ private CompletableFuture<WorkspaceEdit> perform(FixDescriptor data) {
PercentageProgressTask progress = server.getProgressService().createPercentageProgressTask(UUID.randomUUID().toString(), inputs.size() + 1, data.getLabel());
try {
cus = ORAstUtils.parseInputs(jp, inputs, s -> progress.increment());
return applyRecipe(r, project.get(), cus);
return applyRecipe(r, project.get(), cus, false);
} finally {
progress.setCurrent(progress.getTotal());
progress.done();
Expand All @@ -164,7 +170,7 @@ private CompletableFuture<WorkspaceEdit> perform(FixDescriptor data) {
JavaParser jp = ORAstUtils.createJavaParserBuilder(project.get()).dependsOn(data.getTypeStubs()).build();
List<Input> inputs = data.getDocUris().stream().map(URI::create).map(Paths::get).map(p -> ORAstUtils.getParserInput(server.getTextDocumentService(), p)).collect(Collectors.toList());
cus = ORAstUtils.parseInputs(jp, inputs, null);
return applyRecipe(r, project.get(), cus);
return applyRecipe(r, project.get(), cus, false);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public SpringBootUpgrade(SimpleLanguageServer server, RewriteRecipeRepository re
Assert.isLegal(uri != null, "Project URI parameter must not be 'null'");
Version targetVersion = Version.parse(((JsonElement) params.getArguments().get(1)).getAsString());
Assert.isLegal(targetVersion != null, "Target Spring Boot version must not be 'null'");
boolean needsConfirmation = params.getArguments().size() > 2 ? ((JsonElement) params.getArguments().get(2)).getAsBoolean() : false;

IJavaProject project = projectFinder.find(new TextDocumentIdentifier(uri)).orElse(null);
Assert.isLegal(project != null, "No Spring Boot project found for uri: " + uri);
Expand All @@ -75,7 +76,8 @@ public SpringBootUpgrade(SimpleLanguageServer server, RewriteRecipeRepository re
return recipeRepo.recipes().thenComposeAsync(recipes -> recipeRepo.apply(
createUpgradeRecipe(recipes, version, targetVersion),
uri,
UUID.randomUUID().toString()
UUID.randomUUID().toString(),
needsConfirmation
));
});
}
Expand Down
39 changes: 31 additions & 8 deletions vscode-extensions/vscode-spring-boot/lib/rewrite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ async function showRefactorings(uri: VSCode.Uri, filter: string) {
}
const choices = await showCurrentPathQuickPick(VSCode.commands.executeCommand('sts/rewrite/list', uri.toString(true), filter).then((cmds: RecipeDescriptor[]) => cmds.map(convertToQuickPickItem)), []);
const recipeDescriptors = choices.filter(i => i.selected).map(convertToRecipeDescriptor);
const needsConfirmation = await shwoNeedsConfirmation();
if (recipeDescriptors.length) {
const aggregateRecipeDescriptor = recipeDescriptors.length === 1 ? recipeDescriptors[0] : {
name: `${recipeDescriptors.length} recipes`,
Expand All @@ -111,7 +112,7 @@ async function showRefactorings(uri: VSCode.Uri, filter: string) {
if (aggregateRecipeDescriptor.estimatedEffortPerOccurrence === 0) {
delete aggregateRecipeDescriptor.estimatedEffortPerOccurrence;
}
VSCode.commands.executeCommand('sts/rewrite/execute', uri.toString(true), aggregateRecipeDescriptor);
VSCode.commands.executeCommand('sts/rewrite/execute', uri.toString(true), aggregateRecipeDescriptor, needsConfirmation);
} else {
VSCode.window.showErrorMessage('No Recipes were selected!');
}
Expand All @@ -137,6 +138,33 @@ function convertToQuickPickItem(i: RecipeDescriptor): RecipeQuickPickItem {
};
}

function shwoNeedsConfirmation(): Thenable<boolean> {
return new Promise((resolve, reject) => {
const previewPick = VSCode.window.createQuickPick<VSCode.QuickPickItem>();
previewPick.title = 'Preview Before Applying?';
previewPick.canSelectMany = false;

const applyItem = {
label: "Apply",
description: "Apply changes maded by a recipe"
}
const previewItem = {
label: "Preview",
description: "Preview and confirm changes made by a recipe before applying"
}
previewPick.items = [
applyItem,
previewItem
];
previewPick.show();

previewPick.onDidAccept(() => {
previewPick.hide();
resolve(previewPick.selectedItems[0] === previewItem);
});
});
}

function showCurrentPathQuickPick(itemsPromise: Thenable<RecipeQuickPickItem[]>, itemsPath: RecipeQuickPickItem[]): Thenable<RecipeQuickPickItem[]> {
const quickPick = VSCode.window.createQuickPick<RecipeQuickPickItem>();
quickPick.title = 'Loading Recipes...';
Expand Down Expand Up @@ -176,13 +204,8 @@ function showCurrentPathQuickPick(itemsPromise: Thenable<RecipeQuickPickItem[]>,
});
quickPick.onDidAccept(() => {
currentItems.forEach(i => i.selected = quickPick.selectedItems.includes(i));
if (itemsPath.length) {
itemsPath.pop();
showCurrentPathQuickPick(Promise.resolve(items), itemsPath).then(resolve, reject);
} else {
quickPick.hide();
resolve(items);
}
quickPick.hide();
resolve(items);
});
quickPick.onDidChangeSelection(selected => {
currentItems.forEach(i => {
Expand Down

0 comments on commit 1539beb

Please sign in to comment.