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 af2c6edf7..3328f7308 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 @@ -30,7 +30,8 @@ Require-Bundle: org.eclipse.jdt.launching;bundle-version="3.9.0", org.eclipse.mylyn.wikitext.markdown, org.eclipse.mylyn.wikitext, org.springframework.ide.eclipse.boot.dash;bundle-version="3.9.12", - com.google.gson + com.google.gson, + org.eclipse.lsp4e.jdt;bundle-version="0.10.0" 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/plugin.xml b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml index 3b4d3e4df..bb1ef7f78 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml @@ -110,37 +110,6 @@ - - - - - - - - - - - - - - - { - if (getInput() == null) - return; - Browser browser = (Browser) event.getSource(); - @Nullable - Point constraints = getSizeConstraints(); - Point hint = computeSizeHint(); - - setSize(hint.x, hint.y); - browser.execute("document.getElementsByTagName(\"html\")[0].style.whiteSpace = \"nowrap\""); //$NON-NLS-1$ - Double width = 20 + (Double) browser.evaluate("return document.body.scrollWidth;"); //$NON-NLS-1$ - if (constraints != null && constraints.x < width) { - width = (double) constraints.x; - } - - setSize(width.intValue(), hint.y); - browser.execute("document.getElementsByTagName(\"html\")[0].style.whiteSpace = \"normal\""); //$NON-NLS-1$ - Double height = (Double) browser.evaluate("return document.body.scrollHeight;"); //$NON-NLS-1$ - if (constraints != null && constraints.y < height) { - height = (double) constraints.y; - } - if (Platform.getPreferencesService().getBoolean(EditorsUI.PLUGIN_ID, - AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TEXT_HOVER_AFFORDANCE, true, - null)) { - FontData[] fontDatas = JFaceResources.getDialogFont().getFontData(); - height = fontDatas[0].getHeight() + height; - } - setSize(width.intValue(), height.intValue()); - })); - b.setJavascriptEnabled(true); - } - - @Override - public IInformationControlCreator getInformationPresenterControlCreator() { - return parent -> { - BrowserInformationControl res = new BrowserInformationControl(parent, JFaceResources.DEFAULT_FONT, - true); - res.addLocationListener(HYPER_LINK_LISTENER); - return res; - }; - } -} \ No newline at end of file diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/jdt/LSPTextHover.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/jdt/LSPTextHover.java deleted file mode 100644 index 1831e18d3..000000000 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/jdt/LSPTextHover.java +++ /dev/null @@ -1,262 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016, 2018 Red Hat Inc. and others. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Mickael Istria (Red Hat Inc.) - initial implementation - * Lucas Bullen (Red Hat Inc.) - Bug 508458 - Add support for codelens - * Angelo Zerr - Bug 525602 - LSBasedHover must check if LS have codelens capability - * Lucas Bullen (Red Hat Inc.) - [Bug 517428] Requests sent before initialization - * Kris De Volder - copied from lsp4e see: https://www.pivotaltracker.com/story/show/167763955 - *******************************************************************************/ -package org.springframework.tooling.boot.ls.jdt; - -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jface.internal.text.html.BrowserInformationControl; -import org.eclipse.jface.resource.ColorRegistry; -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.jface.text.AbstractReusableInformationControlCreator; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.DefaultInformationControl; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IInformationControl; -import org.eclipse.jface.text.IInformationControlCreator; -import org.eclipse.jface.text.IRegion; -import org.eclipse.jface.text.ITextHover; -import org.eclipse.jface.text.ITextHoverExtension; -import org.eclipse.jface.text.ITextViewer; -import org.eclipse.jface.text.Region; -import org.eclipse.lsp4e.LSPEclipseUtils; -import org.eclipse.lsp4e.LanguageServerPlugin; -import org.eclipse.lsp4e.LanguageServiceAccessor; -import org.eclipse.lsp4j.Hover; -import org.eclipse.lsp4j.MarkedString; -import org.eclipse.lsp4j.MarkupContent; -import org.eclipse.lsp4j.jsonrpc.messages.Either; -import org.eclipse.mylyn.wikitext.markdown.MarkdownLanguage; -import org.eclipse.mylyn.wikitext.parser.MarkupParser; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.RGB; -import org.eclipse.swt.widgets.Shell; - -/** - * LSP implementation of {@link org.eclipse.jface.text.ITextHover} - * - */ -public class LSPTextHover implements ITextHover, ITextHoverExtension { - - private static final String HEAD = ""; //$NON-NLS-1$ - - - private static final MarkupParser MARKDOWN_PARSER = new MarkupParser(new MarkdownLanguage()); - - - private IRegion lastRegion; - private ITextViewer lastViewer; - private CompletableFuture> request; - - public LSPTextHover() { - // nothing to init yet, comment requested by sonar - } - - public static String styleHtml(String html) { - if (html == null || html.isEmpty()) { - return html; - } - // put CSS styling to match Eclipse style - ColorRegistry colorRegistry = JFaceResources.getColorRegistry(); - Color foreground = colorRegistry.get("org.eclipse.ui.workbench.HOVER_FOREGROUND"); //$NON-NLS-1$ - Color background = colorRegistry.get("org.eclipse.ui.workbench.HOVER_BACKGROUND"); //$NON-NLS-1$ - String style = ""; //$NON-NLS-1$ - - int headIndex = html.indexOf(HEAD); - StringBuilder builder = new StringBuilder(html.length() + style.length()); - builder.append(html.substring(0, headIndex + HEAD.length())); - builder.append(style); - builder.append(html.substring(headIndex + HEAD.length())); - return builder.toString(); - } - - @Override - public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { - if (textViewer == null || hoverRegion == null) { - return null; - } - if (this.request == null || !textViewer.equals(this.lastViewer) || !hoverRegion.equals(this.lastRegion)) { - initiateHoverRequest(textViewer, hoverRegion.getOffset()); - } - try { - String result = request.get(500, TimeUnit.MILLISECONDS).stream() - .filter(Objects::nonNull) - .map(LSPTextHover::getHoverString) - .filter(Objects::nonNull) - .collect(Collectors.joining("\n\n")) //$NON-NLS-1$ - .trim(); - if (!result.isEmpty()) { - return styleHtml(MARKDOWN_PARSER.parseToHtml(result)); - } - } catch (ExecutionException | TimeoutException e) { - LanguageServerPlugin.logError(e); - } catch (InterruptedException e) { - LanguageServerPlugin.logError(e); - Thread.currentThread().interrupt(); - } - return null; - } - - protected static @Nullable String getHoverString(@NonNull Hover hover) { - Either>, MarkupContent> hoverContent = hover.getContents(); - if (hoverContent.isLeft()) { - List> contents = hoverContent.getLeft(); - if (contents == null || contents.isEmpty()) { - return null; - } - return contents.stream().map(content -> { - if (content.isLeft()) { - return content.getLeft(); - } else if (content.isRight()) { - MarkedString markedString = content.getRight(); - // TODO this won't work fully until markup parser will support syntax - // highlighting but will help display - // strings with language tags, e.g. without it things after ) String::isEmpty).negate()).collect(Collectors.joining("\n\n")); //$NON-NLS-1$ ) - } else { - return hoverContent.getRight().getValue(); - } - } - - private static @NonNull String toHTMLrgb(RGB rgb) { - StringBuilder builder = new StringBuilder(7); - builder.append('#'); - appendAsHexString(builder, rgb.red); - appendAsHexString(builder, rgb.green); - appendAsHexString(builder, rgb.blue); - return builder.toString(); - } - - private static void appendAsHexString(StringBuilder buffer, int intValue) { - String hexValue= Integer.toHexString(intValue); - if (hexValue.length() == 1) { - buffer.append('0'); - } - buffer.append(hexValue); - } - - @Override - public IRegion getHoverRegion(ITextViewer textViewer, int offset) { - if (textViewer == null) { - return null; - } - if (this.request == null || this.lastRegion == null || !textViewer.equals(this.lastViewer) - || offset < this.lastRegion.getOffset() || offset > lastRegion.getOffset() + lastRegion.getLength()) { - initiateHoverRequest(textViewer, offset); - } - try { - final IDocument document = textViewer.getDocument(); - boolean[] oneHoverAtLeast = new boolean[] { false }; - int[] regionStartOffset = new int[] { 0 }; - int[] regionEndOffset = new int[] { document.getLength() }; - this.request.get(500, TimeUnit.MILLISECONDS).stream() - .filter(Objects::nonNull) - .map(Hover::getRange) - .filter(Objects::nonNull) - .forEach(range -> { - try { - regionStartOffset[0] = Math.max(regionStartOffset[0], - LSPEclipseUtils.toOffset(range.getStart(), document)); - regionEndOffset[0] = Math.min(regionEndOffset[0], - LSPEclipseUtils.toOffset(range.getEnd(), document)); - oneHoverAtLeast[0] = true; - } catch (BadLocationException e) { - LanguageServerPlugin.logError(e); - } - }); - if (oneHoverAtLeast[0]) { - this.lastRegion = new Region(regionStartOffset[0], regionEndOffset[0] - regionStartOffset[0]); - return this.lastRegion; - } - } catch (ExecutionException | TimeoutException e1) { - LanguageServerPlugin.logError(e1); - } catch (InterruptedException e1) { - LanguageServerPlugin.logError(e1); - Thread.currentThread().interrupt(); - } - this.lastRegion = new Region(offset, 0); - return this.lastRegion; - } - - /** - * Initialize hover requests with hover (if available) and codelens (if - * available). - * - * @param viewer - * the text viewer. - * @param offset - * the hovered offset. - */ - private void initiateHoverRequest(@NonNull ITextViewer viewer, int offset) { - final IDocument document = viewer.getDocument(); - this.lastViewer = viewer; - this.request = LanguageServiceAccessor - .getLanguageServers(document, capabilities -> Boolean.TRUE.equals(capabilities.getHoverProvider())) - .thenApplyAsync(languageServers -> // Async is very important here, otherwise the LS Client thread is in - // deadlock and doesn't read bytes from LS - languageServers.stream() - .map(languageServer -> { - try { - return languageServer.getTextDocumentService() - .hover(LSPEclipseUtils.toHoverParams(offset, document)).get(); - } catch (ExecutionException | BadLocationException e) { - LanguageServerPlugin.logError(e); - return null; - } catch (InterruptedException e) { - LanguageServerPlugin.logError(e); - Thread.currentThread().interrupt(); - return null; - } - }).filter(Objects::nonNull).collect(Collectors.toList())); - } - - @Override - public IInformationControlCreator getHoverControlCreator() { - return new AbstractReusableInformationControlCreator() { - @Override - protected IInformationControl doCreateInformationControl(Shell parent) { - if (BrowserInformationControl.isAvailable(parent)) { - return new FocusableBrowserInformationControl(parent); - } else { - return new DefaultInformationControl(parent); - } - } - }; - } -} diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/jdt/SpringBootJavaCompletionProposalComputer.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/jdt/SpringBootJavaCompletionProposalComputer.java deleted file mode 100644 index 79a7107d3..000000000 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/jdt/SpringBootJavaCompletionProposalComputer.java +++ /dev/null @@ -1,162 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017, 2019 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 - * https://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Pivotal, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.tooling.boot.ls.jdt; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext; -import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; -import org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.contentassist.ICompletionProposal; -import org.eclipse.jface.text.contentassist.IContextInformation; -import org.eclipse.lsp4e.operations.completion.LSContentAssistProcessor; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.Point; - -/** - * @author Martin Lippert - */ -@SuppressWarnings("restriction") -public class SpringBootJavaCompletionProposalComputer implements IJavaCompletionProposalComputer { - - private static TimeUnit TIMEOUT_UNIT = TimeUnit.MILLISECONDS; - private static long TIMEOUT_LENGTH = 2000; - - private LSContentAssistProcessor lsContentAssistProcessor; - - public SpringBootJavaCompletionProposalComputer() { - lsContentAssistProcessor = new LSContentAssistProcessor(); - } - - @Override - public void sessionStarted() { - } - - @Override - public List computeCompletionProposals(ContentAssistInvocationContext context, - IProgressMonitor monitor) { - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - return lsContentAssistProcessor.computeCompletionProposals(context.getViewer(), context.getInvocationOffset()); - }); - - try { - return Arrays.asList(asJavaProposals(future)); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - e.printStackTrace(); - return Collections.emptyList(); - } - } - - /** - * PT 159667257 - Strange sorting order - * - * In order for boot LS proposals to appear in the right order by JDT, we need to return IJavaCompletionProposal - * . the LSPCompletionProposal that LSP4E computes is NOT IJavaCompletionProposal, and as a consequence JDT - * will by default sort any non Java proposals by display value, which is why we get strange sorting order, even if our boot LS - * and LSP4E both return a proposal list in the right order. - * - * This method wraps around the LSCompletionProposal with a IJavaCompletionProposal, and it sets the relevance - * number that JDT uses to sort proposals in a desired order. - * @param future - * @return - * @throws InterruptedException - * @throws ExecutionException - * @throws TimeoutException - */ - private ICompletionProposal[] asJavaProposals(CompletableFuture future) - throws InterruptedException, ExecutionException, TimeoutException { - ICompletionProposal[] originalProposals = future.get(TIMEOUT_LENGTH, TIMEOUT_UNIT); - - // We assume that the original proposals are in the correct order, so we set relevance - // based on this existing order. Note that based on IJavaCompletionProposal javadoc, - // relevance values are [0,1000] so we start at 1000 - int relevance = 1000; - ICompletionProposal[] javaProposals = new ICompletionProposal[originalProposals.length]; - - for (int i = 0; i < originalProposals.length; i++) { - javaProposals[i] = new LSJavaProposal(originalProposals[i], relevance--); - } - - return javaProposals; - } - - @Override - public List computeContextInformation(ContentAssistInvocationContext context, - IProgressMonitor monitor) { - IContextInformation[] contextInformation = lsContentAssistProcessor.computeContextInformation(context.getViewer(), context.getInvocationOffset()); - return Arrays.asList(contextInformation); - } - - @Override - public String getErrorMessage() { - return lsContentAssistProcessor.getErrorMessage(); - } - - @Override - public void sessionEnded() { - } - - class LSJavaProposal implements IJavaCompletionProposal { - - private ICompletionProposal delegate; - private int relevance; - - public LSJavaProposal(ICompletionProposal delegate, int relevance) { - this.delegate = delegate; - this.relevance = relevance; - } - - @Override - public void apply(IDocument document) { - delegate.apply(document); - } - - @Override - public String getAdditionalProposalInfo() { - return delegate.getAdditionalProposalInfo(); - } - - @Override - public IContextInformation getContextInformation() { - return delegate.getContextInformation(); - } - - @Override - public String getDisplayString() { - return delegate.getDisplayString(); - } - - @Override - public Image getImage() { - return delegate.getImage(); - } - - @Override - public Point getSelection(IDocument document) { - return delegate.getSelection(document); - } - - @Override - public int getRelevance() { - return relevance; - } - - } - -} diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/jdt/SpringBootJavaHoverProvider.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/jdt/SpringBootJavaHoverProvider.java deleted file mode 100644 index bcde43f8f..000000000 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/jdt/SpringBootJavaHoverProvider.java +++ /dev/null @@ -1,96 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017, 2019 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 - * https://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Pivotal, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.tooling.boot.ls.jdt; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.internal.ui.text.java.hover.JavadocBrowserInformationControlInput; -import org.eclipse.jdt.internal.ui.text.java.hover.JavadocHover; -import org.eclipse.jface.text.IRegion; -import org.eclipse.jface.text.ITextViewer; - -/** - * @author Martin Lippert - */ -@SuppressWarnings("restriction") -public class SpringBootJavaHoverProvider extends JavadocHover { - - private LSPTextHover lsBasedHover; - - public SpringBootJavaHoverProvider() { - super(); - lsBasedHover = new LSPTextHover(); - } - - @Override - public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) { - // Launch javadoc hover computation in async fashion - CompletableFuture javadocHoverFuture = CompletableFuture.supplyAsync( - () -> (JavadocBrowserInformationControlInput) super.getHoverInfo2(textViewer, hoverRegion)); - String bootContent = this.lsBasedHover.getHoverInfo(textViewer, hoverRegion); - if (bootContent != null && !bootContent.isEmpty()) { - IJavaElement javaElement = null; - JavadocBrowserInformationControlInput previous = null; - int leadingImageWidth = 0; - JavadocBrowserInformationControlInput input; - String htmlContentFromOtherLs = ""; - try { - input = javadocHoverFuture.get(500, TimeUnit.MILLISECONDS); - if (input != null) { - previous = (JavadocBrowserInformationControlInput) input.getPrevious(); - javaElement = input.getElement(); - leadingImageWidth = input.getLeadingImageWidth(); - htmlContentFromOtherLs = input.getHtml(); - } - } catch (InterruptedException e) { - htmlContentFromOtherLs = noJavadocMessage("Javadoc unavailable."); - } catch (ExecutionException e) { - htmlContentFromOtherLs = noJavadocMessage("Javadoc unavailable. Failed to obtain it."); - } catch (TimeoutException e) { - htmlContentFromOtherLs = noJavadocMessage("Javadoc unavailable. Took too long to obtain it."); - } - String content = formatContent(bootContent, htmlContentFromOtherLs); - return new JavadocBrowserInformationControlInput(previous, javaElement, content, leadingImageWidth); - } else { - javadocHoverFuture.cancel(true); - } - return null; - } - - private String formatContent(String content, String contentFromElsewhere) { - if (content != null && content.trim().length() > 0 && contentFromElsewhere != null - && contentFromElsewhere.trim().length() > 0) { - // PT 162742296 - Introduce a separator between our content and content from - // elsewhere - return content + "
" + contentFromElsewhere; - } else { - return content + contentFromElsewhere; - } - } - - private String noJavadocMessage(String message) { - StringBuilder sb = new StringBuilder(); - sb.append("

"); - sb.append(message); - sb.append("

"); - return sb.toString(); - } - - @Override - public IRegion getHoverRegion(ITextViewer textViewer, int offset) { - return this.lsBasedHover.getHoverRegion(textViewer, offset); - } - -}