reorganize code showing parameter hints on completion

Both completion and 'static' hints are generated in one place now -
by hints provider.
This should also fix IDEA-173963.
This commit is contained in:
Dmitry Batrak
2017-07-17 11:22:16 +03:00
parent 9894ab5ed4
commit 2ff70734e1
14 changed files with 148 additions and 200 deletions
@@ -17,13 +17,13 @@ package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.AutoPopupController;
import com.intellij.codeInsight.completion.util.MethodParenthesesHandler;
import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager;
import com.intellij.codeInsight.hint.ParameterInfoController;
import com.intellij.codeInsight.hint.ShowParameterInfoContext;
import com.intellij.codeInsight.hint.api.impls.MethodParameterInfoHandler;
import com.intellij.codeInsight.hints.HintInfo;
import com.intellij.codeInsight.hints.JavaInlayParameterHintsProvider;
import com.intellij.codeInsight.hints.MethodInfoBlacklistFilter;
import com.intellij.codeInsight.hints.ParameterHintsPass;
import com.intellij.codeInsight.lookup.*;
import com.intellij.codeInsight.lookup.impl.JavaElementLookupRenderer;
import com.intellij.codeInsight.template.*;
@@ -32,10 +32,11 @@ import com.intellij.codeInsight.template.impl.TemplateImpl;
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
import com.intellij.codeInsight.template.impl.TemplateState;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.project.Project;
@@ -55,7 +56,6 @@ import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
@@ -65,7 +65,7 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
public class JavaMethodCallElement extends LookupItem<PsiMethod> implements TypedLookupItem, StaticallyImportable {
public static final ClassConditionKey<JavaMethodCallElement> CLASS_CONDITION_KEY = ClassConditionKey.create(JavaMethodCallElement.class);
public static final Key<List<Inlay>> COMPLETION_HINTS = Key.create("completion.hints");
public static final Key<Boolean> COMPLETION_HINTS = Key.create("completion.hints");
@Nullable private final PsiClass myContainingClass;
private final PsiMethod myMethod;
private final MemberLookupHelper myHelper;
@@ -314,19 +314,10 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
return;
}
boolean showHints = true;
if (parametersCount == 1) {
HintInfo.MethodInfo methodInfo = JavaInlayParameterHintsProvider.Companion.getInstance().getMethodInfo(method);
if (methodInfo != null) {
showHints = MethodInfoBlacklistFilter.forLanguage(JavaLanguage.INSTANCE).showHint(methodInfo);
}
}
Editor editor = context.getEditor();
CaretModel caretModel = editor.getCaretModel();
int offset = caretModel.getOffset();
caretModel.moveToOffset(offset - 1); // avoid caret impact on hints location
int braceOffset = caretModel.getOffset();
int braceOffset = offset - 1;
int numberOfCommas = parametersCount - 1;
if (parametersCount > 1 && PsiImplUtil.isVarArgs(method)) numberOfCommas--;
String commas = StringUtil.repeat(", ", numberOfCommas);
@@ -338,48 +329,24 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
ShowParameterInfoContext infoContext = new ShowParameterInfoContext(editor, project, context.getFile(), braceOffset, braceOffset);
if (handler.findElementForParameterInfo(infoContext) == null) {
editor.getDocument().deleteString(offset, offset + commas.length());
caretModel.moveToOffset(offset);
return;
}
List<Inlay> addedHints = new ArrayList<>(parametersCount);
if (showHints) {
for (PsiParameter parameter : parameterList.getParameters()) {
String name = parameter.getName();
if (name != null) {
if (parametersCount > 1 && parameter.isVarArgs()) {
name = ", " + name;
offset -= 2;
}
addedHints.add(ParameterHintsPresentationManager.getInstance().addHint(editor, offset, name + ":", false, true));
}
offset += 2;
}
}
VisualPosition afterBracePosition = editor.offsetToVisualPosition(braceOffset + 1);
caretModel.moveToVisualPosition(new VisualPosition(afterBracePosition.line,
afterBracePosition.column + (showHints ? 1 : 0))); // after hint
parameterOwner.putUserData(COMPLETION_HINTS, addedHints);
methodCall.putUserData(COMPLETION_HINTS, Boolean.TRUE);
ParameterInfoController controller = new ParameterInfoController(project, editor, braceOffset, infoContext.getItemsToShow(), null,
parameterOwner, handler, false, false);
Disposable hintsDisposal = () -> {
for (Inlay inlay : addedHints) {
if (inlay != null) ParameterHintsPresentationManager.getInstance().unpin(inlay);
}
addedHints.clear();
};
Disposable hintsDisposal = () -> methodCall.putUserData(COMPLETION_HINTS, null);
if (Disposer.isDisposed(controller)) {
Disposer.dispose(hintsDisposal);
}
else {
ParameterHintsPass.syncUpdate(methodCall, editor);
Disposer.register(controller, hintsDisposal);
}
}
public static boolean hasCompletionHints(@NotNull PsiCallExpression expression) {
PsiExpressionList argumentList = expression.getArgumentList();
return argumentList != null && !ContainerUtil.isEmpty(argumentList.getUserData(COMPLETION_HINTS));
public static boolean showCompletionHints(@NotNull PsiCallExpression expression) {
return expression.getUserData(COMPLETION_HINTS) != null;
}
private static void setupNonFilledArgumentRemoving(final Editor editor, final TemplateState templateState) {
@@ -17,16 +17,14 @@ package com.intellij.codeInsight.editorActions;
import com.intellij.codeInsight.completion.CompletionMemory;
import com.intellij.codeInsight.completion.JavaMethodCallElement;
import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager;
import com.intellij.codeInsight.hint.ParameterInfoController;
import com.intellij.codeInsight.hints.ParameterHintsPass;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import com.intellij.psi.infos.CandidateInfo;
@@ -34,7 +32,9 @@ import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class JavaMethodOverloadSwitchHandler extends EditorWriteActionHandler {
private static final Key<Map<String, String>> ENTERED_PARAMETERS = Key.create("entered.parameters");
@@ -117,39 +117,25 @@ public class JavaMethodOverloadSwitchHandler extends EditorWriteActionHandler {
caret.moveToOffset(lbraceOffset); // avoid caret impact on hints location
int offset = lbraceOffset + 1;
int endOffset = exprList.getTextRange().getEndOffset() - 1;
List<Inlay> oldInlays = editor.getInlayModel().getInlineElementsInRange(offset, endOffset);
for (Inlay inlay : oldInlays) {
Disposer.dispose(inlay);
}
editor.getDocument().deleteString(offset, endOffset);
int targetCaretPosition = -1;
List<Inlay> addedHints = new ArrayList<>(parametersCount);
for (int i = 0; i < parametersCount; i++) {
String key = getParameterKey(targetMethod, i);
String value = enteredParameters.getOrDefault(key, "");
if (value.isEmpty() && targetCaretPosition == -1) targetCaretPosition = offset;
if (i < parametersCount - 1) value += ", ";
editor.getDocument().insertString(offset, value);
String name = parameterList.getParameters()[i].getName();
if (name != null) {
addedHints.add(ParameterHintsPresentationManager.getInstance().addHint(editor, offset, name + ":", false, true));
}
offset += value.length();
}
if (targetCaretPosition == -1) targetCaretPosition = offset;
caret.moveToLogicalPosition(editor.offsetToLogicalPosition(targetCaretPosition).leanForward(true));
exprList.putUserData(JavaMethodCallElement.COMPLETION_HINTS, addedHints);
Disposer.register(controller, () -> {
for (Inlay hint : addedHints) {
if (hint != null) ParameterHintsPresentationManager.getInstance().unpin(hint);
}
addedHints.clear();
});
call.putUserData(JavaMethodCallElement.COMPLETION_HINTS, Boolean.TRUE);
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
CompletionMemory.registerChosenMethod(targetMethod, (PsiCall)call);
controller.resetHighlighted();
controller.updateComponent(); // update popup immediately (otherwise, it will be updated only after delay)
ParameterHintsPass.syncUpdate(call, editor);
}
private static String getParameterKey(PsiMethod method, int parameterIndex) {
@@ -15,18 +15,15 @@
*/
package com.intellij.codeInsight.editorActions;
import com.intellij.codeInsight.completion.JavaMethodCallElement;
import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager;
import com.intellij.codeInsight.hint.ParameterInfoController;
import com.intellij.codeInsight.hints.ParameterHintsPass;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.psi.*;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.util.containers.ContainerUtil;
@@ -35,7 +32,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
public class JavaVarArgNextParameterHandler extends EditorActionHandler {
private final EditorActionHandler myDelegate;
@@ -79,51 +75,18 @@ public class JavaVarArgNextParameterHandler extends EditorActionHandler {
PsiMethod currentMethod = (PsiMethod)((CandidateInfo)objects[currentIndex]).getElement();
if (currentMethod.isVarArgs()) {
int rParOffset = list.getTextRange().getEndOffset() - 1;
boolean lastParameterIsEmpty =
CharArrayUtil.containsOnlyWhiteSpaces(
text.subSequence(prev + 1, rParOffset));
boolean lastParameterIsEmpty = CharArrayUtil.containsOnlyWhiteSpaces(text.subSequence(prev + 1, rParOffset));
if (lastParameterIsEmpty) {
if (prevChar == ',') {
List<Inlay> inlays = editor.getInlayModel().getInlineElementsInRange(prev, rParOffset);
ParameterHintsPresentationManager presentationManager = ParameterHintsPresentationManager.getInstance();
String inlayText = null;
List<Inlay> hints = exprList.getUserData(JavaMethodCallElement.COMPLETION_HINTS);
for (Inlay inlay : inlays) {
if (presentationManager.isParameterHint(inlay)) {
inlayText = presentationManager.getHintText(inlay);
if (hints != null) hints.remove(inlay);
Disposer.dispose(inlay);
break;
}
}
WriteAction.run(() -> editor.getDocument().deleteString(prev, rParOffset));
if (inlayText != null) {
Inlay inlay = presentationManager.addHint(editor, prev, ", " + inlayText, false, true);
if (hints != null) hints.add(inlay);
}
}
}
else {
int wsStart = CharArrayUtil.shiftBackward(text, rParOffset - 1, " \t") + 1;
List<Inlay> inlays = editor.getInlayModel().getInlineElementsInRange(wsStart, rParOffset);
ParameterHintsPresentationManager presentationManager = ParameterHintsPresentationManager.getInstance();
String inlayText = null;
List<Inlay> hints = exprList.getUserData(JavaMethodCallElement.COMPLETION_HINTS);
for (Inlay inlay : inlays) {
if (presentationManager.isParameterHint(inlay) && presentationManager.getHintText(inlay).startsWith(", ")) {
inlayText = presentationManager.getHintText(inlay);
if (hints != null) hints.remove(inlay);
Disposer.dispose(inlay);
break;
}
}
WriteAction.run(() -> editor.getDocument().insertString(rParOffset, ", "));
if (inlayText != null) {
Inlay inlay = presentationManager.addHint(editor, rParOffset + 2, inlayText.substring(2), false, true);
if (hints != null) hints.add(inlay);
}
}
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
PsiElement call = list.getParent();
if (call != null) ParameterHintsPass.syncUpdate(call, editor);
}
}
}
@@ -15,18 +15,15 @@
*/
package com.intellij.codeInsight.editorActions;
import com.intellij.codeInsight.completion.JavaMethodCallElement;
import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager;
import com.intellij.codeInsight.hint.ParameterInfoController;
import com.intellij.codeInsight.hints.ParameterHintsPass;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.psi.*;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.util.containers.ContainerUtil;
@@ -35,7 +32,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
public class JavaVarArgPrevParameterHandler extends EditorActionHandler {
private final EditorActionHandler myDelegate;
@@ -73,48 +69,18 @@ public class JavaVarArgPrevParameterHandler extends EditorActionHandler {
if (currentIndex >= 0) {
PsiMethod currentMethod = (PsiMethod)((CandidateInfo)objects[currentIndex]).getElement();
if (currentMethod.isVarArgs()) {
boolean toReturn = false;
if (offset == rParOffset + 1) {
int wsStart = CharArrayUtil.shiftBackward(text, rParOffset - 1, " \t") + 1;
List<Inlay> inlays = editor.getInlayModel().getInlineElementsInRange(wsStart, rParOffset);
ParameterHintsPresentationManager presentationManager = ParameterHintsPresentationManager.getInstance();
String inlayText = null;
List<Inlay> hints = exprList.getUserData(JavaMethodCallElement.COMPLETION_HINTS);
for (Inlay inlay : inlays) {
if (presentationManager.isParameterHint(inlay) && presentationManager.getHintText(inlay).startsWith(", ")) {
inlayText = presentationManager.getHintText(inlay);
if (hints != null) hints.remove(inlay);
Disposer.dispose(inlay);
break;
}
}
WriteAction.run(() -> editor.getDocument().insertString(rParOffset, ", "));
if (inlayText != null) {
Inlay inlay = presentationManager.addHint(editor, rParOffset + 2, inlayText.substring(2), false, true);
if (hints != null) hints.add(inlay);
}
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
}
else {
List<Inlay> inlays = editor.getInlayModel().getInlineElementsInRange(prev, next);
ParameterHintsPresentationManager presentationManager = ParameterHintsPresentationManager.getInstance();
String inlayText = null;
List<Inlay> hints = exprList.getUserData(JavaMethodCallElement.COMPLETION_HINTS);
for (Inlay inlay : inlays) {
if (presentationManager.isParameterHint(inlay)) {
inlayText = presentationManager.getHintText(inlay);
if (hints != null) hints.remove(inlay);
Disposer.dispose(inlay);
break;
}
}
WriteAction.run(() -> editor.getDocument().deleteString(prev, next));
if (inlayText != null) {
Inlay inlay = presentationManager.addHint(editor, prev, ", " + inlayText, false, true);
if (hints != null) hints.add(inlay);
editor.getCaretModel().moveToVisualPosition(inlay.getVisualPosition());
}
return;
toReturn = true;
}
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
PsiElement call = exprList.getParent();
if (call != null) ParameterHintsPass.syncUpdate(call, editor);
if (toReturn) return;
}
}
}
@@ -23,12 +23,10 @@ import com.intellij.codeInsight.completion.CompletionMemory;
import com.intellij.codeInsight.completion.JavaCompletionUtil;
import com.intellij.codeInsight.completion.JavaMethodCallElement;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager;
import com.intellij.codeInsight.javadoc.JavaDocInfoGenerator;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.lang.parameterInfo.*;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbService;
import com.intellij.psi.*;
@@ -137,24 +135,18 @@ public class MethodParameterInfoHandler implements ParameterInfoHandlerWithTabAc
if (!(element instanceof PsiMethod)) return null;
PsiMethod method = (PsiMethod)element;
PsiElement parent = expressionList.getParent();
int currentNumberOfParameters = expressionList.getExpressions().length;
PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(context.getProject());
Document document = psiDocumentManager.getCachedDocument(context.getFile());
if ((context.getHighlightedParameter() != null || candidates.length == 1) &&
if ((context.getHighlightedParameter() != null || candidates.length == 1) && parent != null &&
document != null && psiDocumentManager.isCommitted(document) &&
isIncompatibleParameterCount(method, currentNumberOfParameters)) {
List<Inlay> hints = expressionList.getUserData(JavaMethodCallElement.COMPLETION_HINTS);
if (hints != null) {
for (Inlay hint : hints) {
if (hint != null) ParameterHintsPresentationManager.getInstance().unpin(hint);
}
hints.clear();
}
parent.putUserData(JavaMethodCallElement.COMPLETION_HINTS, null);
}
String originalMethodName = method.getName();
PsiQualifiedReference currentMethodReference = null;
PsiElement parent = expressionList.getParent();
if (parent instanceof PsiMethodCallExpression && !method.isConstructor()) {
currentMethodReference = ((PsiMethodCallExpression)parent).getMethodExpression();
}
@@ -27,7 +27,23 @@ import com.intellij.psi.util.TypeConversionUtil
object JavaInlayHintsProvider {
fun hints(callExpression: PsiCallExpression): Set<InlayInfo> {
if (JavaMethodCallElement.hasCompletionHints(callExpression)) return emptySet()
if (JavaMethodCallElement.showCompletionHints(callExpression)) {
val method = CompletionMemory.getChosenMethod(callExpression)?:return emptySet()
val params = method.parameterList.parameters
val arguments = callExpression.argumentList?.expressions ?: emptyArray()
return params.mapIndexedNotNull { i, parameter ->
val paramName = parameter.name ?: return@mapIndexedNotNull null
val varargHint = parameter.type is PsiEllipsisType && params.size > 1 &&
(arguments.size == params.size - 1 || params.size == 2 && arguments.isEmpty())
val paramToShow = (if (varargHint) ", " else "") + paramName
val offset = if (i < arguments.size) inlayOffset(arguments[i])
else if (varargHint && i <= arguments.size) inlayOffset(arguments[i - 1], true)
else (callExpression.argumentList?.textOffset?:return@mapIndexedNotNull null) + 1
InlayInfo(paramToShow, offset, false, params.size == 1, varargHint)
}.toSet()
}
val resolveResult = callExpression.resolveMethodGenerics()
val hints = methodHints(callExpression, resolveResult)
@@ -163,12 +179,14 @@ private fun inlayInfo(callArgument: PsiExpression, methodParam: PsiParameter, sh
return InlayInfo(paramToShow, offset, showOnlyIfExistedBefore)
}
fun inlayOffset(callArgument: PsiExpression): Int {
fun inlayOffset(callArgument: PsiExpression): Int = inlayOffset(callArgument, false)
fun inlayOffset(callArgument: PsiExpression, atEnd: Boolean): Int {
if (callArgument.textRange.isEmpty) {
val next = callArgument.nextSibling as? PsiWhiteSpace
if (next != null) return next.textRange.endOffset
}
return callArgument.textRange.startOffset
return if (atEnd) callArgument.textRange.endOffset else callArgument.textRange.startOffset
}
private fun isUnclearExpression(callArgument: PsiElement): Boolean {
@@ -15,6 +15,7 @@
*/
package com.intellij.codeInsight.hints
import com.intellij.codeInsight.completion.CompletionMemory
import com.intellij.codeInsight.hints.HintInfo.MethodInfo
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.util.text.StringUtil
@@ -30,7 +31,7 @@ class JavaInlayParameterHintsProvider : InlayParameterHintsProvider {
override fun getHintInfo(element: PsiElement): MethodInfo? {
if (element is PsiCallExpression) {
val resolvedElement = element.resolveMethodGenerics().element
val resolvedElement = CompletionMemory.getChosenMethod(element) ?: element.resolveMethodGenerics ().element
if (resolvedElement is PsiMethod) {
return getMethodInfo(resolvedElement)
}
@@ -282,7 +282,7 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase {
right();
type("2");
waitForAllAsyncStuff();
checkResultWithInlays("class C { void m() { Character.forDigit(<hint text=\"digit:\"/>1,2 <hint text=\"radix:\"/>) } }");
checkResultWithInlays("class C { void m() { Character.forDigit(<hint text=\"digit:\"/>1,<hint text=\"radix:\"/>2 ) } }");
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_MOVE_LINE_END);
waitForAllAsyncStuff();
checkResultWithInlays("class C { void m() { Character.forDigit(<hint text=\"digit:\"/>1,<hint text=\"radix:\"/>2 ) } }");
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,10 +26,11 @@ object InlayParameterHintsExtension : LanguageExtension<InlayParameterHintsProvi
class InlayInfo(val text: String,
val offset: Int,
val isShowOnlyIfExistedBefore: Boolean,
val isFilterByBlacklist: Boolean) {
val isFilterByBlacklist: Boolean,
val showAfterCaret: Boolean) {
constructor(text: String, offset: Int, isShowOnlyIfExistedBefore: Boolean) : this(text, offset, isShowOnlyIfExistedBefore, true)
constructor(text: String, offset: Int) : this(text, offset, false, true)
constructor(text: String, offset: Int, isShowOnlyIfExistedBefore: Boolean) : this(text, offset, isShowOnlyIfExistedBefore, true, false)
constructor(text: String, offset: Int) : this(text, offset, false, true, false)
override fun equals(other: Any?): Boolean {
if (this === other) return true
@@ -53,7 +53,7 @@ public class InlineElementData extends HighlightData {
@Override
public void addHighlToView(Editor view, EditorColorsScheme scheme, Map<TextAttributesKey, String> displayText) {
int offset = getStartOffset();
ParameterHintsPresentationManager.getInstance().addHint(view, offset, myText, false, false);
ParameterHintsPresentationManager.getInstance().addHint(view, offset, myText, false);
List<Inlay> inlays = view.getInlayModel().getInlineElementsInRange(offset, offset);
for (Inlay inlay : inlays) {
EditorCustomElementRenderer renderer = inlay.getRenderer();
@@ -43,7 +43,6 @@ import java.util.Iterator;
import java.util.Set;
public class ParameterHintsPresentationManager implements Disposable {
private static final Key<Boolean> PINNED = Key.create("parameter.hint.pinned");
private static final Key<MyFontMetrics> HINT_FONT_METRICS = Key.create("ParameterHintFontMetrics");
private static final Key<AnimationStep> ANIMATION_STEP = Key.create("ParameterHintAnimationStep");
@@ -64,24 +63,15 @@ public class ParameterHintsPresentationManager implements Disposable {
return inlay.getRenderer() instanceof MyRenderer;
}
public boolean isPinned(@NotNull Inlay inlay) {
return Boolean.TRUE.equals(inlay.getUserData(PINNED));
}
public void unpin(@NotNull Inlay inlay) {
inlay.putUserData(PINNED, null);
}
public String getHintText(@NotNull Inlay inlay) {
EditorCustomElementRenderer renderer = inlay.getRenderer();
return renderer instanceof MyRenderer ? ((MyRenderer)renderer).getText() : null;
}
public Inlay addHint(@NotNull Editor editor, int offset, @NotNull String hintText, boolean useAnimation, boolean pinned) {
public Inlay addHint(@NotNull Editor editor, int offset, @NotNull String hintText, boolean useAnimation) {
MyRenderer renderer = new MyRenderer(editor, hintText, useAnimation);
Inlay inlay = editor.getInlayModel().addInlineElement(offset, renderer);
if (inlay != null) {
if (pinned) inlay.putUserData(PINNED, Boolean.TRUE);
if (useAnimation) scheduleRendererUpdate(editor, inlay);
}
return inlay;
@@ -23,6 +23,7 @@ import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
import com.intellij.openapi.editor.ex.util.CaretVisualPositionKeeper;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.SyntaxTraverser;
@@ -30,25 +31,36 @@ import com.intellij.util.containers.ContainerUtil;
import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import static com.intellij.codeInsight.hints.ParameterHintsPassFactory.putCurrentPsiModificationStamp;
public class ParameterHintsPass extends EditorBoundHighlightingPass {
private final TIntObjectHashMap<String> myHints = new TIntObjectHashMap<>();
private final TIntObjectHashMap<List<HintData>> myHints = new TIntObjectHashMap<>();
private final TIntObjectHashMap<String> myShowOnlyIfExistedBeforeHints = new TIntObjectHashMap<>();
private final SyntaxTraverser<PsiElement> myTraverser;
private final PsiElement myRootElement;
private final HintInfoFilter myHintInfoFilter;
private final boolean myForceImmediateUpdate;
public static void syncUpdate(@NotNull PsiElement element, @NotNull Editor editor) {
MethodInfoBlacklistFilter filter = MethodInfoBlacklistFilter.forLanguage(element.getLanguage());
ParameterHintsPass pass = new ParameterHintsPass(element, editor, filter, true);
pass.doCollectInformation(new ProgressIndicatorBase());
pass.applyInformationToEditor();
}
public ParameterHintsPass(@NotNull PsiElement element,
@NotNull Editor editor,
@NotNull HintInfoFilter hintsFilter) {
@NotNull HintInfoFilter hintsFilter,
boolean forceImmediateUpdate) {
super(editor, element.getContainingFile(), true);
myRootElement = element;
myTraverser = SyntaxTraverser.psiTraverser(element);
myHintInfoFilter = hintsFilter;
myForceImmediateUpdate = forceImmediateUpdate;
}
@Override
@@ -81,13 +93,17 @@ public class ParameterHintsPass extends EditorBoundHighlightingPass {
}
inlays.forEach((hint) -> {
String presentation = provider.getInlayPresentation(hint.getText());
int offset = hint.getOffset();
TextRange rootRange = myRootElement.getTextRange();
if (offset <= rootRange.getStartOffset() || offset >= rootRange.getEndOffset()) return;
String presentation = provider.getInlayPresentation(hint.getText());
if (hint.isShowOnlyIfExistedBefore()) {
myShowOnlyIfExistedBeforeHints.put(offset, presentation);
}
else {
myHints.put(offset, presentation);
List<HintData> hintList = myHints.get(offset);
if (hintList == null) myHints.put(offset, hintList = new ArrayList<>());
hintList.add(new HintData(presentation, hint.getShowAfterCaret()));
}
});
}
@@ -97,7 +113,7 @@ public class ParameterHintsPass extends EditorBoundHighlightingPass {
CaretVisualPositionKeeper keeper = new CaretVisualPositionKeeper(myEditor);
ParameterHintsPresentationManager manager = ParameterHintsPresentationManager.getInstance();
List<Inlay> hints = hintsInRootElementArea(manager);
ParameterHintsUpdater updater = new ParameterHintsUpdater(myEditor, hints, myHints, myShowOnlyIfExistedBeforeHints);
ParameterHintsUpdater updater = new ParameterHintsUpdater(myEditor, hints, myHints, myShowOnlyIfExistedBeforeHints, myForceImmediateUpdate);
updater.update();
keeper.restoreOriginalLocation(false);
putCurrentPsiModificationStamp(myEditor, myFile);
@@ -112,8 +128,18 @@ public class ParameterHintsPass extends EditorBoundHighlightingPass {
int elementEnd = range.getEndOffset();
List<Inlay> inlays = myEditor.getInlayModel()
.getInlineElementsInRange(elementStart, elementEnd);
.getInlineElementsInRange(elementStart + 1, elementEnd - 1);
return ContainerUtil.filter(inlays, (hint) -> manager.isParameterHint(hint));
}
public static class HintData {
public final String presentationText;
public final boolean showAfterCaret;
public HintData(String text, boolean afterCaret) {
presentationText = text;
showAfterCaret = afterCaret;
}
}
}
@@ -46,7 +46,7 @@ public class ParameterHintsPassFactory extends AbstractProjectComponent implemen
Language language = file.getLanguage();
InlayParameterHintsProvider provider = InlayParameterHintsExtension.INSTANCE.forLanguage(language);
if (provider == null) return null;
return new ParameterHintsPass(file, editor, MethodInfoBlacklistFilter.forLanguage(language));
return new ParameterHintsPass(file, editor, MethodInfoBlacklistFilter.forLanguage(language), false);
}
public static long getCurrentModificationStamp(@NotNull PsiFile file) {
@@ -27,10 +27,7 @@ import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.stream.Stream;
public class ParameterHintsUpdater {
@@ -40,19 +37,22 @@ public class ParameterHintsUpdater {
private final ParameterHintsPresentationManager myHintsManager = ParameterHintsPresentationManager.getInstance();
private final TIntObjectHashMap<Caret> myCaretMap;
private final TIntObjectHashMap<String> myNewHints;
private final TIntObjectHashMap<List<ParameterHintsPass.HintData>> myNewHints;
private final TIntObjectHashMap<String> myHintsToPreserve;
private final boolean myForceImmediateUpdate;
private final Editor myEditor;
private final List<InlayUpdateInfo> myUpdateList;
public ParameterHintsUpdater(@NotNull Editor editor,
@NotNull List<Inlay> inlays,
@NotNull TIntObjectHashMap<String> newHints,
@NotNull TIntObjectHashMap<String> hintsToPreserve) {
@NotNull TIntObjectHashMap<List<ParameterHintsPass.HintData>> newHints,
@NotNull TIntObjectHashMap<String> hintsToPreserve,
boolean forceImmediateUpdate) {
myEditor = editor;
myNewHints = newHints;
myHintsToPreserve = hintsToPreserve;
myForceImmediateUpdate = forceImmediateUpdate;
myCaretMap = new TIntObjectHashMap<>();
List<Caret> allCarets = myEditor.getCaretModel().getAllCarets();
@@ -64,20 +64,50 @@ public class ParameterHintsUpdater {
private List<InlayUpdateInfo> getInlayUpdates(List<Inlay> editorHints) {
List<InlayUpdateInfo> updates = ContainerUtil.newArrayList();
ParameterHintsPresentationManager presentationManager = ParameterHintsPresentationManager.getInstance();
editorHints.forEach(editorHint -> {
int offset = editorHint.getOffset();
String newText = myNewHints.remove(offset);
if (delayRemoval(editorHint) || myHintsManager.isPinned(editorHint) || isPreserveHint(editorHint, newText)) return;
updates.add(new InlayUpdateInfo(offset, editorHint, newText));
String presentationText = presentationManager.getHintText(editorHint);
ParameterHintsPass.HintData newHint = findAndRemoveMatchingHint(offset, presentationText, myNewHints);
String newText = newHint == null ? null : newHint.presentationText;
if (!myForceImmediateUpdate && delayRemoval(editorHint) || isPreserveHint(editorHint, newText)) return;
updates.add(new InlayUpdateInfo(offset, editorHint, newText, newHint != null && newHint.showAfterCaret));
});
Arrays.stream(myNewHints.keys()).forEach((offset) -> updates.add(new InlayUpdateInfo(offset, null, myNewHints.get(offset))));
Arrays.stream(myNewHints.keys()).forEach((offset) -> {
for (ParameterHintsPass.HintData hint : myNewHints.get(offset)) {
updates.add(new InlayUpdateInfo(offset, null, hint.presentationText, hint.showAfterCaret));
}
});
updates.sort(Comparator.comparing((update) -> update.offset));
return updates;
}
@Nullable
private static ParameterHintsPass.HintData findAndRemoveMatchingHint(int offset, String presentationText,
TIntObjectHashMap<List<ParameterHintsPass.HintData>> data) {
List<ParameterHintsPass.HintData> newHintList = data.get(offset);
ParameterHintsPass.HintData newHint = null;
if (newHintList != null) {
ParameterHintsPass.HintData lastHint = null;
for (Iterator<ParameterHintsPass.HintData> iterator = newHintList.iterator(); iterator.hasNext(); ) {
lastHint = iterator.next();
if (Objects.equals(lastHint.presentationText, presentationText)) {
newHint = lastHint;
iterator.remove();
break;
}
}
if (newHint == null && lastHint != null) {
newHint = lastHint;
newHintList.remove(lastHint);
}
if (newHintList.isEmpty()) data.remove(offset);
}
return newHint;
}
private boolean isPreserveHint(@NotNull Inlay inlay, @Nullable String newText) {
if (newText == null) {
@@ -103,11 +133,17 @@ public class ParameterHintsUpdater {
InlayUpdateInfo.Action action = info.action();
if (action == InlayUpdateInfo.Action.ADD) {
boolean useAnimation = !firstTime && !isSameHintRemovedNear(newText, infoIndex) && !isInBulkMode;
myHintsManager.addHint(myEditor, info.offset, newText, useAnimation, false);
boolean useAnimation = !myForceImmediateUpdate && !firstTime && !isSameHintRemovedNear(newText, infoIndex) && !isInBulkMode;
Inlay inlay = myHintsManager.addHint(myEditor, info.offset, newText, useAnimation);
if (inlay != null) {
VisualPosition inlayPosition = inlay.getVisualPosition();
VisualPosition visualPosition = new VisualPosition(inlayPosition.line, inlayPosition.column + (info.showAfterCaret ? 1 : 0));
Caret caret = myEditor.getCaretModel().getCaretAt(visualPosition);
if (caret != null) caret.moveToVisualPosition(new VisualPosition(inlayPosition.line, inlayPosition.column + (info.showAfterCaret ? 0 : 1)));
}
}
else if (action == InlayUpdateInfo.Action.DELETE) {
boolean useAnimation = oldText != null && !isSameHintAddedNear(oldText, infoIndex) && !isInBulkMode;
boolean useAnimation = !myForceImmediateUpdate && oldText != null && !isSameHintAddedNear(oldText, infoIndex) && !isInBulkMode;
myHintsManager.deleteHint(myEditor, info.inlay, useAnimation);
}
else if (action == InlayUpdateInfo.Action.REPLACE) {
@@ -162,12 +198,14 @@ public class ParameterHintsUpdater {
public final Inlay inlay;
public final String newText;
public final String oldText;
public final boolean showAfterCaret;
public InlayUpdateInfo(int offset, @Nullable Inlay current, @Nullable String newText) {
public InlayUpdateInfo(int offset, @Nullable Inlay current, @Nullable String newText, boolean showAfterCaret) {
this.offset = offset;
this.inlay = current;
this.newText = newText;
this.oldText = getHintText();
this.showAfterCaret = showAfterCaret;
}
public Action action() {