mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-10-01 21:01:48 +07:00
Dataflow hint display initial implementation (IDEA-188211)
This commit is contained in:
@@ -61,6 +61,18 @@ public class CommonDataflow {
|
||||
DfaFactMap map = this.myFacts.get(expression);
|
||||
return map == null ? null : map.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fact map which represents all the facts known for given expression
|
||||
*
|
||||
* @param expression an expression to check
|
||||
* @return the fact map which represents all the facts known for given expression; empty map if the expression was
|
||||
* analyzed, but no particular facts were inferred; null if the expression was not analyzed.
|
||||
*/
|
||||
@Nullable
|
||||
public DfaFactMap getAllFacts(PsiExpression expression) {
|
||||
return this.myFacts.get(expression);
|
||||
}
|
||||
}
|
||||
|
||||
@Contract("null -> null")
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An immutable collection of facts which are known for some value. Each fact is identified by {@link DfaFactType} and fact value.
|
||||
@@ -169,9 +168,16 @@ public final class DfaFactMap {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public String toString() {
|
||||
return StreamEx.of(myMap.getKeys())
|
||||
.map(key -> ((DfaFactType<Object>)key).toString(Objects.requireNonNull(myMap.get(key))))
|
||||
.joining(", ");
|
||||
return facts(DfaFactType::toString).joining(", ");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <R> StreamEx<R> facts(FactMapper<R> mapper) {
|
||||
return StreamEx.of(myMap.getKeys()).map(f -> {
|
||||
DfaFactType<Object> key = (DfaFactType<Object>)f;
|
||||
Object value = myMap.get(f);
|
||||
return mapper.apply(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,4 +194,9 @@ public final class DfaFactMap {
|
||||
private static <T> DfaFactMap updateMap(DfaFactMap map, DfaFactType<T> factType, DfaVariableValue value) {
|
||||
return map.with(factType, factType.calcFromVariable(value));
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface FactMapper<R> {
|
||||
<T> R apply(DfaFactType<T> factType, T factValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
|
||||
import com.intellij.codeInspection.dataFlow.value.*;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.psi.PsiModifierListOwner;
|
||||
import com.intellij.psi.PsiPrimitiveType;
|
||||
import com.intellij.psi.PsiType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -38,12 +40,20 @@ public abstract class DfaFactType<T> extends Key<T> {
|
||||
/**
|
||||
* This fact specifies whether the value can be null. The absence of the fact means that the nullability is unknown.
|
||||
*/
|
||||
public static final DfaFactType<Boolean> CAN_BE_NULL = new DfaFactType<Boolean>("Can be null") {
|
||||
public static final DfaFactType<Boolean> CAN_BE_NULL = new DfaFactType<Boolean>("Nullability") {
|
||||
@NotNull
|
||||
@Override
|
||||
String toString(@NotNull Boolean fact) {
|
||||
public String toString(@NotNull Boolean fact) {
|
||||
return fact ? "Nullable" : "NotNull";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getPresentationText(Boolean fact, PsiType type) {
|
||||
if (type instanceof PsiPrimitiveType) return "";
|
||||
return super.getPresentationText(fact, type);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
Boolean intersectFacts(@NotNull Boolean left, @NotNull Boolean right) {
|
||||
@@ -95,8 +105,9 @@ public abstract class DfaFactType<T> extends Key<T> {
|
||||
*/
|
||||
public static final DfaFactType<Boolean> OPTIONAL_PRESENCE = new DfaFactType<Boolean>("Optional presense") {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
String toString(@NotNull Boolean fact) {
|
||||
public String toString(@NotNull Boolean fact) {
|
||||
return fact ? "present Optional" : "absent Optional";
|
||||
}
|
||||
};
|
||||
@@ -152,12 +163,20 @@ public abstract class DfaFactType<T> extends Key<T> {
|
||||
LongRangeSet intersection = left.intersect(right);
|
||||
return intersection.isEmpty() ? null : intersection;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getPresentationText(LongRangeSet fact, PsiType type) {
|
||||
LongRangeSet fromType = LongRangeSet.fromType(type);
|
||||
if(fact.equals(fromType)) return "";
|
||||
return fact.toString();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* This fact represents a set of possible types of this value
|
||||
* {@link TypeConstraint#EMPTY} value is equivalent to absent fact (not constrained)
|
||||
*/
|
||||
public static final DfaFactType<TypeConstraint> TYPE_CONSTRAINT = new DfaFactType<TypeConstraint>("Type") {
|
||||
public static final DfaFactType<TypeConstraint> TYPE_CONSTRAINT = new DfaFactType<TypeConstraint>("Type constraints") {
|
||||
@Override
|
||||
boolean isSuper(@Nullable TypeConstraint superFact, @Nullable TypeConstraint subFact) {
|
||||
return superFact == null || (subFact != null && superFact.isSuperStateOf(subFact));
|
||||
@@ -187,14 +206,27 @@ public abstract class DfaFactType<T> extends Key<T> {
|
||||
TypeConstraint unionFacts(@NotNull TypeConstraint left, @NotNull TypeConstraint right) {
|
||||
return left.union(right);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getPresentationText(TypeConstraint fact, PsiType type) {
|
||||
return fact.getPresentationText(type);
|
||||
}
|
||||
};
|
||||
|
||||
private final String myName;
|
||||
|
||||
private DfaFactType(String name) {
|
||||
super("DfaFactType: " + name);
|
||||
myName = name;
|
||||
// Thread-safe as all DfaFactType instances are created only from DfaFactType class static initializer
|
||||
ourFactTypes.add(this);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
T fromDfaValue(DfaValue value) {
|
||||
return value instanceof DfaFactMapValue ? ((DfaFactMapValue)value).get(this) : null;
|
||||
@@ -238,10 +270,28 @@ public abstract class DfaFactType<T> extends Key<T> {
|
||||
return left.equals(right) ? left : null;
|
||||
}
|
||||
|
||||
String toString(@NotNull T fact) {
|
||||
/**
|
||||
* Produces a short suitable for debug output fact representation
|
||||
* @param fact a fact to represent
|
||||
* @return a string representation of the fact
|
||||
*/
|
||||
@NotNull
|
||||
public String toString(@NotNull T fact) {
|
||||
return fact.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a user-friendly presentation of the fact based on the fact itself and the type of the expression
|
||||
* @param fact a fact to represent
|
||||
* @param type an expression type
|
||||
* @return a user-friendly string representation of the fact; empty string if the fact adds nothing to the expression type
|
||||
* (e.g. fact is Range {0..65535} and type is 'char').
|
||||
*/
|
||||
@NotNull
|
||||
public String getPresentationText(T fact, PsiType type) {
|
||||
return toString(fact);
|
||||
}
|
||||
|
||||
static List<DfaFactType<?>> getTypes() {
|
||||
return Collections.unmodifiableList(ourFactTypes);
|
||||
}
|
||||
|
||||
+19
-7
@@ -16,10 +16,7 @@
|
||||
package com.intellij.codeInspection.dataFlow;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaPsiType;
|
||||
import com.intellij.psi.LambdaUtil;
|
||||
import com.intellij.psi.PsiIntersectionType;
|
||||
import com.intellij.psi.PsiPrimitiveType;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import one.util.streamex.EntryStream;
|
||||
import one.util.streamex.StreamEx;
|
||||
@@ -48,6 +45,21 @@ public final class TypeConstraint {
|
||||
myNotInstanceofValues = notInstanceofValues;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getPresentationText(PsiType type) {
|
||||
Set<DfaPsiType> instanceOfTypes = myInstanceofValues;
|
||||
if (type instanceof PsiClassType) {
|
||||
instanceOfTypes = StreamEx.of(instanceOfTypes)
|
||||
.removeBy(DfaPsiType::getPsiType, ((PsiClassType)type).rawType())
|
||||
.toSet();
|
||||
}
|
||||
return EntryStream.of("instanceof ", instanceOfTypes,
|
||||
"not instanceof ", myNotInstanceofValues)
|
||||
.removeValues(Set::isEmpty)
|
||||
.mapKeyValue((prefix, set) -> StreamEx.of(set).map(DfaPsiType::toString).sorted().joining(", ", prefix, ""))
|
||||
.joining("\n");
|
||||
}
|
||||
|
||||
private static TypeConstraint create(@NotNull Set<DfaPsiType> instanceofValues, @NotNull Set<DfaPsiType> notInstanceofValues) {
|
||||
if (instanceofValues.isEmpty() && notInstanceofValues.isEmpty()) {
|
||||
return EMPTY;
|
||||
@@ -222,9 +234,9 @@ public final class TypeConstraint {
|
||||
public String toString() {
|
||||
return EntryStream.of("instanceof ", myInstanceofValues,
|
||||
"not instanceof ", myNotInstanceofValues)
|
||||
.removeValues(Set::isEmpty)
|
||||
.mapKeyValue((prefix, set) -> StreamEx.of(set).joining(",", prefix, ""))
|
||||
.joining(" ");
|
||||
.removeValues(Set::isEmpty)
|
||||
.mapKeyValue((prefix, set) -> StreamEx.of(set).joining(", ", prefix, ""))
|
||||
.joining(" ");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+12
-3
@@ -289,7 +289,7 @@ public abstract class LongRangeSet {
|
||||
*/
|
||||
private static ThreeState[] bits(long from, long to) {
|
||||
ThreeState[] bits = new ThreeState[Long.SIZE];
|
||||
Arrays.setAll(bits, idx -> ThreeState.NO);
|
||||
Arrays.fill(bits, ThreeState.NO);
|
||||
while (true) {
|
||||
int fromBit = Long.numberOfLeadingZeros(from);
|
||||
int toBit = Long.numberOfLeadingZeros(to);
|
||||
@@ -308,6 +308,15 @@ public abstract class LongRangeSet {
|
||||
return bits;
|
||||
}
|
||||
|
||||
private static String formatNumber(long value) {
|
||||
if (value == Long.MAX_VALUE) return "Long.MAX_VALUE";
|
||||
if (value == Long.MAX_VALUE - 1) return "Long.MAX_VALUE-1";
|
||||
if (value == Long.MIN_VALUE) return "Long.MIN_VALUE";
|
||||
if (value == Integer.MAX_VALUE) return "Integer.MAX_VALUE";
|
||||
if (value == Integer.MAX_VALUE - 1) return "Integer.MAX_VALUE-1";
|
||||
if (value == Integer.MIN_VALUE) return "Integer.MIN_VALUE";
|
||||
return String.valueOf(value);
|
||||
}
|
||||
/**
|
||||
* Returns a stream of all values from this range. Be careful: could be huge
|
||||
*
|
||||
@@ -384,7 +393,7 @@ public abstract class LongRangeSet {
|
||||
abstract long[] asRanges();
|
||||
|
||||
static String toString(long from, long to) {
|
||||
return from == to ? String.valueOf(from) : from + (to - from == 1 ? ", " : "..") + to;
|
||||
return formatNumber(from) + (from == to ? "" : (to - from == 1 ? ", " : "..") + formatNumber(to));
|
||||
}
|
||||
|
||||
static long minValue(boolean isLong) {
|
||||
@@ -673,7 +682,7 @@ public abstract class LongRangeSet {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" + myValue + "}";
|
||||
return "{" + formatNumber(myValue) + "}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,13 @@
|
||||
*/
|
||||
package com.intellij.codeInsight.hint;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.CommonDataflow;
|
||||
import com.intellij.codeInspection.dataFlow.DfaFactMap;
|
||||
import com.intellij.codeInspection.dataFlow.DfaFactType;
|
||||
import com.intellij.lang.ExpressionTypeProvider;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
@@ -57,4 +61,38 @@ public class JavaTypeProvider extends ExpressionTypeProvider<PsiExpression> {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAdvancedInformation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getAdvancedInformationHint(@NotNull PsiExpression expression) {
|
||||
expression = PsiUtil.skipParenthesizedExprDown(expression);
|
||||
if (expression == null) return "<unknown>";
|
||||
CommonDataflow.DataflowResult result = CommonDataflow.getDataflowResult(expression);
|
||||
DfaFactMap map = result == null ? null : result.getAllFacts(expression);
|
||||
String basicTypeEscaped = getInformationHint(expression);
|
||||
PsiType type = expression.getType();
|
||||
String advancedTypeInfo = map == null ? "" : map.facts(new DfaFactMap.FactMapper<String>() {
|
||||
@Override
|
||||
public <T> String apply(DfaFactType<T> factType, T value) {
|
||||
return formatFact(factType, value, type);
|
||||
}
|
||||
}).joining();
|
||||
return advancedTypeInfo.isEmpty()
|
||||
? basicTypeEscaped
|
||||
: "<table>" + makeHtmlRow("Type", basicTypeEscaped) + advancedTypeInfo + "</table>";
|
||||
}
|
||||
|
||||
private static <T> String formatFact(DfaFactType<T> factType, T value, PsiType type) {
|
||||
String presentationText = factType.getPresentationText(value, type);
|
||||
return presentationText.isEmpty() ? "" : makeHtmlRow(factType.getName(), StringUtil.escapeXml(presentationText));
|
||||
}
|
||||
|
||||
private static String makeHtmlRow(String titleText, String contentHtml) {
|
||||
return "<tr><td align='right' valign='top'><strong>" + StringUtil.escapeXml(titleText) + ":</strong></td><td>" + contentHtml + "</td></tr>";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeInsight.hint;
|
||||
|
||||
import com.intellij.ide.highlighter.JavaFileType;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.testFramework.LightCodeInsightTestCase;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
public class JavaTypeProviderTest extends LightCodeInsightTestCase {
|
||||
public void testRangeHint() {
|
||||
doTest(" void test(int x) {\n" +
|
||||
" x = Math.abs(x);\n" +
|
||||
" /*expression*/x\n" +
|
||||
" }", "int",
|
||||
"<table>" +
|
||||
"<tr><td align='right' valign='top'><strong>Type:</strong></td>" +
|
||||
"<td>int</td></tr>" +
|
||||
"<tr><td align='right' valign='top'><strong>Range:</strong></td>" +
|
||||
"<td>{Integer.MIN_VALUE, 0..Integer.MAX_VALUE}</td></tr>" +
|
||||
"</table>");
|
||||
}
|
||||
|
||||
public void testOptionalHint() {
|
||||
doTest(" void test(java.util.Optional<String> t) {\n" +
|
||||
" if(t.isPresent()) {\n" +
|
||||
" /*expression*/t\n" +
|
||||
" }\n" +
|
||||
" }", "Optional<String>",
|
||||
"<table>" +
|
||||
"<tr><td align='right' valign='top'><strong>Type:</strong></td>" +
|
||||
"<td>Optional<String></td></tr>" +
|
||||
"<tr><td align='right' valign='top'><strong>Nullability:</strong></td>" +
|
||||
"<td>NotNull</td></tr>" +
|
||||
"<tr><td align='right' valign='top'><strong>Optional presense:</strong></td>" +
|
||||
"<td>present Optional</td></tr>" +
|
||||
"</table>");
|
||||
}
|
||||
|
||||
public void testTypeConstraint() {
|
||||
doTest(" void x(Object a) {\n" +
|
||||
" if(a instanceof String || a instanceof Number) {\n" +
|
||||
" \n" +
|
||||
" } else if(a instanceof CharSequence){\n" +
|
||||
" /*expression*/a\n" +
|
||||
" } else {\n" +
|
||||
" a\n" +
|
||||
" }\n" +
|
||||
" }", "Object",
|
||||
"<table>" +
|
||||
"<tr><td align='right' valign='top'><strong>Type:</strong></td>" +
|
||||
"<td>Object</td></tr>" +
|
||||
"<tr><td align='right' valign='top'><strong>Nullability:</strong></td>" +
|
||||
"<td>NotNull</td></tr>" +
|
||||
"<tr><td align='right' valign='top'><strong>Type constraints:</strong></td>" +
|
||||
"<td>instanceof CharSequence\n" +
|
||||
"not instanceof Number, String</td></tr></table>");
|
||||
}
|
||||
|
||||
public void testTypeConstraint2() {
|
||||
doTest(" void x(Object a) {\n" +
|
||||
" if(a instanceof String || a instanceof Number) {\n" +
|
||||
" \n" +
|
||||
" } else if(a instanceof CharSequence){\n" +
|
||||
" \n" +
|
||||
" } else {\n" +
|
||||
" /*expression*/a\n" +
|
||||
" }\n" +
|
||||
" }\n", "Object",
|
||||
"<table>" +
|
||||
"<tr><td align='right' valign='top'><strong>Type:</strong></td>" +
|
||||
"<td>Object</td></tr>" +
|
||||
"<tr><td align='right' valign='top'><strong>Type constraints:</strong></td>" +
|
||||
"<td>not instanceof CharSequence, Number</td></tr></table>");
|
||||
}
|
||||
|
||||
private static void doTest(@Language(value = "JAVA", prefix = "@SuppressWarnings(\"all\")class X{", suffix = "}") String method,
|
||||
@Language("HTML") String expectedHint,
|
||||
@Language("HTML") String expectedAdvancedHint) {
|
||||
PsiFile file =
|
||||
PsiFileFactory.getInstance(getProject()).createFileFromText("X.java", JavaFileType.INSTANCE, "class X{" + method + "}");
|
||||
PsiComment comment = SyntaxTraverser.psiTraverser(file).filter(PsiComment.class)
|
||||
.filter(c -> c.textMatches("/*expression*/"))
|
||||
.first();
|
||||
assertNotNull("/*expression*/ comment not found", comment);
|
||||
PsiElement sibling = comment.getNextSibling();
|
||||
assertNotNull(sibling);
|
||||
while (!(sibling instanceof PsiExpression) && sibling.getFirstChild() != null) {
|
||||
sibling = sibling.getFirstChild();
|
||||
}
|
||||
assertTrue("Expression not found at: " + sibling.getText(), sibling instanceof PsiExpression);
|
||||
PsiExpression expression = (PsiExpression)sibling;
|
||||
JavaTypeProvider provider = new JavaTypeProvider();
|
||||
assertEquals(expectedHint, provider.getInformationHint(expression));
|
||||
assertTrue(provider.hasAdvancedInformation());
|
||||
assertEquals(expectedAdvancedHint, provider.getAdvancedInformationHint(expression));
|
||||
}
|
||||
}
|
||||
@@ -44,4 +44,26 @@ public abstract class ExpressionTypeProvider<T extends PsiElement> {
|
||||
*/
|
||||
@NotNull
|
||||
public abstract List<T> getExpressionsAt(@NotNull PsiElement elementAt);
|
||||
|
||||
/**
|
||||
* @return true if this type provider can provide more useful information (e.g. value range, nullability, etc.)
|
||||
* on elements via {@link #getAdvancedInformationHint(PsiElement)}.
|
||||
*/
|
||||
public boolean hasAdvancedInformation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML string containing advanced type information hint (e.g. nullability, values range, etc.)
|
||||
*
|
||||
* @param element an element to provide information about
|
||||
* @return an advanced information hint. Should return the same result as {@link #getInformationHint(PsiElement)}
|
||||
* if no additional information is available for given element.
|
||||
* @throws UnsupportedOperationException if this provider does not provide any advanced information
|
||||
* (in this case {@link #hasAdvancedInformation()} method must return false).
|
||||
*/
|
||||
@NotNull
|
||||
public String getAdvancedInformationHint(@NotNull T element) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
+68
-7
@@ -37,8 +37,11 @@ import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.JBIterable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.HierarchyEvent;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public class ShowExpressionTypeHandler implements CodeInsightActionHandler {
|
||||
@@ -64,15 +67,12 @@ public class ShowExpressionTypeHandler implements CodeInsightActionHandler {
|
||||
Pass<PsiElement> callback = new Pass<PsiElement>() {
|
||||
@Override
|
||||
public void pass(@NotNull PsiElement expression) {
|
||||
ExpressionTypeProvider provider = ObjectUtils.assertNotNull(map.get(expression));
|
||||
//noinspection unchecked
|
||||
ExpressionTypeProvider<PsiElement> provider = ObjectUtils.assertNotNull(map.get(expression));
|
||||
final String informationHint = provider.getInformationHint(expression);
|
||||
TextRange range = expression.getTextRange();
|
||||
editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
HintManager.getInstance().setRequestFocusForNextHint(myRequestFocus);
|
||||
HintManager.getInstance().showInformationHint(editor, informationHint);
|
||||
});
|
||||
displayHint(new DisplayedTypeInfo(expression, provider, editor), informationHint);
|
||||
}
|
||||
};
|
||||
if (map.isEmpty()) {
|
||||
@@ -82,16 +82,33 @@ public class ShowExpressionTypeHandler implements CodeInsightActionHandler {
|
||||
});
|
||||
}
|
||||
else if (map.size() == 1) {
|
||||
callback.pass(ObjectUtils.assertNotNull(ContainerUtil.getFirstItem(map.keySet())));
|
||||
Map.Entry<PsiElement, ExpressionTypeProvider> entry = map.entrySet().iterator().next();
|
||||
PsiElement expression = entry.getKey();
|
||||
ExpressionTypeProvider provider = entry.getValue();
|
||||
DisplayedTypeInfo typeInfo = new DisplayedTypeInfo(expression, provider, editor);
|
||||
if (typeInfo.isRepeating() && provider.hasAdvancedInformation()) {
|
||||
//noinspection unchecked
|
||||
String informationHint = provider.getAdvancedInformationHint(expression);
|
||||
displayHint(typeInfo, informationHint);
|
||||
} else {
|
||||
callback.pass(expression);
|
||||
}
|
||||
}
|
||||
else {
|
||||
IntroduceTargetChooser.showChooser(
|
||||
editor, ContainerUtil.newArrayList(map.keySet()), callback,
|
||||
expression -> expression.getText()
|
||||
PsiElement::getText
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void displayHint(@NotNull DisplayedTypeInfo typeInfo, String informationHint) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
HintManager.getInstance().setRequestFocusForNextHint(myRequestFocus);
|
||||
typeInfo.showHint(informationHint);
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<PsiElement, ExpressionTypeProvider> getExpressions(@NotNull PsiFile file,
|
||||
@NotNull Editor editor) {
|
||||
@@ -130,5 +147,49 @@ public class ShowExpressionTypeHandler implements CodeInsightActionHandler {
|
||||
language -> DumbService.getInstance(project).filterByDumbAwareness(LanguageExpressionTypes.INSTANCE.allForLanguage(language))).addAllTo(ContainerUtil.newLinkedHashSet());
|
||||
}
|
||||
|
||||
static final class DisplayedTypeInfo {
|
||||
private static volatile DisplayedTypeInfo ourCurrentInstance;
|
||||
final @NotNull PsiElement myElement;
|
||||
final @NotNull ExpressionTypeProvider<?> myProvider;
|
||||
final @NotNull Editor myEditor;
|
||||
|
||||
DisplayedTypeInfo(@NotNull PsiElement element, @NotNull ExpressionTypeProvider<?> provider, @NotNull Editor editor) {
|
||||
myElement = element;
|
||||
myProvider = provider;
|
||||
myEditor = editor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
DisplayedTypeInfo info = (DisplayedTypeInfo)o;
|
||||
return Objects.equals(myElement, info.myElement) &&
|
||||
Objects.equals(myProvider, info.myProvider) &&
|
||||
Objects.equals(myEditor, info.myEditor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the same hint (i.e. on the same PsiElement, with the same provider, in the same editor) is displayed currently.
|
||||
*/
|
||||
boolean isRepeating() {
|
||||
return this.equals(ourCurrentInstance);
|
||||
}
|
||||
|
||||
void showHint(String informationHint) {
|
||||
JComponent label = HintUtil.createInformationLabel(informationHint);
|
||||
setInstance(this);
|
||||
label.addHierarchyListener(e -> {
|
||||
if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && !label.isShowing()) {
|
||||
setInstance(null);
|
||||
}
|
||||
});
|
||||
HintManager.getInstance().showInformationHint(myEditor, label);
|
||||
}
|
||||
|
||||
private static void setInstance(DisplayedTypeInfo typeInfo) {
|
||||
ourCurrentInstance = typeInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user