diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/META-INF/MANIFEST.MF b/eclipse-language-servers/org.springframework.tooling.boot.ls/META-INF/MANIFEST.MF index aa360a3b0..acfdd5921 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/META-INF/MANIFEST.MF +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/META-INF/MANIFEST.MF @@ -36,7 +36,8 @@ Require-Bundle: org.eclipse.jdt.launching;bundle-version="3.9.0", com.google.guava, org.eclipse.core.expressions, org.springsource.ide.eclipse.commons.core;bundle-version="4.17.0", - org.springframework.tooling.jdt.ls.commons;bundle-version="4.17.0" + org.springframework.tooling.jdt.ls.commons;bundle-version="4.17.0", + org.eclipse.jface.notifications Import-Package: com.google.common.base, com.google.common.collect, com.google.gson;version="2.7.0", diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java index f1a3bf608..993050512 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2022 Pivotal, Inc. + * Copyright (c) 2017, 2023 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 @@ -14,6 +14,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; +import java.net.URL; import java.nio.file.FileSystems; import java.util.Arrays; import java.util.Collection; @@ -24,7 +25,8 @@ import java.util.Map; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.preferences.IEclipsePreferences; -import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.notifications.AbstractNotificationPopup; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.lsp4e.server.StreamConnectionProvider; @@ -34,7 +36,18 @@ import org.eclipse.lsp4j.jsonrpc.messages.Message; import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage; import org.eclipse.lsp4j.services.LanguageServer; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Link; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.springframework.tooling.boot.ls.prefs.CategoryProblemsSeverityPrefsPage; import org.springframework.tooling.boot.ls.prefs.FileListEditor; @@ -164,19 +177,19 @@ public class DelegatingStreamConnectionProvider implements StreamConnectionProvi if (preferenceStore.getBoolean(Constants.PREF_REWRITE_RECONCILE_PROMPT) && !preferenceStore.getBoolean(Constants.PREF_REWRITE_RECONCILE)) { PlatformUI.getWorkbench().getDisplay().asyncExec(() -> { - int result = MessageDialog.open(MessageDialog.INFORMATION, Display.getCurrent().getActiveShell(), - "Spring Boot Java Reconciling", - "Do you wish to enable additional Java sources reconciling to get Spring specific validations and suggestions?", - SWT.NONE, "Yes", "No", "Do not show again"); - switch (result) { - case 0: - preferenceStore.setValue(Constants.PREF_REWRITE_RECONCILE, true); - break; - case 2: - preferenceStore.setValue(Constants.PREF_REWRITE_RECONCILE_PROMPT, false); - break; - default: - break; + try { + NotificationQuestionWithLink question = new NotificationQuestionWithLink( + "Spring Java Reconcile", + "Do you wish to enable additional Java sources reconciling to get Spring specific validations and suggestions?\n\n" + + "See Validations And Quick Fixes for more details.", + new URL("https://github.com/spring-projects/sts4/wiki/Validations-And-Quick-Fixes"), + () -> preferenceStore.setValue(Constants.PREF_REWRITE_RECONCILE, true), + () -> {}, + () -> preferenceStore.setValue(Constants.PREF_REWRITE_RECONCILE_PROMPT, false)); + question.setDelayClose(-1); + question.open(); + } catch (Exception e) { + BootLanguageServerPlugin.getDefault().getLog().error("Failed to Spring Java Reconcile popup", e); } }); } @@ -294,4 +307,115 @@ public class DelegatingStreamConnectionProvider implements StreamConnectionProvi return RemoteBootAppsDataHolder.getDefault().getRemoteApps().getValues(); } + private static class NotificationQuestionWithLink extends AbstractNotificationPopup { + + private final String title; + private final String textWithLinkTag; + private final URL linkUrl; + + /* + * Notification open does not block on open and ignores setBlockOnOpen(true) + * Therefore handlers are passed directly in until this changes + */ + private Runnable yesHandler; + private Runnable noHandler; + private Runnable stopAskingHandler; + + + public NotificationQuestionWithLink(String title, String textWithLinkTag, URL linkUrl, + Runnable yesHandler, Runnable noHandler, Runnable stopAskingHandler) { + super(Display.getCurrent()); + + this.title = title; + this.textWithLinkTag = textWithLinkTag; + this.linkUrl = linkUrl; + this.yesHandler = yesHandler; + this.noHandler = noHandler; + this.stopAskingHandler = stopAskingHandler; + + setParentShell(PlatformUI.getWorkbench().getDisplay().getActiveShell()); + setBlockOnOpen(true); + } + + @Override + public String getPopupShellTitle() { + return title; + } + + @Override + protected void createContentArea(Composite parent) { + Link link = new Link(parent, SWT.WRAP); + link.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + link.setText(textWithLinkTag); + if (linkUrl != null) { + link.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + try { + PlatformUI.getWorkbench().getBrowserSupport().getExternalBrowser().openURL(linkUrl); + } catch (PartInitException ex) { + BootLanguageServerPlugin.getDefault().getLog().error("Failed to open browser", ex); + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }); + } + + Composite buttonsComposite = new Composite(parent, SWT.None); + buttonsComposite.setLayoutData(GridDataFactory.swtDefaults().grab(true, false).align(SWT.RIGHT, SWT.CENTER).create()); + GridLayout buttonsLayout = new GridLayout(3, false); + buttonsLayout.horizontalSpacing = 0; + buttonsComposite.setLayout(buttonsLayout); + + Button yesButton = new Button(buttonsComposite, SWT.None); + yesButton.setText("Yes"); + yesButton.setLayoutData(GridDataFactory.swtDefaults().create()); + yesButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + setReturnCode(SWT.YES); + close(); + yesHandler.run(); + } + }); + + Button noButton = new Button(buttonsComposite, SWT.None); + noButton.setText("No"); + noButton.setLayoutData(GridDataFactory.swtDefaults().create()); + noButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + setReturnCode(SWT.NO); + close(); + noHandler.run(); + } + }); + + Button stopAskingButton = new Button(buttonsComposite, SWT.None); + stopAskingButton.setText("Stop Asking"); + stopAskingButton.setLayoutData(GridDataFactory.swtDefaults().create()); + stopAskingButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + setReturnCode(SWT.SAVE); + close(); + stopAskingHandler.run(); + } + }); + } + + @Override + public Image getPopupShellImage(int maximumHeight) { + return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_INFO_TSK); + } + + } + } diff --git a/vscode-extensions/vscode-spring-boot/lib/Main.ts b/vscode-extensions/vscode-spring-boot/lib/Main.ts index 74a3c6862..93786cee0 100644 --- a/vscode-extensions/vscode-spring-boot/lib/Main.ts +++ b/vscode-extensions/vscode-spring-boot/lib/Main.ts @@ -22,7 +22,7 @@ const FACTORIES_LANGUAGE_ID = "spring-factories"; const YES = 'Yes'; const NO = 'No'; -const NEVER_SHOW_AGAIN = "Do not show again"; +const STOP_ASKING = "Stop Asking"; const RECONCILING_PREF_KEY = 'boot-java.rewrite.reconcile'; const RECONCILING_PROMPT_PREF_KEY = 'vscode-spring-boot.rewrite.reconcile-prompt'; @@ -48,8 +48,8 @@ export function activate(context: VSCode.ExtensionContext): Thenable { - if (selection === NEVER_SHOW_AGAIN) { + STOP_ASKING).then(selection => { + if (selection === STOP_ASKING) { options.workspaceOptions.update('checkJVM', false); } } @@ -143,12 +143,13 @@ export function activate(context: VSCode.ExtensionContext): Thenable { + VSCode.window.showInformationMessage('Do you wish to enable additional Java sources reconciling to get Spring specific validations and suggestions?\n\n' + + 'For more details see [Validations And Quick Fixes](https://github.com/spring-projects/sts4/wiki/Validations-And-Quick-Fixes)', YES, NO, STOP_ASKING).then(answer => { switch (answer) { case YES: VSCode.workspace.getConfiguration().update(RECONCILING_PREF_KEY, true, true); break; - case NEVER_SHOW_AGAIN: + case STOP_ASKING: VSCode.workspace.getConfiguration().update(RECONCILING_PROMPT_PREF_KEY, false, true); break; default: