Send rewrite commands only to Boot LS. Start Boot LS on menu invocation

This commit is contained in:
aboyko
2022-11-20 21:51:48 -05:00
parent 297edabf46
commit dd2f60ad91
3 changed files with 69 additions and 62 deletions

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2020 Pivotal, Inc.
* Copyright (c) 2017, 2022 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -31,6 +31,8 @@ import org.springframework.tooling.boot.ls.prefs.CategoryProblemsSeverityPrefsPa
*/
public class BootLanguageServerPlugin extends AbstractUIPlugin {
public static String BOOT_LS_DEFINITION_ID = "org.eclipse.languageserver.languages.springboot";
public static String PLUGIN_ID = "org.springframework.tooling.boot.ls";
private static final Object LSP4E_COMMAND_SYMBOL_IN_WORKSPACE = "org.eclipse.lsp4e.symbolinworkspace";

View File

@@ -16,23 +16,24 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.lsp4e.LanguageServerWrapper;
import org.eclipse.lsp4e.LanguageServersRegistry;
import org.eclipse.lsp4e.LanguageServersRegistry.LanguageServerDefinition;
import org.eclipse.lsp4e.LanguageServiceAccessor;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.handlers.HandlerUtil;
@@ -88,70 +89,63 @@ public class RewriteRefactoringsHandler extends AbstractHandler {
}
try {
if (project != null && CoreUtil.promptForProjectSave(project)) {
List<@NonNull LanguageServer> usedLanguageServers = LanguageServiceAccessor
.getActiveLanguageServers(serverCapabilities -> true);
LanguageServerDefinition def = LanguageServersRegistry.getInstance().getDefinition(BootLanguageServerPlugin.BOOT_LS_DEFINITION_ID);
Assert.isLegal(def != null, "No definition found for Boot Language Server");
LanguageServerWrapper wrapper = LanguageServiceAccessor.getLSWrapper(project, def);
if (!usedLanguageServers.isEmpty()) {
final String uri = project.getLocationURI().toString();
ExecuteCommandParams commandParams = new ExecuteCommandParams();
commandParams.setCommand(REWRITE_REFACTORINGS_LIST);
commandParams.setArguments(List.of(uri));
final String uri = project.getLocationURI().toString();
ExecuteCommandParams commandParams = new ExecuteCommandParams();
commandParams.setCommand(REWRITE_REFACTORINGS_LIST);
commandParams.setArguments(List.of(uri));
try {
List<Object> allRewriteRecipesJson = new ArrayList<>();
List<Object> syncRecipesJson = Collections.synchronizedList(allRewriteRecipesJson);
CompletableFuture.allOf(usedLanguageServers.stream()
.map(ls -> ls.getWorkspaceService().executeCommand(commandParams).thenAccept(or -> {
if (or != null) {
syncRecipesJson.add(or);
}
}).exceptionally(t -> null))
.toArray(CompletableFuture[]::new)).thenRun(() -> {
allRewriteRecipesJson.stream().filter(List.class::isInstance).map(List.class::cast).findFirst().ifPresent(obj -> {
RecipeDescriptor[] descriptors = serializationGson.fromJson(serializationGson.toJson(obj), RecipeDescriptor[].class);
PlatformUI.getWorkbench().getDisplay().asyncExec(() -> {
RecipeTreeModel recipesModel = new RecipeTreeModel(descriptors);
int returnCode = new SelectRecipesDialog(Display.getCurrent().getActiveShell(), recipesModel).open();
if (returnCode == Window.OK) {
try {
List<Object> allRewriteRecipesJson = new ArrayList<>();
List<Object> syncRecipesJson = Collections.synchronizedList(allRewriteRecipesJson);
wrapper.getInitializedServer().thenComposeAsync(ls -> ls.getWorkspaceService().executeCommand(commandParams).thenAccept(or -> {
if (or != null) {
syncRecipesJson.add(or);
}
}))
.thenRun(() -> {
allRewriteRecipesJson.stream().filter(List.class::isInstance).map(List.class::cast).findFirst().ifPresent(obj -> {
RecipeDescriptor[] descriptors = serializationGson.fromJson(serializationGson.toJson(obj), RecipeDescriptor[].class);
PlatformUI.getWorkbench().getDisplay().asyncExec(() -> {
RecipeTreeModel recipesModel = new RecipeTreeModel(descriptors);
int returnCode = new SelectRecipesDialog(Display.getCurrent().getActiveShell(), recipesModel).open();
if (returnCode == Window.OK) {
try {
RecipeDescriptor recipeToApply = recipesModel.getSelectedRecipeDescriptors();
PlatformUI.getWorkbench().getProgressService().run(true, false, monitor -> {
try {
RecipeDescriptor recipeToApply = recipesModel.getSelectedRecipeDescriptors();
PlatformUI.getWorkbench().getProgressService().run(true, false, monitor -> {
try {
if (!usedLanguageServers.isEmpty()) {
monitor.beginTask("Applying recipe '" + recipeToApply.displayName + "'", IProgressMonitor.UNKNOWN);
ExecuteCommandParams cmdParams = new ExecuteCommandParams();
cmdParams.setCommand(REWRITE_REFACTORINGS_EXEC);
cmdParams.setArguments(List.of(
uri,
serializationGson.toJsonTree(recipeToApply)
));
CompletableFuture.allOf(usedLanguageServers.stream()
.map(ls -> ls.getWorkspaceService().executeCommand(cmdParams))
.toArray(CompletableFuture[]::new)).get();
}
} catch (Exception e) {
throw new InvocationTargetException(e);
} finally {
monitor.done();
}
});
monitor.beginTask("Applying recipe '" + recipeToApply.displayName + "'", IProgressMonitor.UNKNOWN);
ExecuteCommandParams cmdParams = new ExecuteCommandParams();
cmdParams.setCommand(REWRITE_REFACTORINGS_EXEC);
cmdParams.setArguments(List.of(
uri,
serializationGson.toJsonTree(recipeToApply)
));
} catch (CoreException | InvocationTargetException | InterruptedException e) {
BootLanguageServerPlugin.getDefault().getLog().error(e.getMessage(), e);
MessageDialog.openError(Display.getCurrent().getActiveShell(), "Error", "Failed to apply Rewrite recipe(s). See error log for more details.");
wrapper.getInitializedServer()
.thenComposeAsync(ls -> ls.getWorkspaceService().executeCommand(cmdParams)).get();
} catch (Exception e) {
throw new InvocationTargetException(e);
} finally {
monitor.done();
}
}
});
});
});
} catch (CoreException | InvocationTargetException | InterruptedException e) {
BootLanguageServerPlugin.getDefault().getLog().error(e.getMessage(), e);
MessageDialog.openError(Display.getCurrent().getActiveShell(), "Error", "Failed to apply Rewrite recipe(s). See error log for more details.");
}
}
});
} catch (Exception e) {
throw new ExecutionException("Failed to apply Rewrite recipe(s)", e);
}
});
});
} catch (Exception e) {
throw new ExecutionException("Failed to apply Rewrite recipe(s)", e);
}
}
} catch (Exception e) {
throw new ExecutionException("Failed to save project resource(s)", e);

View File

@@ -118,9 +118,11 @@ public class RewriteRecipeRepository implements ApplicationContextAware {
this.scanDirs = UNINITIALIZED_SET;
this.scanFiles = UNINITIALIZED_SET;
this.recipeFilters = UNINITIALIZED_SET;
CompletableFuture<Void> firstConfigLoaded = new CompletableFuture<>();
config.addListener(l -> {
Set<String> recipeFilterFromConfig = config.getRecipesFilters();
boolean firstTimeConfig = recipeFilters == UNINITIALIZED_SET && scanDirs == UNINITIALIZED_SET && scanFiles == UNINITIALIZED_SET;
if (recipeFilters == UNINITIALIZED_SET || recipeFilters.equals(recipeFilterFromConfig)) {
recipeFilters = recipeFilterFromConfig;
}
@@ -130,10 +132,19 @@ public class RewriteRecipeRepository implements ApplicationContextAware {
// 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();
if (!firstTimeConfig) {
load();
}
}
// First time config loaded. Received when server fully initialized. Start load after the firstConfigLoaded then and assign it to loaded field such that it is never null
if (firstTimeConfig) {
firstConfigLoaded.complete(null);
}
});
// Initial configuration is followed by load() as a special case to have 'loaded' future value to never be null
this.loaded = firstConfigLoaded.thenCompose(v -> load());
registerCommands();
}