Hierarchical Recipes UI for vscode

This commit is contained in:
BoykoAlex
2022-05-31 10:14:33 -04:00
parent 8e4eb819f4
commit 184e77afae
5 changed files with 250 additions and 62 deletions

View File

@@ -15,7 +15,7 @@
#
# ---
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.spring.boot2.SpringBootProperties_3_0
name: org.openrewrite.java.spring.boot3.SpringBootProperties_3_0
displayName: Migrate Spring Boot properties to 3.0
description: Migrate properties found in `application.properties` and `application.yml`.
recipeList:

View File

@@ -7,6 +7,17 @@ displayName: Upgrade to Spring Boot 3.0 from 2.x
description: 'Upgrade to Spring Boot 3.0 from prior 2.x version.'
recipeList:
# Upgrade 3.0.x from 2.x
- org.openrewrite.java.spring.boot3.MavenPomUpgrade
- org.openrewrite.java.spring.boot3.data.UpgradeSpringData_3_0
# - org.openrewrite.java.spring.boot3.SpringBootProperties_3_0
---
########################################################################################################################
# SpringBoot 3_0 Maven Pom
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.spring.boot3.MavenPomUpgrade
displayName: Upgrade Maven Pom to Spring Boot 3.0 from 2.x
description: 'Upgrade Maven Pom to Spring Boot 3.0 from prior 2.x version.'
recipeList:
- org.openrewrite.maven.UpgradeDependencyVersion:
groupId: org.springframework.boot
artifactId: "*"
@@ -20,7 +31,5 @@ recipeList:
key: 'java.version'
newValue: 17
addIfMissing: true
- org.openrewrite.java.spring.data.UpgradeSpringData_3_0
- org.openrewrite.java.spring.boot2.SpringBootProperties_3_0

View File

@@ -1,9 +1,19 @@
########################################################################################################################
# Spring Data 3.0
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.spring.data.UpgradeSpringData_3_0
name: org.openrewrite.java.spring.boot3.data.UpgradeSpringData_3_0
displayName: Upgrade to Spring Data 3.0
description: 'Upgrade to Spring Data to 3.0 from any prior version.'
recipeList:
- org.openrewrite.java.spring.boot3.data.SwitchToJakarta
---
########################################################################################################################
# Spring Data 3.0 javax -> jakarta
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.spring.boot3.data.SwitchToJakarta
displayName: Switch to Jakarta JPA
description: 'Switch to Jakarta JPA from Javax'
recipeList:
- org.springframework.ide.vscode.commons.rewrite.maven.ChangeDependencyClassifier:
groupId: org.ehcache

View File

