IDEA-CR-55322: IDEA-226443: Calculate method parameter hint info in non UI thread

GitOrigin-RevId: 0bbeb440f6c86617ba9a9d159c8534d5120406c8
This commit is contained in:
Vladimir Dolzhenko
2019-11-25 09:31:23 +00:00
committed by intellij-monorepo-bot
parent b029ade28b
commit 40ac8526c3
10 changed files with 210 additions and 88 deletions
@@ -275,12 +275,6 @@ public class MethodParameterInfoHandler implements ParameterInfoHandlerWithTabAc
@Override
public void updateParameterInfo(@NotNull final PsiExpressionList o, @NotNull final UpdateParameterInfoContext context) {
PsiElement parameterOwner = context.getParameterOwner();
if (parameterOwner != o) {
context.removeHint();
return;
}
int offset = context.getOffset();
TextRange elRange = o.getTextRange();
int index = offset <= elRange.getStartOffset() || offset >= elRange.getEndOffset()
@@ -502,7 +502,7 @@ public class ParameterInfoTest extends AbstractParameterInfoTestCase {
public void testCustomHandlerHighlighterWithEscaping() {
myFixture.configureByText(PlainTextFileType.INSTANCE, " ");
LanguageParameterInfo.INSTANCE.addExplicitExtension(PlainTextLanguage.INSTANCE, new ParameterInfoHandler<Object, Object>() {
LanguageParameterInfo.INSTANCE.addExplicitExtension(PlainTextLanguage.INSTANCE, new ParameterInfoHandler<PsiElement, Object>() {
@Override
public boolean couldShowInLookup() {
return false;
@@ -516,24 +516,24 @@ public class ParameterInfoTest extends AbstractParameterInfoTestCase {
@NotNull
@Override
public Object findElementForParameterInfo(@NotNull CreateParameterInfoContext context) {
public PsiElement findElementForParameterInfo(@NotNull CreateParameterInfoContext context) {
context.setItemsToShow(new Object[]{this});
return this;
return context.getFile();
}
@Override
public void showParameterInfo(@NotNull Object element, @NotNull CreateParameterInfoContext context) {
public void showParameterInfo(@NotNull PsiElement element, @NotNull CreateParameterInfoContext context) {
context.showHint(context.getFile(), context.getOffset(), this);
}
@NotNull
@Override
public Object findElementForUpdatingParameterInfo(@NotNull UpdateParameterInfoContext context) {
return this;
public PsiElement findElementForUpdatingParameterInfo(@NotNull UpdateParameterInfoContext context) {
return context.getFile();
}
@Override
public void updateParameterInfo(@NotNull Object o, @NotNull UpdateParameterInfoContext context) {}
public void updateParameterInfo(@NotNull PsiElement o, @NotNull UpdateParameterInfoContext context) {}
@Override
public void updateUI(Object p, @NotNull ParameterInfoUIContext context) {
@@ -15,14 +15,21 @@
package com.intellij.lang.parameterInfo;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface ParameterInfoHandler <ParameterOwner, ParameterType> {
import java.awt.*;
public interface ParameterInfoHandler <ParameterOwner extends Object & PsiElement, ParameterType> {
boolean couldShowInLookup();
@Nullable Object[] getParametersForLookup(LookupElement item, ParameterInfoContext context);
// Find element for parameter info should also set ItemsToShow in context and may set highlighted element
/**
* Find psiElement for parameter info should also set ItemsToShow in context and may set highlighted element
*
* Note: it is executed on non UI thread
*/
@Nullable
ParameterOwner findElementForParameterInfo(@NotNull CreateParameterInfoContext context);
// Usually context.showHint
@@ -31,9 +38,25 @@ public interface ParameterInfoHandler <ParameterOwner, ParameterType> {
// Null returns leads to removing hint
@Nullable
ParameterOwner findElementForUpdatingParameterInfo(@NotNull UpdateParameterInfoContext context);
/**
* <p>Updates parameter info context due to change of caret position.</p>
*
* <p>It could update context and state of {@link UpdateParameterInfoContext#getObjectsToView()}</p>
*
* <p>Note: <code>context.getParameterOwner()</code> equals to <code>parameterOwner</code> or <code>null</code></p>
*
* <p>Note: it is executed on non UI thread.</p>
*/
void updateParameterInfo(@NotNull final ParameterOwner parameterOwner, @NotNull UpdateParameterInfoContext context);
// context.setEnabled / context.setupUIComponentPresentation
/**
* <p>This method is executed on UI thread and supposed only to update UI representation using
* {@link ParameterInfoUIContext#setUIComponentEnabled(boolean)} or {@link ParameterInfoUIContext#setupUIComponentPresentation(String, int, int, boolean, boolean, boolean, Color)}.</p>
*
* <p>Don't perform any heavy calculations like resolve here: move it to {@link #findElementForParameterInfo(CreateParameterInfoContext)} or
* {@link #updateParameterInfo(Object, UpdateParameterInfoContext)}.</p>
*/
void updateUI(ParameterType p, @NotNull ParameterInfoUIContext context);
default boolean supportsOverloadSwitching() { return false; }
@@ -136,7 +136,8 @@ public class AutoPopupControllerImpl extends AutoPopupController {
try {
PsiFile file1 = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument());
if (file1 != null) {
ShowParameterInfoHandler.invoke(myProject, editor, file1, lbraceOffset, highlightedMethod, false, true);
ShowParameterInfoHandler.invoke(myProject, editor, file1, lbraceOffset, highlightedMethod, false,
true, CodeInsightBundle.message("auto.popup.progress.title"), e -> { });
}
}
catch (IndexNotReadyException ignored) { //anything can happen on alarm
@@ -3,6 +3,7 @@
package com.intellij.codeInsight.hint;
import com.intellij.codeInsight.AutoPopupController;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager;
import com.intellij.codeInsight.lookup.Lookup;
@@ -14,11 +15,15 @@ import com.intellij.lang.parameterInfo.*;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.command.undo.UndoManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.event.*;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
@@ -26,6 +31,7 @@ import com.intellij.openapi.ui.popup.Balloon.Position;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
@@ -50,6 +56,7 @@ import java.awt.*;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.LockSupport;
@@ -66,7 +73,7 @@ public class ParameterInfoController extends UserDataHolderBase implements Visib
private boolean myKeepOnHintHidden;
private final CaretListener myEditorCaretListener;
@NotNull private final ParameterInfoHandler<Object, Object> myHandler;
@NotNull private final ParameterInfoHandler<PsiElement, Object> myHandler;
private final MyBestLocationPointProvider myProvider;
private final ParameterInfoListener[] myListeners;
@@ -181,11 +188,11 @@ public class ParameterInfoController extends UserDataHolderBase implements Visib
LookupManager.getInstance(project).addPropertyChangeListener(lookupListener, this);
EditorUtil.disposeWithEditor(myEditor, this);
myComponent.update(mySingleParameterInfo); // to have correct preferred size
if (showHint) {
showHint(requestFocus, mySingleParameterInfo);
} else {
updateComponent();
}
updateComponent();
}
void setDescriptors(Object[] descriptors) {
@@ -309,44 +316,43 @@ public class ParameterInfoController extends UserDataHolderBase implements Visib
}
final PsiFile file = PsiUtilBase.getPsiFileInEditor(myEditor, myProject);
CharSequence chars = myEditor.getDocument().getCharsSequence();
int caretOffset = myEditor.getCaretModel().getOffset();
final int offset = myHandler.isWhitespaceSensitive() ? caretOffset :
CharArrayUtil.shiftBackward(chars, caretOffset - 1, WHITESPACE) + 1;
final int offset = getCurrentOffset();
final UpdateParameterInfoContext context = new MyUpdateParameterInfoContext(offset, file);
final Object elementForUpdating = myHandler.findElementForUpdatingParameterInfo(context);
final PsiElement elementForUpdating = myHandler.findElementForUpdatingParameterInfo(context);
if (elementForUpdating != null) {
myHandler.updateParameterInfo(elementForUpdating, context);
boolean knownParameter = (myComponent.getObjects().length == 1 || myComponent.getHighlighted() != null) &&
myComponent.getCurrentParameterIndex() != -1;
if (mySingleParameterInfo && !knownParameter && myHint.isVisible()) {
hideHint();
}
if (myKeepOnHintHidden && knownParameter && !myHint.isVisible()) {
AutoPopupController.getInstance(myProject).autoPopupParameterInfo(myEditor, null);
}
if (!myDisposed && (myHint.isVisible() && !myEditor.isDisposed() &&
(myEditor.getComponent().getRootPane() != null || ApplicationManager.getApplication().isUnitTestMode()) ||
ApplicationManager.getApplication().isHeadlessEnvironment())) {
Model result = myComponent.update(mySingleParameterInfo);
result.project = myProject;
result.range = myComponent.getParameterOwner().getTextRange();
result.editor = myEditor;
for (ParameterInfoListener listener : myListeners) {
listener.hintUpdated(result);
executeUpdateParameterInfo(elementForUpdating, context, () -> {
boolean knownParameter = (myComponent.getObjects().length == 1 || myComponent.getHighlighted() != null) &&
myComponent.getCurrentParameterIndex() != -1;
if (mySingleParameterInfo && !knownParameter && myHint.isVisible()) {
hideHint();
}
if (ApplicationManager.getApplication().isHeadlessEnvironment()) return;
IdeTooltip tooltip = myHint.getCurrentIdeTooltip();
short position = tooltip != null
? toShort(tooltip.getPreferredPosition())
: HintManager.ABOVE;
Pair<Point, Short> pos = myProvider.getBestPointPosition(
myHint, elementForUpdating instanceof PsiElement ? (PsiElement)elementForUpdating : null,
caretOffset, myEditor.getCaretModel().getVisualPosition(), position);
HintManagerImpl.adjustEditorHintPosition(myHint, myEditor, pos.getFirst(), pos.getSecond());
}
if (myKeepOnHintHidden && knownParameter && !myHint.isVisible()) {
AutoPopupController.getInstance(myProject).autoPopupParameterInfo(myEditor, null);
}
if (!myDisposed && (myHint.isVisible() && !myEditor.isDisposed() &&
(myEditor.getComponent().getRootPane() != null || ApplicationManager.getApplication().isUnitTestMode()) ||
ApplicationManager.getApplication().isHeadlessEnvironment())) {
Model result = myComponent.update(mySingleParameterInfo);
result.project = myProject;
result.range = myComponent.getParameterOwner().getTextRange();
result.editor = myEditor;
for (ParameterInfoListener listener : myListeners) {
listener.hintUpdated(result);
}
if (ApplicationManager.getApplication().isHeadlessEnvironment()) return;
IdeTooltip tooltip = myHint.getCurrentIdeTooltip();
short position = tooltip != null
? toShort(tooltip.getPreferredPosition())
: HintManager.ABOVE;
Pair<Point, Short> pos = myProvider.getBestPointPosition(
myHint, elementForUpdating instanceof PsiElement ? (PsiElement)elementForUpdating : null,
caretOffset, myEditor.getCaretModel().getVisualPosition(), position);
HintManagerImpl.adjustEditorHintPosition(myHint, myEditor, pos.getFirst(), pos.getSecond());
}
});
}
else {
hideHint();
@@ -356,6 +362,51 @@ public class ParameterInfoController extends UserDataHolderBase implements Visib
}
}
private int getCurrentOffset() {
int caretOffset = myEditor.getCaretModel().getOffset();
CharSequence chars = myEditor.getDocument().getCharsSequence();
return myHandler.isWhitespaceSensitive() ? caretOffset :
CharArrayUtil.shiftBackward(chars, caretOffset - 1, WHITESPACE) + 1;
}
private void executeUpdateParameterInfo(@NotNull PsiElement elementForUpdating, UpdateParameterInfoContext context, Runnable continuation) {
PsiElement parameterOwner = context.getParameterOwner();
if (parameterOwner != null && !parameterOwner.equals(elementForUpdating)) {
context.removeHint();
return;
}
final Component focusOwner = IdeFocusManager.getInstance(myProject).getFocusOwner();
ProgressManager.getInstance().run(
new Task.Backgroundable(myProject, CodeInsightBundle.message("parameter.info.progress.title"), true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
ReadAction
.nonBlocking(() -> {
try {
myHandler.updateParameterInfo(elementForUpdating, context);
}
catch (IndexNotReadyException e) {
DumbService.getInstance(myProject)
.showDumbModeNotification(CodeInsightBundle.message("parameter.info.indexing.mode.not.supported"));
}
})
.cancelWith(indicator)
.expireWhen(() -> getCurrentOffset() != context.getOffset() || !elementForUpdating.isValid())
.executeSynchronously();
if (continuation != null && !indicator.isCanceled()) {
ApplicationManager.getApplication().invokeLater(() -> {
if (!elementForUpdating.isValid() || getCurrentOffset() != context.getOffset() ||
!Objects.equals(focusOwner, IdeFocusManager.getInstance(myProject).getFocusOwner())) return;
continuation.run();
});
}
}
});
}
@HintManager.PositionFlags
private static short toShort(Position position) {
switch (position) {
@@ -395,7 +446,7 @@ public class ParameterInfoController extends UserDataHolderBase implements Visib
myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
myEditor.getSelectionModel().removeSelection();
if (argsList != null) {
myHandler.updateParameterInfo(argsList, new MyUpdateParameterInfoContext(offset, file));
executeUpdateParameterInfo(argsList, new MyUpdateParameterInfoContext(offset, file), null);
}
}
@@ -634,8 +685,12 @@ public class ParameterInfoController extends UserDataHolderBase implements Visib
@Override
public void removeHint() {
hideHint();
if (!myKeepOnHintHidden) Disposer.dispose(ParameterInfoController.this);
ApplicationManager.getApplication().invokeLater(() -> {
if (!myHint.isVisible()) return;
hideHint();
if (!myKeepOnHintHidden) Disposer.dispose(ParameterInfoController.this);
});
}
@Override
@@ -3,6 +3,7 @@
package com.intellij.codeInsight.hint;
import com.intellij.codeInsight.CodeInsightActionHandler;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.lookup.Lookup;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupManager;
@@ -10,23 +11,33 @@ import com.intellij.lang.Language;
import com.intellij.lang.parameterInfo.LanguageParameterInfo;
import com.intellij.lang.parameterInfo.ParameterInfoHandler;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.ui.LightweightHint;
import com.intellij.util.Consumer;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
public class ShowParameterInfoHandler implements CodeInsightActionHandler {
private static final ParameterInfoHandler[] EMPTY_HANDLERS = new ParameterInfoHandler[0];
private final boolean myRequestFocus;
public ShowParameterInfoHandler() {
@@ -57,11 +68,17 @@ public class ShowParameterInfoHandler implements CodeInsightActionHandler {
public static void invoke(final Project project, final Editor editor, PsiFile file,
int lbraceOffset, PsiElement highlightedElement, boolean requestFocus) {
invoke(project, editor, file, lbraceOffset, highlightedElement, requestFocus, false);
invoke(project, editor, file, lbraceOffset, highlightedElement, requestFocus, false,
CodeInsightBundle.message("parameter.info.progress.title"),
e -> DumbService.getInstance(project)
.showDumbModeNotification(CodeInsightBundle.message("parameter.info.indexing.mode.not.supported")));
}
public static void invoke(final Project project, final Editor editor, PsiFile file,
int lbraceOffset, PsiElement highlightedElement, boolean requestFocus, boolean singleParameterHint) {
int lbraceOffset, PsiElement highlightedElement,
boolean requestFocus, boolean singleParameterHint,
String progressTitle,
Consumer<IndexNotReadyException> indexNotReadyExceptionConsumer) {
ApplicationManager.getApplication().assertIsDispatchThread();
PsiDocumentManager.getInstance(project).commitAllDocuments();
@@ -85,8 +102,9 @@ public class ShowParameterInfoHandler implements CodeInsightActionHandler {
// file.findElementAt(file.getTextLength()) returns null but we may need to show parameter info at EOF offset (for example in SQL)
final int offsetForLangDetection = offset > 0 && offset == fileLength ? offset - 1 : offset;
final Language language = PsiUtilCore.getLanguageAtOffset(file, offsetForLangDetection);
ParameterInfoHandler[] handlers = getHandlers(project, language, file.getViewProvider().getBaseLanguage());
if (handlers == null) handlers = new ParameterInfoHandler[0];
final ParameterInfoHandler<PsiElement, Object>[] handlers =
ObjectUtils.notNull(getHandlers(project, language, file.getViewProvider().getBaseLanguage()), EMPTY_HANDLERS);
Lookup lookup = LookupManager.getInstance(project).getActiveLookup();
@@ -94,7 +112,7 @@ public class ShowParameterInfoHandler implements CodeInsightActionHandler {
LookupElement item = lookup.getCurrentItem();
if (item != null) {
for(ParameterInfoHandler handler:handlers) {
for (ParameterInfoHandler<PsiElement, Object> handler : handlers) {
if (handler.couldShowInLookup()) {
final Object[] items = handler.getParametersForLookup(item, context);
if (items != null && items.length > 0) {
@@ -107,19 +125,60 @@ public class ShowParameterInfoHandler implements CodeInsightActionHandler {
return;
}
DumbService.getInstance(project).setAlternativeResolveEnabled(true);
try {
for (ParameterInfoHandler<Object, ?> handler : handlers) {
Object element = handler.findElementForParameterInfo(context);
if (element != null) {
handler.showParameterInfo(element, context);
break;
final Component focusOwner = IdeFocusManager.getInstance(project).getFocusOwner();
ProgressManager.getInstance().run(
new Task.Backgroundable(project, progressTitle, true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
PsiElement element = null;
ParameterInfoHandler<PsiElement, Object> handler = null;
DumbService dumbService = DumbService.getInstance(project);
dumbService.setAlternativeResolveEnabled(true);
try {
for (int i = 0; i < handlers.length; i++) {
final ParameterInfoHandler<PsiElement, Object> h = handlers[i];
handler = h;
element = ReadAction
.nonBlocking(() -> {
try {
return h.findElementForParameterInfo(context);
}
catch (IndexNotReadyException e) {
indexNotReadyExceptionConsumer.consume(e);
return null;
}
})
.cancelWith(indicator)
.expireWhen(() -> editor.getCaretModel().getOffset() != offset)
.executeSynchronously();
if (element != null) {
break;
}
}
}
finally {
dumbService.setAlternativeResolveEnabled(false);
}
if (element != null && !indicator.isCanceled()) {
final PsiElement el = element;
final ParameterInfoHandler<PsiElement, Object> h = handler;
ApplicationManager.getApplication().invokeLater(() -> {
if (!el.isValid()) return;
if (editor.getCaretModel().getOffset() != context.getOffset() ||
!Objects.equals(focusOwner, IdeFocusManager.getInstance(project).getFocusOwner())) return;
h.showParameterInfo(el, context);
});
}
}
}
}
finally {
DumbService.getInstance(project).setAlternativeResolveEnabled(false);
}
);
}
private static void showLookupEditorHint(Object[] descriptors,
@@ -592,4 +592,9 @@ collapse.selection.overlapping.warning.cancel=Cancel
change.uid.action.name=Randomly change 'serialVersionUID' initializer
intention.convert.to.single.return.name=Transform body to single exit-point form
intention.convert.to.single.return.name=Transform body to single exit-point form
parameter.info.progress.title=Calculating parameter info...
parameter.info.indexing.mode.not.supported=Parameter Info is unavailable during indexing
auto.popup.progress.title=Calculating auto popup info...
@@ -185,12 +185,6 @@ public class GroovyParameterInfoHandler implements ParameterInfoHandlerWithTabAc
@Override
public void updateParameterInfo(@NotNull GroovyPsiElement place, @NotNull UpdateParameterInfoContext context) {
final PsiElement parameterOwner = context.getParameterOwner();
if (parameterOwner != place) {
context.removeHint();
return;
}
int offset = context.getEditor().getCaretModel().getOffset();
offset = CharArrayUtil.shiftForward(context.getEditor().getDocument().getText(), offset, " \t\n");
final int currIndex = getCurrentParameterIndex(place, offset);
@@ -101,11 +101,6 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
*/
@Override
public void updateParameterInfo(@NotNull PyArgumentList argumentList, @NotNull UpdateParameterInfoContext context) {
if (context.getParameterOwner() != argumentList) {
context.removeHint();
return;
}
// align offset to nearest expression; context may point to a space, etc.
final List<PyExpression> flattenedArguments = PyUtil.flattenedParensAndLists(argumentList.getArguments());
final int allegedCursorOffset = context.getOffset(); // this is already shifted backwards to skip spaces
@@ -92,11 +92,7 @@ public class XmlParameterInfoHandler implements ParameterInfoHandler<XmlTag,XmlE
@Override
public void updateParameterInfo(@NotNull final XmlTag parameterOwner, @NotNull final UpdateParameterInfoContext context) {
if (context.getParameterOwner() == null || parameterOwner.equals(context.getParameterOwner())) {
context.setParameterOwner(parameterOwner);
} else {
context.removeHint();
}
context.setParameterOwner(parameterOwner);
}
@Nullable