Skip to content

Commit

Permalink
Hover fix Computing eventually switches to content
Browse files Browse the repository at this point in the history
  • Loading branch information
BoykoAlex authored and mickaelistria committed Nov 10, 2022
1 parent a0dd75a commit 4faef9d
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Red Hat Inc. and others.
* Copyright (c) 2016-2022 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/
Expand All @@ -12,6 +12,8 @@
package org.eclipse.lsp4e.operations.codeactions;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand All @@ -28,13 +30,23 @@
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.AbstractInformationControlManager;
import org.eclipse.jface.text.ITextHover;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension2;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.quickassist.IQuickAssistAssistant;
import org.eclipse.jface.text.quickassist.QuickAssistAssistant;
import org.eclipse.jface.text.source.ISourceViewerExtension3;
import org.eclipse.lsp4e.LSPEclipseUtils;
import org.eclipse.lsp4e.LanguageServerPlugin;
import org.eclipse.lsp4e.LanguageServersRegistry;
import org.eclipse.lsp4e.LanguageServersRegistry.LanguageServerDefinition;
import org.eclipse.lsp4e.LanguageServiceAccessor;
import org.eclipse.lsp4e.operations.diagnostics.LSPDiagnosticsToMarkers;
import org.eclipse.lsp4e.ui.Messages;
import org.eclipse.lsp4e.ui.UI;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionContext;
import org.eclipse.lsp4j.CodeActionOptions;
Expand All @@ -46,9 +58,11 @@
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IMarkerResolution;
import org.eclipse.ui.IMarkerResolution2;
import org.eclipse.ui.IMarkerResolutionGenerator2;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.progress.ProgressInfoItem;

