Hierarchical Recipes UI for vscode
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user