Skip to content

Commit

Permalink
fix: Editor is already disposed in LSP hover operation (#914)
Browse files Browse the repository at this point in the history
Fixes #914

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
azerr committed Jun 7, 2023
1 parent 55e3388 commit 4a93a99
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 48 deletions.
@@ -0,0 +1,78 @@
/*******************************************************************************
* Copyright (c) 2020 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at https://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package com.redhat.devtools.intellij.lsp4ij.operations.hover;

import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileTypes.PlainTextLanguage;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.FakePsiElement;
import com.intellij.psi.impl.PsiElementBase;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.util.IncorrectOperationException;
import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils;
import com.redhat.devtools.intellij.lsp4ij.operations.diagnostics.LSPPsiReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.util.HashMap;
import java.util.Map;

public class LSPPsiElementForHover extends FakePsiElement {
private final Project project;
private final PsiFile file;
private Editor editor;

private int targetOffset;

public LSPPsiElementForHover(Editor editor, PsiFile file, int targetOffset) {
this.editor = editor;
this.file = file;
this.project = LSPIJUtils.getProject(file.getVirtualFile()).getProject();
this.targetOffset = targetOffset;
}

public int getTargetOffset() {
return targetOffset;
}

public Editor getEditor() {
return editor;
}

@NotNull
@Override
public Project getProject() throws PsiInvalidElementAccessException {
return project;
}

@Override
public PsiFile getContainingFile() {
return file;
}

@Override
public PsiElement getParent() {
return null;
}

@Override
public boolean isValid() {
return true;
}
}
Expand Up @@ -60,7 +60,7 @@ public class LSPTextHover extends DocumentationProviderEx implements ExternalDoc

private PsiElement lastElement;
private int lastOffset = -1;
private CompletableFuture<List<Hover>> request,lspRequest;
private CompletableFuture<List<Hover>> lspRequest;

public LSPTextHover() {
LOGGER.info("LSPTextHover");
Expand Down Expand Up @@ -118,30 +118,20 @@ public List<String> getUrlFor(PsiElement element, PsiElement originalElement) {
return null;
}

private CompletableFuture<Integer> getCursorOffset(Editor editor) {
CompletableFuture<Integer> future = new CompletableFuture<>();
ApplicationManager.getApplication().invokeLater(() -> {
int offset = -1;
PointerInfo info = MouseInfo.getPointerInfo();
if (info != null/* && EditorUtil.isPointOverText(editor, info.getLocation())*/) {
Point location = info.getLocation();
SwingUtilities.convertPointFromScreen(location, editor.getContentComponent());
LogicalPosition position = editor.xyToLogicalPosition(location);
offset = editor.logicalPositionToOffset(position);
}
future.complete(offset);
});
return future;
@Override
public @Nullable PsiElement getCustomDocumentationElement(@NotNull Editor editor, @NotNull PsiFile file, @Nullable PsiElement contextElement, int targetOffset) {
return new LSPPsiElementForHover(editor, file, targetOffset);
}

@Nullable
@Override
public String generateDoc(PsiElement element, @Nullable PsiElement originalElement) {
Editor editor = LSPIJUtils.editorForElement(element);
if (editor != null) {
initiateHoverRequest(element, editor);
if (element instanceof LSPPsiElementForHover) {
LSPPsiElementForHover data = (LSPPsiElementForHover) element;
Editor editor = data.getEditor();
initiateHoverRequest(element, data.getTargetOffset());
try {
String result = request.get(500, TimeUnit.MILLISECONDS).stream()
String result = lspRequest.get(500, TimeUnit.MILLISECONDS).stream()
.filter(Objects::nonNull)
.map(LSPTextHover::getHoverString)
.filter(Objects::nonNull)
Expand Down Expand Up @@ -197,38 +187,34 @@ public String generateDoc(PsiElement element, @Nullable PsiElement originalEleme
*
* @param element
* the PSI element.
* @param editor
* the editor.
* @param offset
* the target offset.
*/
private void initiateHoverRequest(PsiElement element, Editor editor) {
private void initiateHoverRequest(PsiElement element, int offset) {
PsiDocumentManager manager = PsiDocumentManager.getInstance(element.getProject());
final Document document = manager.getDocument(element.getContainingFile());
this.request = getCursorOffset(editor).thenComposeAsync(offset -> {
if (offset != -1 && (this.lspRequest == null || !element.equals(this.lastElement) || offset != this.lastOffset)) {
this.lastElement = element;
this.lastOffset = offset;
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 -> {
try {
return languageServer.getTextDocumentService()
.hover(LSPIJUtils.toHoverParams(offset, document)).get();
} catch (ExecutionException e) {
LOGGER.warn(e.getLocalizedMessage(), e);
return null;
} catch (InterruptedException e) {
LOGGER.warn(e.getLocalizedMessage(), e);
Thread.currentThread().interrupt();
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toList()));

}
return this.lspRequest;
});
if (offset != -1 && (this.lspRequest == null || !element.equals(this.lastElement) || offset != this.lastOffset)) {
this.lastElement = element;
this.lastOffset = offset;
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 -> {
try {
return languageServer.getTextDocumentService()
.hover(LSPIJUtils.toHoverParams(offset, document)).get();
} catch (ExecutionException e) {
LOGGER.warn(e.getLocalizedMessage(), e);
return null;
} catch (InterruptedException e) {
LOGGER.warn(e.getLocalizedMessage(), e);
Thread.currentThread().interrupt();
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toList()));
}
}

private boolean isHoverCapable(ServerCapabilities capabilities) {
Expand Down

0 comments on commit 4a93a99

Please sign in to comment.