public class LSPCodeActionMarkerResolution implements IMarkerResolutionGenerator2 {
Expand Down Expand Up @@ -121,8 +135,8 @@ private void checkMarkerResoultion(IMarker marker) throws IOException, CoreExcep
IFile file = (IFile)res;
Object[] attributes = marker.getAttributes(new String[]{LSPDiagnosticsToMarkers.LANGUAGE_SERVER_ID, LSPDiagnosticsToMarkers.LSP_DIAGNOSTIC});
String languageServerId = (String) attributes[0];
Diagnostic diagnostic = (Diagnostic) attributes[1];
List<CompletableFuture<?>> futures = new ArrayList<>();
Diagnostic diagnostic = (Diagnostic) attributes[1];
for (CompletableFuture<LanguageServer> lsf : getLanguageServerFutures(file, languageServerId)) {
marker.setAttribute(LSP_REMEDIATION, COMPUTING);
CodeActionContext context = new CodeActionContext(Collections.singletonList(diagnostic));
Expand All @@ -136,6 +150,14 @@ private void checkMarkerResoultion(IMarker marker) throws IOException, CoreExcep
codeAction.thenAcceptAsync(actions -> {
try {
marker.setAttribute(LSP_REMEDIATION, actions);
Display display = PlatformUI.getWorkbench().getDisplay();
display.asyncExec(() -> {
ITextViewer textViewer = UI.getActiveTextViewer();
if (textViewer != null) {
// Do not re-invoke hover right away as hover may not be showing at all yet
display.timerExec(500, () -> reinvokeQuickfixProposalsIfNecessary(textViewer));
}
});
} catch (CoreException e) {
LanguageServerPlugin.logError(e);
}
Expand Down Expand Up @@ -183,6 +205,45 @@ private List<CompletableFuture<LanguageServer>> getLanguageServerFutures(@NonNul
return languageServerFutures;
}

private void reinvokeQuickfixProposalsIfNecessary(ITextViewer textViewer) {
try {
// Quick assist proposals popup case
if (textViewer instanceof ISourceViewerExtension3) {
IQuickAssistAssistant quickAssistant = ((ISourceViewerExtension3)textViewer).getQuickAssistAssistant();
Field f = QuickAssistAssistant.class.getDeclaredField("fQuickAssistAssistantImpl"); //$NON-NLS-1$
if (f != null) {
f.setAccessible(true);
ContentAssistant ca = (ContentAssistant) f.get(quickAssistant);
Method m = ContentAssistant.class.getDeclaredMethod("isProposalPopupActive"); //$NON-NLS-1$
if (m != null) {
m.setAccessible(true);
boolean isProposalPopupActive = (Boolean) m.invoke(ca);
if (isProposalPopupActive) {
quickAssistant.showPossibleQuickAssists();
}
}
}
}
// Hover case
if (textViewer instanceof ITextViewerExtension2) {
ITextHover hover = ((ITextViewerExtension2) textViewer).getCurrentTextHover();
boolean hoverShowing = hover != null;
if (hoverShowing) {
Field f = TextViewer.class.getDeclaredField("fTextHoverManager"); //$NON-NLS-1$
if (f != null) {
f.setAccessible(true);
AbstractInformationControlManager manager = (AbstractInformationControlManager) f.get(textViewer);
if (manager != null) {
manager.showInformation();
}
}
}
}
} catch (Exception e) {
LanguageServerPlugin.logError(e);
}
}

static boolean providesCodeActions(@NonNull ServerCapabilities serverCapabilities) {
Either<Boolean, CodeActionOptions> codeActionProvider = serverCapabilities.getCodeActionProvider();
if (codeActionProvider == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,14 @@ public ICompletionProposal[] computeQuickAssistProposals(IQuickAssistInvocationC
// If context has changed, i.e. neq quick assist invocation rather than old
// proposals computed and calling this method artificially to show proposals in
// the UI
boolean proposalsRefreshInProgress = false;
synchronized (lock) {
if (cachedContext != invocationContext) {
cachedContext = invocationContext;
proposals.clear();
} else {
proposalsRefreshInProgress = true;
proposals.remove(COMPUTING);
}
}

Expand All @@ -160,21 +164,14 @@ public ICompletionProposal[] computeQuickAssistProposals(IQuickAssistInvocationC
List<CompletableFuture<Void>> futures = Collections.emptyList();
try {
// Prevent infinite re-entrance by only computing proposals if there aren't any
if (proposals.contains(COMPUTING) || proposals.isEmpty()) {
if (!proposalsRefreshInProgress) {
proposals.clear();
futures = infos.stream()
.map(info -> info.getInitializedLanguageClient()
.thenComposeAsync(ls -> ls.getTextDocumentService().codeAction(params)
.thenAcceptAsync(actions -> actions.stream().filter(Objects::nonNull)
.map(action -> new CodeActionCompletionProposal(action, info))
.forEach(p -> {
// non-ui thread. Context might have changed (quick assist at a different spot) by the time time proposals are computed
synchronized(lock) {
if (cachedContext == invocationContext) {
proposals.add(p);
}
}
}))))
.forEach(p -> processNewProposal(invocationContext, p)))))
.collect(Collectors.toList());

CompletableFuture<?> aggregateFutures = CompletableFuture
Expand All @@ -195,6 +192,15 @@ public ICompletionProposal[] computeQuickAssistProposals(IQuickAssistInvocationC
return proposals.toArray(new ICompletionProposal[proposals.size()]);
}

private void processNewProposal(IQuickAssistInvocationContext invocationContext, ICompletionProposal p) {
// non-ui thread. Context might have changed (quick assist at a different spot) by the time time proposals are computed
synchronized(lock) {
if (cachedContext == invocationContext) {
proposals.add(p);
}
}
}

/**
* Reinvokes the quickAssist in order to refresh the list of completion
* proposals
Expand Down
12 changes: 11 additions & 1 deletion org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/UI.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021 Vegard IT GmbH and others.
* Copyright (c) 2021, 2022 Vegard IT GmbH 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/
Expand All @@ -12,6 +12,7 @@
package org.eclipse.lsp4e.ui;

import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
Expand Down Expand Up @@ -61,6 +62,15 @@ public static ITextEditor getActiveTextEditor() {
return null;
}

@Nullable
public static ITextViewer getActiveTextViewer() {
ITextEditor editor = getActiveTextEditor();
if (editor != null) {
return editor.getAdapter(ITextViewer.class);
}
return null;
}

@Nullable
public static IWorkbenchWindow getActiveWindow() {
return PlatformUI.getWorkbench().getActiveWorkbenchWindow();
Expand Down

0 comments on commit 4faef9d

Please sign in to comment.