@@ -14,6 +14,7 @@ import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -38,6 +39,7 @@ import org.openrewrite.Recipe;
import org.openrewrite.Result;
import org.openrewrite.SourceFile;
import org.openrewrite.Validated;
import org.openrewrite.config.DeclarativeRecipe;
import org.openrewrite.config.Environment;
import org.openrewrite.java.JavaParser;
import org.openrewrite.maven.MavenParser;
@@ -54,6 +56,7 @@ import org.springframework.ide.vscode.commons.rewrite.maven.MavenProjectParser;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
public class RewriteRecipeRepository {
@@ -135,6 +138,39 @@ public class RewriteRecipeRepository {
});
listBuilder.add("sts/rewrite/list");
server.onCommand("sts/rewrite/execute", params -> {
String uri = ((JsonElement) params.getArguments().get(0)).getAsString();
JsonElement recipesJson = ((JsonElement) params.getArguments().get(1));
RecipeDescriptor[] recipeDescriptors = new Gson().fromJson(recipesJson, RecipeDescriptor[].class);
List<Recipe> convertedRecipes = Arrays.stream(recipeDescriptors)
.filter(rd -> rd.selected)
.filter(rd -> recipes.containsKey(rd.id))
.map(rd -> convert(recipes.get(rd.id), rd))
.collect(Collectors.toList());
if (convertedRecipes.isEmpty()) {
throw new RuntimeException("Not recipes to execute!");
} else if (convertedRecipes.size() == 1) {
Recipe r = convertedRecipes.get(0);
String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? r.getName() : params.getWorkDoneToken().getLeft();
return apply(r, uri, progressToken);
} else {
Recipe multiRecipe = new Recipe() {
@Override
public String getDisplayName() {
return convertedRecipes.size() + " recipes";
}
};
for (Recipe r : convertedRecipes) {
multiRecipe.doNext(r);
}
String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? multiRecipe.getName() : params.getWorkDoneToken().getLeft();
return apply(multiRecipe, uri, progressToken);
}
});
listBuilder.add("sts/rewrite/execute");
for (Recipe r : globalCommandRecipes) {
listBuilder.add(createGlobalCommand(r));
}
@@ -159,44 +195,47 @@ public class RewriteRecipeRepository {
String commandId = "sts/rewrite/recipe/" + r.getName();
server.onCommand(commandId, params -> {
final String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? r.getName() : params.getWorkDoneToken().getLeft();
return CompletableFuture.supplyAsync(() -> {
JsonElement uri = (JsonElement) params.getArguments().get(0);
server.getProgressService().progressEvent(progressToken, r.getDisplayName() + ": initiated...");
return projectFinder.find(new TextDocumentIdentifier(uri.getAsString()));
}).thenCompose(p -> {
if (p.isPresent()) {
try {
Optional<WorkspaceEdit> edit = apply(r, p.get());
return CompletableFuture.completedFuture(edit).thenCompose(we -> {
if (we.isPresent()) {
server.getProgressService().progressEvent(progressToken,
r.getDisplayName() + ": applying document changes...");
return server.getClient().applyEdit(new ApplyWorkspaceEditParams(we.get(), r.getDisplayName())).thenCompose(res -> {
if (res.isApplied()) {
server.getProgressService().progressEvent(progressToken, null);
return CompletableFuture.completedFuture("success");
} else {
server.getProgressService().progressEvent(progressToken, null);
return CompletableFuture.completedFuture(null);
}
});
} else {
server.getProgressService().progressEvent(progressToken, null);
return CompletableFuture.completedFuture(null);
}
});
} catch (Throwable t) {
server.getProgressService().progressEvent(progressToken, null);
throw t;
}
}
return CompletableFuture.completedFuture(null);
});
String uri = ((JsonElement) params.getArguments().get(0)).getAsString();
return apply(r, uri, progressToken);
});
return commandId;
}
private CompletableFuture<Object> apply(Recipe r, String uri, String progressToken) {
return CompletableFuture.supplyAsync(() -> {
server.getProgressService().progressEvent(progressToken, r.getDisplayName() + ": initiated...");
return projectFinder.find(new TextDocumentIdentifier(uri));
}).thenCompose(p -> {
if (p.isPresent()) {
try {
Optional<WorkspaceEdit> edit = apply(r, p.get());
return CompletableFuture.completedFuture(edit).thenCompose(we -> {
if (we.isPresent()) {
server.getProgressService().progressEvent(progressToken,
r.getDisplayName() + ": applying document changes...");
return server.getClient().applyEdit(new ApplyWorkspaceEditParams(we.get(), r.getDisplayName())).thenCompose(res -> {
if (res.isApplied()) {
server.getProgressService().progressEvent(progressToken, null);
return CompletableFuture.completedFuture("success");
} else {
server.getProgressService().progressEvent(progressToken, null);
return CompletableFuture.completedFuture(null);
}
});
} else {
server.getProgressService().progressEvent(progressToken, null);
return CompletableFuture.completedFuture(null);
}
});
} catch (Throwable t) {
server.getProgressService().progressEvent(progressToken, null);
throw t;
}
}
return CompletableFuture.completedFuture(null);
});
}
private Optional<WorkspaceEdit> apply(Recipe r, IJavaProject project) {
Path absoluteProjectDir = Paths.get(project.getLocationUri());
server.getProgressService().progressEvent(r.getName(), r.getDisplayName() + ": parsing files...");
@@ -215,7 +254,7 @@ public class RewriteRecipeRepository {
if (projectOpt.isPresent()) {
List<RecipeDescriptor> commandDescriptors = new ArrayList<>(globalCommandRecipes.size());
for (Recipe r : globalCommandRecipes) {
commandDescriptors.add(new RecipeDescriptor(r.getName(), r.getDisplayName(), r.getDescription()));
commandDescriptors.add(new RecipeDescriptor(r));
}
return commandDescriptors;
}
@@ -247,17 +286,48 @@ public class RewriteRecipeRepository {
.map(file -> file.getAbsoluteFile().toPath()).collect(Collectors.toList());
}
}
private static Recipe convert(Recipe r, RecipeDescriptor d) {
try {
if (d.selected) {
if (d.children != null && !d.children.isEmpty()) {
Recipe recipe = r instanceof DeclarativeRecipe ? new DeclarativeRecipe(r.getName(), r.getDisplayName(), r.getDescription(), r.getTags(), r.getEstimatedEffortPerOccurrence(), null)
: r.getClass().getDeclaredConstructor().newInstance();
int i = 0;
for (Recipe sr : r.getRecipeList()) {
Recipe convertedSubRecipe = convert(sr, d.children.get(i++));
if (convertedSubRecipe != null) {
recipe.doNext(convertedSubRecipe);
}
}
return recipe;
} else {
return r;
}
}
} catch (Exception e) {
log.error("", e);
}
return null;
}
@SuppressWarnings("unused")
private static class RecipeDescriptor {
String id;
String label;
String description;
public RecipeDescriptor(String id, String label, String description) {
this.id = id;
this.label = label;
this.description = description;
}
String detail;
List<RecipeDescriptor> children;
boolean selected;
RecipeDescriptor(Recipe r) {
this.id = r.getName();
this.label = r.getDisplayName();
this.detail = r.getDescription();
List<Recipe> subRecipes = r.getRecipeList();
if (r instanceof DeclarativeRecipe && !subRecipes.isEmpty() && (subRecipes.size() > 1 || subRecipes.get(0) instanceof DeclarativeRecipe)) {
this.children = r.getRecipeList().stream().map(sr -> new RecipeDescriptor(sr)).collect(Collectors.toList());
}
}
}
}

