blob: 50d278573637bb45077e4e86e437386d9442228a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017, 2020 Pivotal 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:
* - Martin Lippert (Pivotal Inc.) - Initial implementation
*******************************************************************************/
package org.eclipse.lsp4e.jdt;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
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.jdt.ui.PreferenceConstants;
import org.eclipse.jface.internal.text.html.HTMLPrinter;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.lsp4e.LanguageServerPlugin;
import org.eclipse.lsp4e.operations.hover.LSPTextHover;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.RGB;
@SuppressWarnings("restriction")
public class LSJavaHoverProvider extends JavadocHover {
private static String fgStyleSheet;
private static String BODY_OPEN = "<body";
private static String BODY_CLOSE = "</body>";
private static String SEPARATOR = "<hr/>";
private LSPTextHover lsBasedHover;
public LSJavaHoverProvider() {
super();
lsBasedHover = new LSPTextHover();
}
@Override
public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) {
if (textViewer == null || hoverRegion == null) {
return super.getHoverInfo2(textViewer, hoverRegion);
}
CompletableFuture<String> lsHoverFuture = this.lsBasedHover.getHoverInfoFuture(textViewer, hoverRegion);
AtomicReference<String> lsHtmlHoverContent = new AtomicReference<>();
AtomicReference<JavadocBrowserInformationControlInput> jdtHoverControlInput = new AtomicReference<>();
JavadocBrowserInformationControlInput input;
IJavaElement javaElement = null;
JavadocBrowserInformationControlInput previous = null;
int leadingImageWidth = 0;
String jdtHtmlHoverContent = "";
try {
CompletableFuture.allOf(
CompletableFuture
.runAsync(
() -> jdtHoverControlInput.set((JavadocBrowserInformationControlInput) super.getHoverInfo2(textViewer, hoverRegion))),
lsHoverFuture.thenAccept(lsHtmlHoverContent::set)
).get(1000, TimeUnit.MILLISECONDS);
if (lsHtmlHoverContent.get() == null) {
// No hover content from Language Servers. Return null to let JDT compute the hover using its own Hover Providers
return null;
}
input = jdtHoverControlInput.get();
if (input != null) {
previous = (JavadocBrowserInformationControlInput) input.getPrevious();
javaElement = input.getElement();
leadingImageWidth = input.getLeadingImageWidth();
jdtHtmlHoverContent = input.getHtml();
}
} catch (InterruptedException | ExecutionException e) {
LanguageServerPlugin.logWarning("Javadoc unavailable. Failed to obtain it.", e);
// Return null to let JDT compute the hover using its own Hover Providers
return null;
} catch (TimeoutException e) {
LanguageServerPlugin.logWarning("Timeout waiting for data to generate LS hover", e);
// Return null to let JDT compute the hover using its own Hover Providers
return null;
}
/*
* LS HTML and JDT HTML are two HTML docs that need to be combined. JDT HTML comes with embedded CSS.
* Therefore it is best to insert LS HTML body inside the body of JDT HTML to take advantage of the JDT CSS.
*/
String content = formatContent(lsHtmlHoverContent.get(), jdtHtmlHoverContent);
return new JavadocBrowserInformationControlInput(previous, javaElement, content, leadingImageWidth);
}
private String formatContent(String lsContent, String jdtContent) {
if (lsContent != null && lsContent.trim().length() > 0 && jdtContent != null
&& jdtContent.trim().length() > 0) {
return concatenateHtml(lsContent, jdtContent);
} else if (lsContent != null && (jdtContent == null || jdtContent.trim().isEmpty())) {
return wrapHtml(lsContent).toString();
} else {
return (lsContent == null ? "" : lsContent) + (jdtContent == null ? "" : jdtContent);
}
}
private static StringBuilder 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;
}
private static String concatenateHtml(String lsHtml, String jdtHtml) {
int insertPosition = jdtHtml.indexOf(BODY_OPEN);
if (insertPosition >= 0) {
insertPosition = jdtHtml.indexOf('>', insertPosition);
if (insertPosition >= 0 && insertPosition < jdtHtml.length()) {
// skip <body> tag close
insertPosition++;
int bodyStartIdx = lsHtml.indexOf(BODY_OPEN);
int bodyEndIdx = -1;
if (bodyStartIdx >= 0) {
bodyStartIdx = lsHtml.indexOf('>', bodyStartIdx);
if (bodyStartIdx >= 0 && bodyStartIdx < lsHtml.length()) {
// skip <body> tag close
bodyStartIdx++;
bodyEndIdx = lsHtml.indexOf(BODY_CLOSE, bodyStartIdx);
if (bodyEndIdx >= bodyStartIdx && bodyEndIdx <= lsHtml.length()) {
return jdtHtml.substring(0, insertPosition) + lsHtml.substring(bodyStartIdx, bodyEndIdx) + SEPARATOR + jdtHtml.substring(insertPosition);
} else {
LanguageServerPlugin.logWarning("LS Hover Html and JDT hover html were naively concatenated as LS hover HTML BODY tag closing bracket is at invalid position", null);
}
} else {
LanguageServerPlugin.logWarning("LS Hover Html and JDT hover html were naively concatenated as LS hover HTML BODY tag closing bracket wasn't found", null);
}
} else {
LanguageServerPlugin.logWarning("LS Hover Html and JDT hover html were naively concatenated as LS hover HTML BODY tag wasn't found", null);
}
} else {
LanguageServerPlugin.logWarning("LS Hover Html and JDT hover html were naively concatenated as JDT hover HTML BODY tag closing bracket wasn't found", null);
}
} else {
LanguageServerPlugin.logWarning("LS Hover Html and JDT hover html were naively concatenated as JDT hover HTML BODY tag wasn't found", null);
}
return lsHtml + SEPARATOR + jdtHtml;
}
@Override
public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
return this.lsBasedHover.getHoverRegion(textViewer, offset);
}
/**
* Taken from {@link JavadocHover}. It's <code>private</code>. 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;
}
}