Start Boot LS properly when invoking Rewrite Refactorings command
This commit is contained in:
@@ -36,11 +36,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
|
||||
import org.eclipse.lsp4j.Registration;
|
||||
import org.eclipse.lsp4j.RegistrationParams;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.Unregistration;
|
||||
import org.eclipse.lsp4j.UnregistrationParams;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.InMemoryExecutionContext;
|
||||
@@ -77,9 +73,6 @@ import org.springframework.ide.vscode.commons.rewrite.config.StsEnvironment;
|
||||
import org.springframework.ide.vscode.commons.rewrite.maven.MavenProjectParser;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
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.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
@@ -89,15 +82,15 @@ public class RewriteRecipeRepository implements ApplicationContextAware {
|
||||
private static final String CMD_REWRITE_RELOAD = "sts/rewrite/reload";
|
||||
private static final String CMD_REWRITE_EXECUTE = "sts/rewrite/execute";
|
||||
private static final String CMD_REWRITE_LIST = "sts/rewrite/list";
|
||||
private static final String CMD_REWRITE_RECIPE_EXECUTE = "sts/rewrite/recipe/execute";
|
||||
private static final Logger log = LoggerFactory.getLogger(RewriteRecipeRepository.class);
|
||||
private static final String WORKSPACE_EXECUTE_COMMAND = "workspace/executeCommand";
|
||||
|
||||
private static final Set<String> UNINITIALIZED_SET = Collections.emptySet();
|
||||
|
||||
final private SimpleLanguageServer server;
|
||||
|
||||
final private Map<String, Recipe> recipes;
|
||||
|
||||
final private List<Recipe> globalCommandRecipes;
|
||||
|
||||
final private JavaProjectFinder projectFinder;
|
||||
|
||||
final private List<RecipeCodeActionDescriptor> codeActionDescriptors;
|
||||
@@ -108,9 +101,9 @@ public class RewriteRecipeRepository implements ApplicationContextAware {
|
||||
|
||||
private CompletableFuture<Void> loaded;
|
||||
|
||||
private Set<String> scanFiles = Collections.emptySet();
|
||||
private Set<String> scanDirs = Collections.emptySet();
|
||||
private Set<String> recipeFilters = Collections.emptySet();
|
||||
private Set<String> scanFiles;
|
||||
private Set<String> scanDirs;
|
||||
private Set<String> recipeFilters;
|
||||
|
||||
private static Gson serializationGson = new GsonBuilder()
|
||||
.registerTypeAdapter(Duration.class, new DurationTypeConverter())
|
||||
@@ -120,33 +113,31 @@ public class RewriteRecipeRepository implements ApplicationContextAware {
|
||||
this.server = server;
|
||||
this.projectFinder = projectFinder;
|
||||
this.recipes = new HashMap<>();
|
||||
this.globalCommandRecipes = new ArrayList<>();
|
||||
this.codeActionDescriptors = new ArrayList<>();
|
||||
this.loadListeners = new ListenerList<>();
|
||||
this.scanDirs = UNINITIALIZED_SET;
|
||||
this.scanFiles = UNINITIALIZED_SET;
|
||||
this.recipeFilters = UNINITIALIZED_SET;
|
||||
|
||||
server.doOnInitialized(() -> {
|
||||
this.scanDirs = config.getRecipeDirectories();
|
||||
this.scanFiles = config.getRecipeFiles();
|
||||
this.recipeFilters = config.getRecipesFilters();
|
||||
load().thenAccept(v -> registerCommands());
|
||||
config.addListener(l -> {
|
||||
if (!scanDirs.equals(config.getRecipeDirectories())
|
||||
|| !scanFiles.equals(config.getRecipeFiles())) {
|
||||
// Eclipse client sends one event for init and the other config changed event due to remote app value expr listener.
|
||||
// Therefore it is best to store the scanDirs here right after it is received, not during scan process or anything else done async
|
||||
scanDirs = config.getRecipeDirectories();
|
||||
scanFiles = config.getRecipeFiles();
|
||||
load();
|
||||
}
|
||||
Set<String> recipeFilterFromConfig = config.getRecipesFilters();
|
||||
if (!recipeFilters.equals(recipeFilterFromConfig)) {
|
||||
recipeFilters = recipeFilterFromConfig;
|
||||
updateGlobalCommandRecipes();
|
||||
}
|
||||
});
|
||||
config.addListener(l -> {
|
||||
Set<String> recipeFilterFromConfig = config.getRecipesFilters();
|
||||
if (recipeFilters == UNINITIALIZED_SET || recipeFilters.equals(recipeFilterFromConfig)) {
|
||||
recipeFilters = recipeFilterFromConfig;
|
||||
}
|
||||
if (scanDirs == UNINITIALIZED_SET || !scanDirs.equals(config.getRecipeDirectories())
|
||||
|| scanFiles == UNINITIALIZED_SET || !scanFiles.equals(config.getRecipeFiles())) {
|
||||
// Eclipse client sends one event for init and the other config changed event due to remote app value expr listener.
|
||||
// Therefore it is best to store the scanDirs here right after it is received, not during scan process or anything else done async
|
||||
scanDirs = config.getRecipeDirectories();
|
||||
scanFiles = config.getRecipeFiles();
|
||||
load();
|
||||
}
|
||||
});
|
||||
|
||||
registerCommands();
|
||||
}
|
||||
|
||||
|
||||
public CompletableFuture<Void> load() {
|
||||
return this.loaded = CompletableFuture.runAsync(() -> {
|
||||
clearRecipes();
|
||||
@@ -157,7 +148,6 @@ public class RewriteRecipeRepository implements ApplicationContextAware {
|
||||
|
||||
private synchronized void clearRecipes() {
|
||||
recipes.clear();
|
||||
globalCommandRecipes.clear();
|
||||
codeActionDescriptors.clear();
|
||||
}
|
||||
|
||||
@@ -172,11 +162,7 @@ public class RewriteRecipeRepository implements ApplicationContextAware {
|
||||
if (recipes.containsKey(r.getName())) {
|
||||
log.error("Duplicate ids: '" + r.getName() + "'");
|
||||
}
|
||||
recipes.put(r.getName(), r);
|
||||
|
||||
if (isAcceptableGlobalCommandRecipe(r)) {
|
||||
globalCommandRecipes.add(r);
|
||||
}
|
||||
recipes.put(r.getName(), r);
|
||||
}
|
||||
}
|
||||
codeActionDescriptors.addAll(env.listCodeActionDescriptors());
|
||||
@@ -188,15 +174,6 @@ public class RewriteRecipeRepository implements ApplicationContextAware {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateGlobalCommandRecipes() {
|
||||
globalCommandRecipes.clear();
|
||||
for (Recipe r : recipes.values()) {
|
||||
if (isAcceptableGlobalCommandRecipe(r)) {
|
||||
globalCommandRecipes.add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAcceptableGlobalCommandRecipe(Recipe r) {
|
||||
for (String filter : recipeFilters) {
|
||||
if (!filter.isBlank()) {
|
||||
@@ -318,10 +295,6 @@ public class RewriteRecipeRepository implements ApplicationContextAware {
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
log.info("Registering commands for rewrite recipes...");
|
||||
|
||||
Builder<Object> listBuilder = ImmutableList.builder();
|
||||
|
||||
server.onCommand(CMD_REWRITE_LIST, params -> {
|
||||
JsonElement uri = (JsonElement) params.getArguments().get(0);
|
||||
return loaded.thenApply(v -> {
|
||||
@@ -332,59 +305,38 @@ public class RewriteRecipeRepository implements ApplicationContextAware {
|
||||
}
|
||||
});
|
||||
});
|
||||
listBuilder.add(CMD_REWRITE_LIST);
|
||||
|
||||
server.onCommand(CMD_REWRITE_EXECUTE, params -> {
|
||||
String uri = ((JsonElement) params.getArguments().get(0)).getAsString();
|
||||
JsonElement recipesJson = ((JsonElement) params.getArguments().get(1));
|
||||
|
||||
RecipeDescriptor d = serializationGson.fromJson(recipesJson, RecipeDescriptor.class);
|
||||
|
||||
Recipe aggregateRecipe = LoadUtils.createRecipe(d, id -> getRecipe(id).map(r -> r.getClass()).orElse(null));
|
||||
|
||||
if (aggregateRecipe instanceof DeclarativeRecipe && aggregateRecipe.getRecipeList().isEmpty()) {
|
||||
throw new RuntimeException("No recipes found to perform!");
|
||||
} else if (aggregateRecipe.getRecipeList().size() == 1) {
|
||||
Recipe r = aggregateRecipe.getRecipeList().get(0);
|
||||
String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? r.getName() : params.getWorkDoneToken().getLeft();
|
||||
return apply(r, uri, progressToken);
|
||||
} else {
|
||||
String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? aggregateRecipe.getName() : params.getWorkDoneToken().getLeft();
|
||||
return apply(aggregateRecipe, uri, progressToken);
|
||||
}
|
||||
return loaded.thenCompose(v -> {
|
||||
String uri = ((JsonElement) params.getArguments().get(0)).getAsString();
|
||||
JsonElement recipesJson = ((JsonElement) params.getArguments().get(1));
|
||||
|
||||
RecipeDescriptor d = serializationGson.fromJson(recipesJson, RecipeDescriptor.class);
|
||||
|
||||
Recipe aggregateRecipe = LoadUtils.createRecipe(d, id -> getRecipe(id).map(r -> r.getClass()).orElse(null));
|
||||
|
||||
if (aggregateRecipe instanceof DeclarativeRecipe && aggregateRecipe.getRecipeList().isEmpty()) {
|
||||
throw new RuntimeException("No recipes found to perform!");
|
||||
} else if (aggregateRecipe.getRecipeList().size() == 1) {
|
||||
Recipe r = aggregateRecipe.getRecipeList().get(0);
|
||||
String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? r.getName() : params.getWorkDoneToken().getLeft();
|
||||
return apply(r, uri, progressToken);
|
||||
} else {
|
||||
String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? aggregateRecipe.getName() : params.getWorkDoneToken().getLeft();
|
||||
return apply(aggregateRecipe, uri, progressToken);
|
||||
}
|
||||
});
|
||||
});
|
||||
listBuilder.add(CMD_REWRITE_EXECUTE);
|
||||
|
||||
server.onCommand(CMD_REWRITE_RELOAD, params -> load().thenApply((v) -> "executed"));
|
||||
listBuilder.add(CMD_REWRITE_RELOAD);
|
||||
|
||||
for (Recipe r : globalCommandRecipes) {
|
||||
listBuilder.add(createGlobalCommand(r));
|
||||
}
|
||||
|
||||
String registrationId = UUID.randomUUID().toString();
|
||||
RegistrationParams params = new RegistrationParams(ImmutableList.of(
|
||||
new Registration(registrationId,
|
||||
WORKSPACE_EXECUTE_COMMAND,
|
||||
ImmutableMap.of("commands", listBuilder.build())
|
||||
)
|
||||
));
|
||||
|
||||
server.getClient().registerCapability(params).thenAccept((v) -> {
|
||||
server.onShutdown(() -> server.getClient().unregisterCapability(new UnregistrationParams(List.of(new Unregistration(registrationId, WORKSPACE_EXECUTE_COMMAND)))));
|
||||
log.info("Done registering commands for rewrite recipes");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private String createGlobalCommand(Recipe r) {
|
||||
String commandId = "sts/rewrite/recipe/" + r.getName();
|
||||
server.onCommand(commandId, params -> {
|
||||
server.onCommand(CMD_REWRITE_RECIPE_EXECUTE, params -> {
|
||||
String recipeId = ((JsonElement) params.getArguments().get(0)).getAsString();
|
||||
Recipe r = getRecipe(recipeId).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(0)).getAsString();
|
||||
String uri = ((JsonElement) params.getArguments().get(1)).getAsString();
|
||||
return apply(r, uri, progressToken);
|
||||
});
|
||||
return commandId;
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<Object> apply(Recipe r, String uri, String progressToken) {
|
||||
@@ -416,8 +368,9 @@ public class RewriteRecipeRepository implements ApplicationContextAware {
|
||||
server.getProgressService().progressDone(progressToken);
|
||||
throw t;
|
||||
}
|
||||
} else {
|
||||
return CompletableFuture.failedFuture(new IllegalArgumentException("Cannot find Spring Boot project for uri: " + uri));
|
||||
}
|
||||
return CompletableFuture.completedFuture(null);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -441,14 +394,15 @@ public class RewriteRecipeRepository implements ApplicationContextAware {
|
||||
|
||||
private List<Recipe> listProjectRefactoringRecipes(String uri) {
|
||||
if (uri != null) {
|
||||
Optional<IJavaProject> projectOpt = projectFinder.find(new TextDocumentIdentifier(uri));
|
||||
if (projectOpt.isPresent()) {
|
||||
List<Recipe> commandDescriptors = new ArrayList<>(globalCommandRecipes.size());
|
||||
for (Recipe r : globalCommandRecipes) {
|
||||
commandDescriptors.add(r);
|
||||
}
|
||||
return commandDescriptors;
|
||||
}
|
||||
/*
|
||||
* When LS started on listing rewrite recipes project lookup may not find any projects as classpath might still be resolving.
|
||||
* Therefore, it is best probably to list the available recipes and figure out if it is a Spring Boot project recipes is applied to
|
||||
* and if not throw an exception.
|
||||
*/
|
||||
// Optional<IJavaProject> projectOpt = projectFinder.find(new TextDocumentIdentifier(uri));
|
||||
// if (projectOpt.isPresent()) {
|
||||
return recipes.values().stream().filter(this::isAcceptableGlobalCommandRecipe).collect(Collectors.toList());
|
||||
// }
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -89,9 +89,7 @@ async function liveHoverConnectHandler(uri: VSCode.Uri) {
|
||||
if (!uri) {
|
||||
uri = await getTargetPomXml();
|
||||
}
|
||||
const cmds: RecipeDescriptor[] = await VSCode.commands.executeCommand('sts/rewrite/list', uri.toString(true));
|
||||
const choices = cmds.map(convertToQuickPickItem);
|
||||
await showCurrentPathQuickPick(choices, []);
|
||||
const choices = await showCurrentPathQuickPick(VSCode.commands.executeCommand('sts/rewrite/list', uri.toString(true)).then((cmds: RecipeDescriptor[]) => cmds.map(convertToQuickPickItem)), []);
|
||||
const recipeDescriptors = choices.filter(i => i.selected).map(convertToRecipeDescriptor);
|
||||
if (recipeDescriptors.length) {
|
||||
const aggregateRecipeDescriptor = recipeDescriptors.length === 1 ? recipeDescriptors[0] : {
|
||||
@@ -133,56 +131,61 @@ function convertToQuickPickItem(i: RecipeDescriptor): RecipeQuickPickItem {
|
||||
};
|
||||
}
|
||||
|
||||
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<RecipeQuickPickItem>();
|
||||
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));
|
||||
function showCurrentPathQuickPick(itemsPromise: Thenable<RecipeQuickPickItem[]>, itemsPath: RecipeQuickPickItem[]): Thenable<RecipeQuickPickItem[]> {
|
||||
const quickPick = VSCode.window.createQuickPick<RecipeQuickPickItem>();
|
||||
quickPick.title = 'Loading Recipes...';
|
||||
quickPick.canSelectMany = true;
|
||||
quickPick.busy = true;
|
||||
quickPick.show();
|
||||
return itemsPromise.then(items => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let currentItems = items;
|
||||
let parent: RecipeQuickPickItem | undefined;
|
||||
itemsPath.forEach(p => {
|
||||
parent = currentItems.find(i => i === p);
|
||||
currentItems = parent.children;
|
||||
});
|
||||
quickPick.items = currentItems;
|
||||
if (itemsPath.length) {
|
||||
itemsPath.pop();
|
||||
showCurrentPathQuickPick(items, itemsPath).then(resolve, reject);
|
||||
} else {
|
||||
quickPick.hide();
|
||||
resolve();
|
||||
quickPick.buttons = [ ROOT_RECIPES_BUTTON ];
|
||||
}
|
||||
});
|
||||
quickPick.onDidChangeSelection(selected => {
|
||||
currentItems.forEach(i => {
|
||||
const isSelectedItem = selected.includes(i);
|
||||
if (i.selected !== isSelectedItem) {
|
||||
selectItemRecursively(i, isSelectedItem);
|
||||
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(Promise.resolve(items), itemsPath).then(resolve, reject);
|
||||
}
|
||||
});
|
||||
updateParentSelection(itemsPath.slice());
|
||||
quickPick.onDidTriggerButton(b => {
|
||||
if (b === ROOT_RECIPES_BUTTON) {
|
||||
currentItems.forEach(i => i.selected = quickPick.selectedItems.includes(i));
|
||||
itemsPath.splice(0, itemsPath.length);
|
||||
showCurrentPathQuickPick(Promise.resolve(items), itemsPath).then(resolve, reject);
|
||||
}
|
||||
});
|
||||
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.onDidChangeSelection(selected => {
|
||||
currentItems.forEach(i => {
|
||||
const isSelectedItem = selected.includes(i);
|
||||
if (i.selected !== isSelectedItem) {
|
||||
selectItemRecursively(i, isSelectedItem);
|
||||
}
|
||||
});
|
||||
updateParentSelection(itemsPath.slice());
|
||||
});
|
||||
quickPick.title = 'Select Recipes';
|
||||
quickPick.busy = false;
|
||||
});
|
||||
quickPick.show();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user