View File

@@ -6,11 +6,15 @@ import * as path from "path";
interface RewriteCommandInfo {
id: string;
label: string;
description: string
detail?: string;
children: RewriteCommandInfo[];
selected: boolean;
}
interface RewriteCommandQuickPickItem extends VSCode.QuickPickItem {
interface RecipeQuickPickItem extends VSCode.QuickPickItem{
id: string;
children: RecipeQuickPickItem[];
selected: boolean;
}
function getWorkspaceFolderName(file: VSCode.Uri): string {
@@ -20,7 +24,7 @@ function getWorkspaceFolderName(file: VSCode.Uri): string {
return wf.name;
}
}
return "";
return '';
}
function getRelativePathToWorkspaceFolder(file: VSCode.Uri): string {
@@ -30,7 +34,7 @@ function getRelativePathToWorkspaceFolder(file: VSCode.Uri): string {
return path.relative(wf.uri.fsPath, file.fsPath);
}
}
return "";
return '';
}
async function getTargetPomXml(): Promise<VSCode.Uri> {
@@ -55,24 +59,119 @@ async function getTargetPomXml(): Promise<VSCode.Uri> {
return undefined;
}
const ROOT_RECIPES_BUTTON: VSCode.QuickInputButton = {
iconPath: new VSCode.ThemeIcon('home'),
tooltip: 'Root Recipes'
}
const SUB_RECIPES_BUTTON: VSCode.QuickInputButton = {
iconPath: new VSCode.ThemeIcon('type-hierarchy'),
tooltip: 'Sub-Recipes'
}
async function liveHoverConnectHandler(uri: VSCode.Uri) {
if (!uri) {
uri = await getTargetPomXml();
}
const cmds: RewriteCommandInfo[] = await VSCode.commands.executeCommand('sts/rewrite/list', uri.toString(true));
const choices: RewriteCommandQuickPickItem[] = cmds.map(d => {
return {
id: d.id,
label: d.label,
description: d.description,
};
});
if (choices) {
const picked = await VSCode.window.showQuickPick(choices);
if (picked) {
VSCode.commands.executeCommand(`sts/rewrite/recipe/${picked.id}`, uri.toString(true))
const choices = cmds.map(convertToQuickPickItem);
await showCurrentPathQuickPick(choices, []);
const recipesInfo = choices.filter(i => i.selected).map(convertToRecipeDescriptor);
if (recipesInfo.length) {
VSCode.commands.executeCommand('sts/rewrite/execute', uri.toString(true), recipesInfo);
} else {
VSCode.window.showErrorMessage('No Recipes were selected!');
}
}
function convertToRecipeDescriptor(i: RecipeQuickPickItem): RewriteCommandInfo {
return {
id: i.id,
label: i.label,
selected: i.selected,
children: i.children.map(convertToRecipeDescriptor)
};
}
function convertToQuickPickItem(i: RewriteCommandInfo): RecipeQuickPickItem {
return {
id: i.id,
label: i.label,
detail: i.detail,
selected: i.selected,
children: i.children ? i.children.map(convertToQuickPickItem) : [],
buttons: i.children && i.children.length ? [ SUB_RECIPES_BUTTON ] : undefined,
};
}
function showCurrentPathQuickPick(items: RecipeQuickPickItem[], itemsPath: RecipeQuickPickItem[]): Promise<void> {
return new Promise((resolve, reject) => {
let currentItems = items;
let parent: RecipeQuickPickItem | undefined;
itemsPath.forEach(p => {
parent = currentItems.find(i => i === p);
currentItems = parent.children;
});
const quickPick = VSCode.window.createQuickPick<RewriteCommandInfo & VSCode.QuickPickItem>();
quickPick.items = currentItems;
quickPick.title = 'Select Recipes';
quickPick.canSelectMany = true;
if (itemsPath.length) {
quickPick.buttons = [ ROOT_RECIPES_BUTTON ];
}
quickPick.selectedItems = currentItems.filter(i => i.selected);
quickPick.onDidTriggerItemButton(e => {
if (e.button === SUB_RECIPES_BUTTON) {
currentItems.forEach(i => i.selected = quickPick.selectedItems.includes(i));
itemsPath.push(e.item);
showCurrentPathQuickPick(items, itemsPath).then(resolve, reject);
}
});
quickPick.onDidTriggerButton(b => {
if (b === ROOT_RECIPES_BUTTON) {
currentItems.forEach(i => i.selected = quickPick.selectedItems.includes(i));
itemsPath.splice(0, itemsPath.length);
showCurrentPathQuickPick(items, itemsPath).then(resolve, reject);
}
});
quickPick.onDidAccept(() => {
currentItems.forEach(i => i.selected = quickPick.selectedItems.includes(i));
if (itemsPath.length) {
itemsPath.pop();
showCurrentPathQuickPick(items, itemsPath).then(resolve, reject);
} else {
quickPick.hide();
resolve();
}
});
quickPick.onDidChangeSelection(selected => {
currentItems.forEach(i => {
const isSelectedItem = selected.includes(i);
if (i.selected !== isSelectedItem) {
selectItemRecursively(i, isSelectedItem);
}
});
updateParentSelection(itemsPath.slice());
});
quickPick.show();
});
}
function updateParentSelection(hierarchy: RecipeQuickPickItem[]): void {
if (hierarchy.length) {
const parent = hierarchy.pop();
const isSelected = !!parent.children.find(i => i.selected);
if (parent.selected !== isSelected) {
parent.selected = isSelected;
updateParentSelection(hierarchy)
}
}
}
function selectItemRecursively(i: RecipeQuickPickItem, isSelectedItem: boolean) {
i.selected = isSelectedItem;
if (i.children) {
i.children.forEach(c => selectItemRecursively(c, isSelectedItem));
}
}