Skip to content

Commit

Permalink
fix: NPE in initiateHoverRequest (this.cancellationSupport is null)
Browse files Browse the repository at this point in the history
Fixes #1104

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr authored and fbricon committed Aug 16, 2023
1 parent d81ac03 commit 13d4b7b
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 143 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
package com.redhat.devtools.intellij.lsp4ij;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.redhat.devtools.intellij.lsp4ij.operations.diagnostics.LSPDiagnosticsForServer;
import com.redhat.devtools.intellij.lsp4ij.operations.documentLink.LSPDocumentLinkForServer;
import com.redhat.devtools.intellij.lsp4ij.operations.hover.LSPTextHoverForFile;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DocumentLink;

Expand All @@ -45,10 +48,12 @@ public class LSPVirtualFileWrapper implements Disposable {
private final VirtualFile file;

private final Map<LanguageServerWrapper, LSPVirtualFileData /* current LSP data (diagnostics, documentLink, etc) */> dataPerServer;
private final LSPTextHoverForFile hover;

LSPVirtualFileWrapper(VirtualFile file) {
this.file = file;
this.dataPerServer = new HashMap<>();
this.hover = new LSPTextHoverForFile();
Module project = LSPIJUtils.getProject(file);
if (project != null) {
Disposer.register(project, this);
Expand Down Expand Up @@ -103,6 +108,11 @@ public Collection<LSPDocumentLinkForServer> getAllDocumentLink() {
return getData(LSPVirtualFileData::getDocumentLinkForServer);
}


public String getHoverContent(PsiElement element, int targetOffset, Editor editor) {
return hover.getHoverContent(element, targetOffset, editor);
}

// ------------------------ Other methods

private LSPVirtualFileData getLSPVirtualFileData(LanguageServerWrapper languageServerWrapper) {
Expand Down Expand Up @@ -176,4 +186,5 @@ public static void dispose(VirtualFile file) {
wrapper.dispose();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils;
import com.redhat.devtools.intellij.lsp4ij.LSPVirtualFileWrapper;
import com.redhat.devtools.intellij.lsp4ij.LanguageServiceAccessor;
import com.redhat.devtools.intellij.lsp4ij.internal.CancellationSupport;
import com.vladsch.flexmark.html.HtmlRenderer;
Expand All @@ -44,62 +47,11 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
* LSP testDocument/hover support.
*
*/
public class LSPTextHover extends DocumentationProviderEx implements ExternalDocumentationHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(LSPTextHover.class);

private static final String HEAD = "<head>"; //$NON-NLS-1$

private static final Parser PARSER = Parser.builder().build();
private static final HtmlRenderer RENDERER = HtmlRenderer.builder().build();

private PsiElement lastElement;
private int lastOffset = -1;
private CompletableFuture<List<Hover>> lspRequest;
private CancellationSupport cancellationSupport;

public LSPTextHover() {
LOGGER.info("LSPTextHover");
}

public static String styleHtml(Editor editor, String html) {
if (html == null || html.isEmpty()) {
return html;
}
Color background = editor.getColorsScheme().getDefaultBackground();
Color foreground = editor.getColorsScheme().getDefaultForeground();
// put CSS styling to match Eclipse style
String style = "<html><head><style TYPE='text/css'>html { " + //$NON-NLS-1$
(background != null ? "background-color: " + toHTMLrgb(background) + "; " : "") + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
(foreground != null ? "color: " + toHTMLrgb(foreground) + "; " : "") + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
" }</style></head><body>"; //$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();*/
StringBuilder builder = new StringBuilder(style);
builder.append(html).append("</body></html>");
return builder.toString();
}

private static String toHTMLrgb(Color rgb) {
StringBuilder builder = new StringBuilder(7);
builder.append('#');
appendAsHexString(builder, rgb.getRed());
appendAsHexString(builder, rgb.getGreen());
appendAsHexString(builder, rgb.getBlue());
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);
}

@Nullable
@Override
Expand All @@ -110,109 +62,29 @@ public String getQuickNavigateInfo(PsiElement element, PsiElement originalElemen
@Nullable
@Override
public String generateDoc(PsiElement element, @Nullable PsiElement originalElement) {
Project project = element.getProject();
if (project.isDisposed()) {
return null;
}
if (originalElement == null || !Objects.equals(element.getContainingFile(), originalElement.getContainingFile())) {
return null;
}
Editor editor = LSPIJUtils.editorForElement(element);
if (editor == null) {
return null;
}

VirtualFile file = originalElement.getContainingFile().getVirtualFile();
int targetOffset = getTargetOffset(originalElement);
initiateHoverRequest(element, targetOffset);
try {
String result = lspRequest.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(editor, RENDERER.render(PARSER.parse(result)));
}
} catch (ExecutionException | TimeoutException e) {
LOGGER.warn(e.getLocalizedMessage(), e);
} catch (InterruptedException e) {
LOGGER.warn(e.getLocalizedMessage(), e);
Thread.currentThread().interrupt();
} finally {
this.cancellationSupport = null;
}
return null;
return LSPVirtualFileWrapper.getLSPVirtualFileWrapper(file).getHoverContent(element, targetOffset, editor);
}

private int getTargetOffset(PsiElement originalElement) {
private static int getTargetOffset(PsiElement originalElement) {
int startOffset = originalElement.getTextOffset();
int textLength = originalElement.getTextLength();
return startOffset + textLength / 2;
}

private static @Nullable String getHoverString(Hover hover) {
Either<List<Either<String, MarkedString>>, MarkupContent> hoverContent = hover.getContents();
if (hoverContent.isLeft()) {
List<Either<String, MarkedString>> 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 <?php tag aren't
// displayed
if (markedString.getLanguage() != null && !markedString.getLanguage().isEmpty()) {
return String.format("```%s%n%s%n```", markedString.getLanguage(), markedString.getValue()); //$NON-NLS-1$
} else {
return markedString.getValue();
}
} else {
return ""; //$NON-NLS-1$
}
}).filter(((Predicate<String>) String::isEmpty).negate()).collect(Collectors.joining("\n\n")); //$NON-NLS-1$ )
} else {
return hoverContent.getRight().getValue();
}
}


/**
* Initialize hover requests with hover (if available) and codelens (if
* available).
*
* @param element the PSI element.
* @param offset the target offset.
*/
private void initiateHoverRequest(PsiElement element, int offset) {
if (this.cancellationSupport != null) {
this.cancellationSupport.cancel();
this.cancellationSupport = null;
}
PsiDocumentManager manager = PsiDocumentManager.getInstance(element.getProject());
final Document document = manager.getDocument(element.getContainingFile());
if (offset != -1 && (this.lspRequest == null || !element.equals(this.lastElement) || offset != this.lastOffset)) {
this.lastElement = element;
this.lastOffset = offset;
this.cancellationSupport = new CancellationSupport();
this.lspRequest = LanguageServiceAccessor.getInstance(element.getProject())
.getLanguageServers(document, capabilities -> isHoverCapable(capabilities))
.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 ->
cancellationSupport.execute(
languageServer.getServer().getTextDocumentService()
.hover(LSPIJUtils.toHoverParams(offset, document)))
.join()
).filter(Objects::nonNull).collect(Collectors.toList()));
}
}

private boolean isHoverCapable(ServerCapabilities capabilities) {
return (capabilities.getHoverProvider().isLeft() && capabilities.getHoverProvider().getLeft()) || capabilities.getHoverProvider().isRight();
}

@Nullable
@Override
public PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Object object, PsiElement element) {
Expand Down
Loading

0 comments on commit 13d4b7b

Please sign in to comment.