From 8bf0736a1db6e8d222c0c922cec5293332eb815d Mon Sep 17 00:00:00 2001 From: aboyko Date: Tue, 21 Jun 2022 15:35:54 -0400 Subject: [PATCH] Eclipse UI for Rewrite Recipes --- .../plugin.xml | 20 +- .../boot/ls/commands/RecipeDescriptor.java | 69 +++++ .../boot/ls/commands/RecipeTreeModel.java | 146 ++++++++++ .../commands/RewriteRefactoringsHandler.java | 155 +++++++++++ .../boot/ls/commands/SelectRecipesDialog.java | 261 ++++++++++++++++++ .../ls/commands/UpgradeToBoot3Handler.java | 70 ----- 6 files changed, 641 insertions(+), 80 deletions(-) create mode 100644 eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RecipeDescriptor.java create mode 100644 eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RecipeTreeModel.java create mode 100644 eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteRefactoringsHandler.java create mode 100644 eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/SelectRecipesDialog.java delete mode 100644 eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/UpgradeToBoot3Handler.java diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml index 14d24f196..a1b321030 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml @@ -175,8 +175,8 @@ + class="org.springframework.tooling.boot.ls.commands.RewriteRefactoringsHandler" + commandId="org.springframework.tooling.boot.ls.Rewrite"> + description="Rewrite Refactorings for Spring Boot projects" + id="org.springframework.tooling.boot.ls.Rewrite" + name="Rewrite Refactorings..."> @@ -372,9 +372,9 @@ allPopups="false" locationURI="popup:org.springframework.ide.eclipse.ui.tools?after=boot"> @@ -389,7 +389,7 @@ + property="org.springsource.ide.eclipse.boot.isBootResource"> + property="org.springsource.ide.eclipse.boot.javaelement.isBootProject"> diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RecipeDescriptor.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RecipeDescriptor.java new file mode 100644 index 000000000..2ab085134 --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RecipeDescriptor.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2022 VMware, 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.tooling.boot.ls.commands; + +import java.net.URI; +import java.time.Duration; +import java.util.List; +import java.util.Set; + +class RecipeDescriptor { + + String name; + + String displayName; + + String description; + + Set tags; + + Duration estimatedEffortPerOccurrence; + + List options; + + List languages; + + List recipeList; + + URI source; + + RecipeDescriptor getCopyWithoutSubRecipes() { + RecipeDescriptor copy = new RecipeDescriptor(); + copy.name = name; + copy.displayName = displayName; + copy.description = description; + copy.tags = tags; + copy.estimatedEffortPerOccurrence = estimatedEffortPerOccurrence; + copy.options = options; + copy.languages = languages; + copy.source = source; + return copy; + } + + static class OptionDescriptor { + + String name; + + String type; + + String displayName; + + String description; + + String example; + + List valid; + + boolean required; + + Object value; + } +} \ No newline at end of file diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RecipeTreeModel.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RecipeTreeModel.java new file mode 100644 index 000000000..9d15a2eba --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RecipeTreeModel.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2022 VMware, 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.tooling.boot.ls.commands; + +import java.util.Arrays; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.Status; + + +public class RecipeTreeModel { + + public enum CheckedState { + UNCHECKED, + CHECKED, + GRAYED + } + + private RecipeDescriptor[] recipeDescriptors; + private Map parentMap = new IdentityHashMap<>(); + private Map checkedMap = new IdentityHashMap<>(); + + RecipeTreeModel(RecipeDescriptor[] recipeDescriptors) { + this.recipeDescriptors = recipeDescriptors; + for (RecipeDescriptor d : recipeDescriptors) { + initParentMap(d); + } + } + + private void initParentMap(RecipeDescriptor d) { + if (d.recipeList != null) { + for (RecipeDescriptor dc : d.recipeList) { + parentMap.put(dc, d); + initParentMap(dc); + } + } + } + + public void check(RecipeDescriptor d) { + if (simpleCheck(d)) { + inferCheckedStateFromChildren(parentMap.get(d)); + } + } + + private boolean simpleCheck(RecipeDescriptor d) { + if (checkedMap.get(d) != CheckedState.CHECKED) { + checkedMap.put(d, CheckedState.CHECKED); + for (RecipeDescriptor dc : d.recipeList) { + simpleCheck(dc); + } + return true; + } + return false; + } + + public void uncheck(RecipeDescriptor d) { + if (simpleUncheck(d)) { + inferCheckedStateFromChildren(parentMap.get(d)); + } + } + + private boolean simpleUncheck(RecipeDescriptor d) { + if (checkedMap.get(d) != CheckedState.UNCHECKED) { + checkedMap.put(d, CheckedState.UNCHECKED); + for (RecipeDescriptor dc : d.recipeList) { + simpleUncheck(dc); + } + return true; + } + return false; + } + + public CheckedState getCheckedState(RecipeDescriptor d) { + CheckedState state = checkedMap.get(d); + return state == null ? CheckedState.UNCHECKED : state; + } + + private void inferCheckedStateFromChildren(RecipeDescriptor d) { + if (d != null && d.recipeList != null) { + boolean all = true; + boolean none = true; + for (RecipeDescriptor child : d.recipeList) { + CheckedState childState = getCheckedState(child); + if (childState == CheckedState.UNCHECKED) { + all = false; + } else { + none = false; + } + } + CheckedState inferredState = CheckedState.GRAYED; + if (all) { + inferredState = CheckedState.CHECKED; + } else if (none) { + inferredState = CheckedState.UNCHECKED; + } + if (getCheckedState(d) != inferredState) { + checkedMap.put(d, inferredState); + inferCheckedStateFromChildren(parentMap.get(d)); + } + } + } + + public RecipeDescriptor[] getRecipeDescriptors() { + return recipeDescriptors; + } + + public RecipeDescriptor getSelectedRecipeDescriptors() throws CoreException { + RecipeDescriptor[] recipes = Arrays.stream(recipeDescriptors).map(this::copySelectedDescriptor).filter(Objects::nonNull).toArray(RecipeDescriptor[]::new); + if (recipes.length == 0) { + throw new CoreException(Status.error("No recipes selected")); + } else if (recipes.length == 1) { + return recipes[0]; + } else { + RecipeDescriptor aggregate = new RecipeDescriptor(); + aggregate.displayName = recipes.length + " recipes"; + aggregate.description = "Multiple recipes to be applied. Number of recipes " + recipes.length; + aggregate.tags = Arrays.stream(recipes).flatMap(r -> r.tags.stream()).collect(Collectors.toSet()); + aggregate.recipeList = Arrays.asList(recipes); + return aggregate; + } + } + + private RecipeDescriptor copySelectedDescriptor(RecipeDescriptor d) { + if (getCheckedState(d) != CheckedState.UNCHECKED) { + RecipeDescriptor copy = d.getCopyWithoutSubRecipes(); + if (d.recipeList != null) { + copy.recipeList = d.recipeList.stream().map(this::copySelectedDescriptor).filter(Objects::nonNull).collect(Collectors.toList()); + } + return copy; + } + return null; + } + +} diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteRefactoringsHandler.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteRefactoringsHandler.java new file mode 100644 index 000000000..6e999f3d4 --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteRefactoringsHandler.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright (c) 2022 VMware, 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.tooling.boot.ls.commands; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Type; +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.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.window.Window; +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; +import org.springframework.tooling.boot.ls.BootLanguageServerPlugin; +import org.springsource.ide.eclipse.commons.livexp.util.Log; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +public class RewriteRefactoringsHandler extends AbstractHandler { + + private static class DurationTypeConverter implements JsonSerializer, JsonDeserializer { + @Override + public JsonElement serialize(Duration src, Type srcType, JsonSerializationContext context) { + return new JsonPrimitive(src.toNanos()); + } + + @Override + public Duration deserialize(JsonElement json, Type type, JsonDeserializationContext context) + throws JsonParseException { + return Duration.ofNanos(json.getAsLong()); + } + } + + private static Gson serializationGson = new GsonBuilder() + .registerTypeAdapter(Duration.class, new DurationTypeConverter()) + .create(); + + + private static final String REWRITE_REFACTORINGS_LIST = "sts/rewrite/list"; + private static final String REWRITE_REFACTORINGS_EXEC = "sts/rewrite/execute"; + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + + IStructuredSelection selection = HandlerUtil.getCurrentStructuredSelection(event); + Object o = selection.getFirstElement(); + IProject project = null; + if (o instanceof IResource) { + project = ((IResource) o).getProject(); + } else if (o instanceof IProject) { + project = (IProject) o; + } else if (o instanceof IAdaptable) { + project = ((IAdaptable) o).getAdapter(IProject.class); + } + if (project != null) { + List<@NonNull LanguageServer> usedLanguageServers = LanguageServiceAccessor + .getActiveLanguageServers(serverCapabilities -> true); + + if (!usedLanguageServers.isEmpty()) { + final String uri = project.getLocationURI().toString(); + ExecuteCommandParams commandParams = new ExecuteCommandParams(); + commandParams.setCommand(REWRITE_REFACTORINGS_LIST); + commandParams.setArguments(List.of(uri)); + + try { + List allRewriteRecipesJson = new ArrayList<>(); + List 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 { + 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) { + Log.log(e); + } finally { + monitor.done(); + } + }); + + } catch (CoreException | InvocationTargetException | InterruptedException e) { + BootLanguageServerPlugin.getDefault().getLog().error(e.getMessage(), e); + } + } + }); + }); + + }); + } catch (Exception e) { + throw new ExecutionException("Failed to apply Rewrite recipe(s)", e); + } + } + + } + return null; + } + +} diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/SelectRecipesDialog.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/SelectRecipesDialog.java new file mode 100644 index 000000000..8ae43dc35 --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/SelectRecipesDialog.java @@ -0,0 +1,261 @@ +/******************************************************************************* + * Copyright (c) 2022 VMware, 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.tooling.boot.ls.commands; + +import java.util.Arrays; + +import org.eclipse.core.runtime.Status; +import org.eclipse.jdt.internal.ui.text.java.hover.JavadocHover; +import org.eclipse.jdt.ui.PreferenceConstants; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.dialogs.StatusDialog; +import org.eclipse.jface.internal.text.html.HTMLPrinter; +import org.eclipse.jface.layout.FillLayoutFactory; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.resource.ColorRegistry; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.viewers.CheckStateChangedEvent; +import org.eclipse.jface.viewers.CheckboxTreeViewer; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.ICheckStateProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.browser.Browser; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.PlatformUI; +import org.osgi.framework.FrameworkUtil; +import org.springframework.tooling.boot.ls.commands.RecipeDescriptor.OptionDescriptor; +import org.springframework.tooling.boot.ls.commands.RecipeTreeModel.CheckedState; + +public class SelectRecipesDialog extends StatusDialog { + + private static final int MARGIN = 5; + private static final String SELECT_REWRITE_RECIPE_S_FROM_THE_LIST = "Select Rewrite Recipe(s) from the list"; + private static String fgStyleSheet; + + private RecipeTreeModel model; + + public SelectRecipesDialog(Shell parentShell, RecipeTreeModel model) { + super(parentShell); + setShellStyle(getShellStyle() | SWT.RESIZE); + this.model = model; + } + + @Override + protected Control createDialogArea(Composite parent) { + SashForm form = new SashForm(parent, SWT.HORIZONTAL); + form.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create()); + form.setLayout(new FillLayout()); + + Composite left = new Composite(form, SWT.NONE); + left.setLayout(FillLayoutFactory.fillDefaults().margins(MARGIN, MARGIN).create()); + CheckboxTreeViewer treeViewer = new CheckboxTreeViewer(left); + treeViewer.setContentProvider(new ITreeContentProvider() { + + @Override + public Object[] getElements(Object inputElement) { + if (inputElement instanceof RecipeTreeModel) { + return ((RecipeTreeModel) inputElement).getRecipeDescriptors(); + } + return new Object[0]; + } + + @Override + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof RecipeDescriptor) { + RecipeDescriptor r = (RecipeDescriptor) parentElement; + return r.recipeList.toArray(new RecipeDescriptor[r.recipeList.size()]); + } + return new RecipeDescriptor[0]; + } + + @Override + public Object getParent(Object element) { + return null; + } + + @Override + public boolean hasChildren(Object element) { + if (element instanceof RecipeDescriptor) { + RecipeDescriptor r = (RecipeDescriptor) element; + return r.recipeList != null && !r.recipeList.isEmpty(); + } + return false; + } + }); + treeViewer.setLabelProvider(LabelProvider.createTextProvider(input -> { + if (input instanceof RecipeDescriptor) { + return ((RecipeDescriptor)input).displayName; + } + return "unknown"; + })); + treeViewer.setCheckStateProvider(new ICheckStateProvider() { + + @Override + public boolean isGrayed(Object element) { + if (element instanceof RecipeDescriptor) { + RecipeDescriptor r = (RecipeDescriptor) element; + return model.getCheckedState(r) == CheckedState.GRAYED; + } + return false; + } + + @Override + public boolean isChecked(Object element) { + if (element instanceof RecipeDescriptor) { + RecipeDescriptor r = (RecipeDescriptor) element; + return model.getCheckedState(r) != CheckedState.UNCHECKED; + } + return false; + } + }); + treeViewer.addCheckStateListener(new ICheckStateListener() { + + @Override + public void checkStateChanged(CheckStateChangedEvent event) { + if (event.getElement() instanceof RecipeDescriptor) { + RecipeDescriptor d = (RecipeDescriptor) event.getElement(); + if (event.getChecked()) { + model.check(d); + } else { + model.uncheck(d); + } + treeViewer.refresh(); + updateStatus(); + } + } + }); + + treeViewer.setInput(model); + + Composite right = new Composite(form, SWT.NONE); + right.setLayout(FillLayoutFactory.fillDefaults().extendedMargins(MARGIN, MARGIN).create()); + Browser docViewer = new Browser(right, SWT.NONE); + docViewer.setJavascriptEnabled(false); + + Display display= parent.getDisplay(); + docViewer.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); + docViewer.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + + // Cancel opening of new windows + docViewer.addOpenWindowListener(event -> event.required= true); + + // Replace browser's built-in context menu with none + docViewer.setMenu(new Menu(getShell(), SWT.NONE)); + + docViewer.setText(wrapHtml("Select a Recipe on the left to read description")); + + + treeViewer.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + Object o = event.getStructuredSelection().getFirstElement(); + if (o instanceof RecipeDescriptor) { + docViewer.setText(wrapHtml(buildHtmlDescriptionSnippet((RecipeDescriptor) o))); + } else { + docViewer.setText(wrapHtml("Select a Recipe on the left to read description")); + } + } + }); + + form.setWeights(new int[] { 50, 50 }); + + return form; + } + + + + @Override + protected Control createContents(Composite parent) { + Control c = super.createContents(parent); + setTitle("Select Rewrite Recipe"); + parent.getDisplay().asyncExec(() -> updateStatus()); + return c; + } + + private void updateStatus() { + boolean anythingSelected = Arrays.stream(model.getRecipeDescriptors()).anyMatch(d -> model.getCheckedState(d) != CheckedState.UNCHECKED); + updateStatus(anythingSelected ? Status.info(SELECT_REWRITE_RECIPE_S_FROM_THE_LIST) : Status.error(SELECT_REWRITE_RECIPE_S_FROM_THE_LIST)); + } + + private String buildHtmlDescriptionSnippet(RecipeDescriptor r) { + StringBuilder sb = new StringBuilder(); + sb.append("

"); + sb.append(r.description); + sb.append("

"); + sb.append("
    "); + for (OptionDescriptor option : r.options) { + if (option.value != null) { + sb.append("
  • "); + sb.append("
    ");
    +				sb.append(option.value);
    +				sb.append("
    "); + sb.append(option.description); + sb.append("
  • "); + } + } + sb.append("
"); + return sb.toString(); + } + + private static String wrapHtml(String html) { + /* + * No JDT content. Means no JDT CSS part either. Therefore add JDT CSS chunk to it. + */ + ColorRegistry registry = JFaceResources.getColorRegistry(); + RGB fgRGB = registry.getRGB("org.eclipse.jdt.ui.Javadoc.foregroundColor"); //$NON-NLS-1$ + RGB bgRGB= registry.getRGB("org.eclipse.jdt.ui.Javadoc.backgroundColor"); //$NON-NLS-1$ + + StringBuilder buffer = new StringBuilder(html); + HTMLPrinter.insertPageProlog(buffer, 0, fgRGB, bgRGB, getStyleSheet()); + HTMLPrinter.addPageEpilog(buffer); + return buffer.toString(); + } + + protected IDialogSettings getDialogBoundsSettings() { + String sectionName= getClass().getName() + "_dialogBounds"; //$NON-NLS-1$ + IDialogSettings settings = PlatformUI + .getDialogSettingsProvider(FrameworkUtil.getBundle(getClass())).getDialogSettings(); + IDialogSettings section= settings.getSection(sectionName); + if (section == null) + section= settings.addNewSection(sectionName); + return section; + } + + /** + * Taken from {@link JavadocHover}. It's private. See {@link JavadocHover#getStyleSheet()}. + * @return CSS as string + */ + private static String getStyleSheet() { + if (fgStyleSheet == null) { + fgStyleSheet= JavadocHover.loadStyleSheet("/JavadocHoverStyleSheet.css"); //$NON-NLS-1$ + } + String css= fgStyleSheet; + if (css != null) { + FontData fontData= JFaceResources.getFontRegistry().getFontData(PreferenceConstants.APPEARANCE_JAVADOC_FONT)[0]; + css= HTMLPrinter.convertTopLevelFont(css, fontData); + } + return css; + } + +} diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/UpgradeToBoot3Handler.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/UpgradeToBoot3Handler.java deleted file mode 100644 index 515382705..000000000 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/UpgradeToBoot3Handler.java +++ /dev/null @@ -1,70 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 VMware, 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 - * https://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * VMware, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.tooling.boot.ls.commands; - -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.IAdaptable; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.lsp4e.LanguageServiceAccessor; -import org.eclipse.lsp4j.ExecuteCommandParams; -import org.eclipse.lsp4j.services.LanguageServer; -import org.eclipse.ui.handlers.HandlerUtil; -import org.springsource.ide.eclipse.commons.livexp.util.Log; - -public class UpgradeToBoot3Handler extends AbstractHandler { - - private static final String UPGRADE_TO_BOOT_3_COMMAND_ID = "sts/rewrite/recipe/org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0"; - - @Override - public Object execute(ExecutionEvent event) throws ExecutionException { - - IStructuredSelection selection = HandlerUtil.getCurrentStructuredSelection(event); - Object o = selection.getFirstElement(); - IProject project = null; - if (o instanceof IResource) { - project = ((IResource) o).getProject(); - } else if (o instanceof IProject) { - project = (IProject) o; - } else if (o instanceof IAdaptable) { - project = ((IAdaptable) o).getAdapter(IProject.class); - } - if (project != null) { - List<@NonNull LanguageServer> usedLanguageServers = LanguageServiceAccessor - .getActiveLanguageServers(serverCapabilities -> true); - - if (!usedLanguageServers.isEmpty()) { - ExecuteCommandParams commandParams = new ExecuteCommandParams(); - commandParams.setCommand(UPGRADE_TO_BOOT_3_COMMAND_ID); - commandParams.setArguments(List.of(project.getLocationURI().toString())); - - try { - CompletableFuture.allOf(usedLanguageServers.stream() - .map(ls -> ls.getWorkspaceService().executeCommand(commandParams)) - .toArray(CompletableFuture[]::new)); - } catch (Exception e) { - Log.log(e); - throw new ExecutionException("Failed to perform Upgarde to Spring Boot 3", e); - } - } - - } - return null; - } - -}