IDEA-66656 Suggest static fields in Class Name completion

This commit is contained in:
peter
2011-03-28 17:26:41 +02:00
parent b05d9f8568
commit 027c366b4d
10 changed files with 277 additions and 133 deletions
@@ -41,7 +41,7 @@ public class ExcludeFromCompletionLookupActionProvider implements LookupActionPr
} else if (o instanceof PsiMethod) {
final PsiMethod method = (PsiMethod)o;
if (method.hasModifierProperty(PsiModifier.STATIC)) {
addExcludes(consumer, method, StaticImportMethodFix.getMethodQualifiedName(method));
addExcludes(consumer, method, StaticImportMethodFix.getMemberQualifiedName(method));
}
}
}
@@ -3,9 +3,10 @@ package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.lookup.DefaultLookupItemRenderer;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiFormatUtil;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMember;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiSubstitutor;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@@ -16,92 +17,54 @@ import static com.intellij.util.ObjectUtils.assertNotNull;
* @author peter
*/
public class JavaGlobalMemberLookupElement extends LookupElement implements StaticallyImportable {
private final PsiMember myMember;
private final boolean myMergedOverloads;
private final PsiClass myContainingClass;
private final MemberLookupHelper myHelper;
private final InsertHandler<JavaGlobalMemberLookupElement> myQualifiedInsertion;
private final InsertHandler<JavaGlobalMemberLookupElement> myImportInsertion;
private boolean myShouldImport = false;
public JavaGlobalMemberLookupElement(List<PsiMethod> overloads,
PsiClass containingClass,
InsertHandler<JavaGlobalMemberLookupElement> qualifiedInsertion,
InsertHandler<JavaGlobalMemberLookupElement> importInsertion, boolean shouldImport) {
myMember = overloads.get(0);
myContainingClass = containingClass;
myHelper = new MemberLookupHelper(overloads, containingClass, shouldImport);
myQualifiedInsertion = qualifiedInsertion;
myImportInsertion = importInsertion;
myShouldImport = shouldImport;
myMergedOverloads = true;
}
public JavaGlobalMemberLookupElement(PsiMember member,
PsiClass containingClass,
InsertHandler<JavaGlobalMemberLookupElement> qualifiedInsertion,
InsertHandler<JavaGlobalMemberLookupElement> importInsertion, boolean shouldImport) {
myMember = member;
myContainingClass = containingClass;
myHelper = new MemberLookupHelper(member, containingClass, shouldImport, false);
myQualifiedInsertion = qualifiedInsertion;
myImportInsertion = importInsertion;
myShouldImport = shouldImport;
myMergedOverloads = false;
}
@NotNull
@Override
public PsiMember getObject() {
return myMember;
return myHelper.getMember();
}
@NotNull
public PsiClass getContainingClass() {
return myContainingClass;
return assertNotNull(myHelper.getContainingClass());
}
@NotNull
@Override
public String getLookupString() {
return assertNotNull(myMember.getName());
return assertNotNull(getObject().getName());
}
@Override
public void renderElement(LookupElementPresentation presentation) {
final String className = myContainingClass.getName();
presentation.setIcon(DefaultLookupItemRenderer.getRawIcon(this, presentation.isReal()));
final String methodName = myMember.getName();
if (Boolean.FALSE.equals(myShouldImport) && StringUtil.isNotEmpty(className)) {
presentation.setItemText(className + "." + methodName);
} else {
presentation.setItemText(methodName);
}
final String qname = myContainingClass.getQualifiedName();
String location = StringUtil.isEmpty(qname) ? "" : " (" + StringUtil.getPackageName(qname) + ")";
final String params = myMergedOverloads
? "(...)"
: myMember instanceof PsiMethod
? PsiFormatUtil.formatMethod((PsiMethod)myMember, PsiSubstitutor.EMPTY,
PsiFormatUtil.SHOW_PARAMETERS,
PsiFormatUtil.SHOW_NAME | PsiFormatUtil.SHOW_TYPE)
: "";
if (Boolean.TRUE.equals(myShouldImport) && StringUtil.isNotEmpty(className)) {
presentation.setTailText(params + " in " + className + location);
} else {
presentation.setTailText(params + location);
}
final PsiType type = myMember instanceof PsiMethod ? ((PsiMethod)myMember).getReturnType() : ((PsiField) myMember).getType();
if (type != null) {
presentation.setTypeText(type.getPresentableText());
}
myHelper.renderElement(presentation, false, PsiSubstitutor.EMPTY);
}
@Override
public void setShouldBeImported(boolean shouldImportStatic) {
myShouldImport = shouldImportStatic;
myHelper.setShouldBeImported(shouldImportStatic);
}
@Override
@@ -111,11 +74,12 @@ public class JavaGlobalMemberLookupElement extends LookupElement implements Stat
@Override
public boolean willBeImported() {
return myShouldImport;
return myHelper.willBeImported();
}
@Override
public void handleInsert(InsertionContext context) {
(myShouldImport ? myImportInsertion : myQualifiedInsertion).handleInsert(context, this);
(willBeImported() ? myImportInsertion : myQualifiedInsertion).handleInsert(context, this);
}
}
@@ -1,6 +1,7 @@
package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.codeInsight.lookup.VariableLookupItem;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
@@ -20,9 +21,7 @@ public class JavaGlobalMemberNameCompletionContributor extends CompletionContrib
return;
}
final PrefixMatcher matcher = result.getPrefixMatcher();
final String prefix = matcher.getPrefix();
if (prefix.length() == 0 || !Character.isLowerCase(prefix.charAt(0))) {
if (result.getPrefixMatcher().getPrefix().length() == 0) {
return;
}
@@ -43,24 +42,13 @@ public class JavaGlobalMemberNameCompletionContributor extends CompletionContrib
final StaticMemberProcessor processor = new StaticMemberProcessor(position) {
@NotNull
@Override
protected LookupElement createLookupElement(@NotNull PsiMember member, @NotNull final PsiClass containingClass, boolean shouldImport) {
protected LookupElement createLookupElement(@NotNull PsiMember member, @NotNull final PsiClass containingClass, final boolean shouldImport) {
if (member instanceof PsiMethod) {
final JavaMethodCallElement element = new JavaMethodCallElement((PsiMethod)member, true, false);
element.setShouldBeImported(shouldImport);
return element;
}
return new VariableLookupItem((PsiVariable)member) {
@Override
public void handleInsert(InsertionContext context) {
context.commitDocument();
final PsiReferenceExpression ref = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiReferenceExpression.class, false);
if (ref != null) {
ref.bindToElementViaStaticImport(containingClass);
PostprocessReformattingAspect.getInstance(ref.getProject()).doPostponedFormatting();
}
super.handleInsert(context);
}
};
return new StaticFieldLookupItem((PsiField)member, shouldImport, containingClass);
}
@Override
@@ -86,4 +74,51 @@ public class JavaGlobalMemberNameCompletionContributor extends CompletionContrib
return processor;
}
private static class StaticFieldLookupItem extends VariableLookupItem implements StaticallyImportable {
private final MemberLookupHelper myHelper;
private final PsiClass myContainingClass;
public StaticFieldLookupItem(PsiField field, boolean shouldImport, PsiClass containingClass) {
super(field);
myContainingClass = containingClass;
myHelper = new MemberLookupHelper(field, containingClass, shouldImport, false);
}
@Override
public void setShouldBeImported(boolean shouldImportStatic) {
myHelper.setShouldBeImported(shouldImportStatic);
}
@Override
public boolean canBeImported() {
return true;
}
@Override
public boolean willBeImported() {
return myHelper.willBeImported();
}
@Override
public void renderElement(LookupElementPresentation presentation) {
super.renderElement(presentation);
myHelper.renderElement(presentation, getAttribute(FORCE_QUALIFY) != null, PsiSubstitutor.EMPTY);
}
@Override
public void handleInsert(InsertionContext context) {
if (willBeImported()) {
context.commitDocument();
final PsiReferenceExpression ref = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiReferenceExpression.class, false);
if (ref != null) {
ref.bindToElementViaStaticImport(myContainingClass);
PostprocessReformattingAspect.getInstance(ref.getProject()).doPostponedFormatting();
}
} else {
context.getDocument().insertString(context.getStartOffset(), ".");
JavaCompletionUtil.insertClassReference(myContainingClass, context.getFile(), context.getStartOffset());
}
super.handleInsert(context);
}
}
}
@@ -24,9 +24,7 @@ import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.ClassConditionKey;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiFormatUtil;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -39,9 +37,7 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
private static final Key<PsiSubstitutor> INFERENCE_SUBSTITUTOR = Key.create("INFERENCE_SUBSTITUTOR");
@Nullable private final PsiClass myContainingClass;
private final PsiMethod myMethod;
private final boolean myCanImportStatic;
private boolean myShouldImportStatic;
private final boolean myMergedOverloads;
private final MemberLookupHelper myHelper;
public JavaMethodCallElement(@NotNull PsiMethod method) {
this(method, false, false);
@@ -50,9 +46,8 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
public JavaMethodCallElement(PsiMethod method, boolean canImportStatic, boolean mergedOverloads) {
super(method, method.getName());
myMethod = method;
myMergedOverloads = mergedOverloads;
myContainingClass = method.getContainingClass();
myCanImportStatic = canImportStatic;
myHelper = canImportStatic ? new MemberLookupHelper(method, myContainingClass, false, mergedOverloads) : null;
}
public PsiType getType() {
@@ -77,18 +72,17 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
@Override
public void setShouldBeImported(boolean shouldImportStatic) {
assert myCanImportStatic;
myShouldImportStatic = shouldImportStatic;
myHelper.setShouldBeImported(shouldImportStatic);
}
@Override
public boolean canBeImported() {
return myCanImportStatic;
return myHelper != null;
}
@Override
public boolean willBeImported() {
return myShouldImportStatic;
return canBeImported() && myHelper.willBeImported();
}
@Override
@@ -108,9 +102,9 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
qualifyMethodCall(file, startOffset, document);
insertExplicitTypeParameters(context, refStart);
}
else if (myCanImportStatic || getAttribute(FORCE_QUALIFY) != null) {
else if (myHelper != null || getAttribute(FORCE_QUALIFY) != null) {
context.commitDocument();
if (myCanImportStatic && myShouldImportStatic) {
if (myHelper != null && willBeImported()) {
final PsiReferenceExpression ref = PsiTreeUtil.findElementOfClassAtOffset(file, startOffset, PsiReferenceExpression.class, false);
if (ref != null && myContainingClass != null) {
ref.bindToElementViaStaticImport(myContainingClass);
@@ -234,38 +228,12 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
@Override
public void renderElement(LookupElementPresentation presentation) {
final String className = myContainingClass == null ? "???" : myContainingClass.getName();
presentation.setIcon(DefaultLookupItemRenderer.getRawIcon(this, presentation.isReal()));
final String methodName = myMethod.getName();
final boolean qualify = myCanImportStatic && !myShouldImportStatic || getAttribute(FORCE_QUALIFY) != null;
if (qualify && StringUtil.isNotEmpty(className)) {
presentation.setItemText(className + "." + methodName);
} else {
presentation.setItemText(methodName);
}
final String qname = myContainingClass == null ? "" : myContainingClass.getQualifiedName();
String location = !myCanImportStatic || StringUtil.isEmpty(qname) ? "" : " (" + StringUtil.getPackageName(qname) + ")";
presentation.setStrikeout(JavaElementLookupRenderer.isToStrikeout(this));
presentation.setItemTextBold(getAttribute(HIGHLIGHTED_ATTR) != null);
final String params = myMergedOverloads
? "(...)"
: PsiFormatUtil.formatMethod(myMethod, PsiSubstitutor.EMPTY,
PsiFormatUtil.SHOW_PARAMETERS,
PsiFormatUtil.SHOW_NAME | PsiFormatUtil.SHOW_TYPE);
if (myShouldImportStatic && StringUtil.isNotEmpty(className)) {
presentation.setTailText(params + " in " + className + location);
} else {
presentation.setTailText(params + location);
}
final PsiType type = myMethod.getReturnType();
if (type != null) {
presentation.setTypeText(getSubstitutor().substitute(type).getPresentableText());
}
MemberLookupHelper helper = myHelper != null ? myHelper : new MemberLookupHelper(myMethod, myContainingClass, false, false);
helper.renderElement(presentation, getAttribute(FORCE_QUALIFY) != null, getSubstitutor());
}
}
@@ -0,0 +1,82 @@
package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiFormatUtil;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* @author peter
*/
public class MemberLookupHelper {
private final PsiMember myMember;
private final boolean myMergedOverloads;
@Nullable private final PsiClass myContainingClass;
private boolean myShouldImport = false;
public MemberLookupHelper(List<PsiMethod> overloads, PsiClass containingClass, boolean shouldImport) {
this(overloads.get(0), containingClass, shouldImport, true);
}
public MemberLookupHelper(PsiMember member, PsiClass containingClass, boolean shouldImport, final boolean mergedOverloads) {
myMember = member;
myContainingClass = containingClass;
myShouldImport = shouldImport;
myMergedOverloads = mergedOverloads;
}
public PsiMember getMember() {
return myMember;
}
@Nullable
public PsiClass getContainingClass() {
return myContainingClass;
}
public void setShouldBeImported(boolean shouldImportStatic) {
myShouldImport = shouldImportStatic;
}
public boolean willBeImported() {
return myShouldImport;
}
public void renderElement(LookupElementPresentation presentation, boolean forceQualify, PsiSubstitutor substitutor) {
final String className = myContainingClass == null ? "???" : myContainingClass.getName();
final String memberName = myMember.getName();
if (!myShouldImport && StringUtil.isNotEmpty(className) || forceQualify) {
presentation.setItemText(className + "." + memberName);
} else {
presentation.setItemText(memberName);
}
final String qname = myContainingClass == null ? "" : myContainingClass.getQualifiedName();
String pkg = StringUtil.getPackageName(qname);
String location = StringUtil.isEmpty(pkg) ? "" : " (" + pkg + ")";
final String params = myMergedOverloads
? "(...)"
: myMember instanceof PsiMethod
? PsiFormatUtil.formatMethod((PsiMethod)myMember, PsiSubstitutor.EMPTY,
PsiFormatUtil.SHOW_PARAMETERS,
PsiFormatUtil.SHOW_NAME | PsiFormatUtil.SHOW_TYPE)
: "";
if (myShouldImport && StringUtil.isNotEmpty(className)) {
presentation.setTailText(params + " in " + className + location);
} else {
presentation.setTailText(params + location, !(myMember instanceof PsiMethod));
}
final PsiType type = myMember instanceof PsiMethod ? ((PsiMethod)myMember).getReturnType() : ((PsiField) myMember).getType();
if (type != null) {
presentation.setTypeText(substitutor.substitute(type).getPresentableText());
}
}
}
@@ -69,13 +69,7 @@ public abstract class StaticMemberProcessor {
if (classes.add(containingClass) && JavaCompletionUtil.isSourceLevelAccessible(myPosition, containingClass, myPackagedContext)) {
final boolean shouldImport = myStaticImportedClasses.contains(containingClass);
if (!myHintShown && !shouldImport && CompletionService.getCompletionService().getAdvertisementText() == null) {
final String shortcut = CompletionContributor.getActionShortcut(IdeActions.ACTION_SHOW_INTENTION_ACTIONS);
if (shortcut != null) {
CompletionService.getCompletionService().setAdvertisementText("To import a method statically, press " + shortcut);
}
myHintShown = true;
}
showHint(shouldImport);
final PsiMethod[] allMethods = containingClass.getAllMethods();
final List<PsiMethod> overloads = ContainerUtil.findAll(allMethods, new Condition<PsiMethod>() {
@@ -100,6 +94,32 @@ public abstract class StaticMemberProcessor {
}
}
}
for (final String fieldName : namesCache.getAllFieldNames()) {
if (matcher.prefixMatches(fieldName)) {
for (final PsiField field : namesCache.getFieldsByName(fieldName, scope)) {
if (isStaticallyImportable(field)) {
final PsiClass containingClass = field.getContainingClass();
assert containingClass != null;
if (JavaCompletionUtil.isSourceLevelAccessible(myPosition, containingClass, myPackagedContext)) {
final boolean shouldImport = myStaticImportedClasses.contains(containingClass);
showHint(shouldImport);
consumer.consume(createLookupElement(field, containingClass, shouldImport));
}
}
}
}
}
}
private void showHint(boolean shouldImport) {
if (!myHintShown && !shouldImport && CompletionService.getCompletionService().getAdvertisementText() == null) {
final String shortcut = CompletionContributor.getActionShortcut(IdeActions.ACTION_SHOW_INTENTION_ACTIONS);
if (shortcut != null) {
CompletionService.getCompletionService().setAdvertisementText("To import a method statically, press " + shortcut);
}
myHintShown = true;
}
}
public List<PsiMember> processMembersOfRegisteredClasses(@Nullable final PrefixMatcher matcher, PairConsumer<PsiMember, PsiClass> consumer) {
@@ -113,7 +133,7 @@ public abstract class StaticMemberProcessor {
}
}
for (final PsiField field : psiClass.getAllFields()) {
if (matcher == null || matcher.prefixMatches(field.getName())) {
if (matcher == null || matcher.prefixMatches(field. getName())) {
if (isStaticallyImportable(field)) {
consumer.consume(field, psiClass);
}
@@ -125,17 +145,11 @@ public abstract class StaticMemberProcessor {
private boolean isStaticallyImportable(final PsiMember member) {
if (member.hasModifierProperty(PsiModifier.STATIC) && myResolveHelper.isAccessible(member, myPosition, null)) {
final PsiClass containingClass = member.getContainingClass();
if (containingClass != null) {
if (!JavaCompletionUtil.isInExcludedPackage(containingClass) &&
(!(member instanceof PsiMethod) || !StaticImportMethodFix.isExcluded((PsiMethod)member))) {
return true;
}
return member.hasModifierProperty(PsiModifier.STATIC) && isAccessible(member) && !StaticImportMethodFix.isExcluded(member);
}
}
}
return false;
protected boolean isAccessible(PsiMember member) {
return myResolveHelper.isAccessible(member, myPosition, null);
}
@NotNull
@@ -142,8 +142,8 @@ public class StaticImportMethodFix implements IntentionAction {
return result;
}
public static boolean isExcluded(PsiMethod method) {
String name = getMethodQualifiedName(method);
public static boolean isExcluded(PsiMember method) {
String name = getMemberQualifiedName(method);
if (name == null) return false;
CodeInsightSettings cis = CodeInsightSettings.getInstance();
for (String excluded : cis.EXCLUDED_PACKAGES) {
@@ -204,7 +204,7 @@ public class StaticImportMethodFix implements IntentionAction {
return FINAL_CHOICE;
}
String qname = getMethodQualifiedName(selectedValue);
String qname = getMemberQualifiedName(selectedValue);
if (qname == null) return FINAL_CHOICE;
List<String> excludableStrings = AddImportAction.getAllExcludableStrings(qname);
return new BaseListPopupStep<String>(null, excludableStrings) {
@@ -270,7 +270,7 @@ public class StaticImportMethodFix implements IntentionAction {
}
@Nullable
public static String getMethodQualifiedName(PsiMethod method) {
public static String getMemberQualifiedName(PsiMember method) {
PsiClass containingClass = method.getContainingClass();
if (containingClass == null) return null;
String className = containingClass.getQualifiedName();
@@ -24,6 +24,36 @@ public class Foo {
class Bar {{ abcmethod()<caret> }}"""
}
public void testFieldName() throws Exception {
myFixture.addClass("""
package foo;
public class Foo {
public static int abcfield = 2
static final int fieldThatsNotVisible = 3
}
""")
doTest "class Bar {{ abcf<caret> }}", true, """import static foo.Foo.abcfield;
class Bar {{ abcfield<caret> }}"""
}
public void testFieldNameQualified() throws Exception {
myFixture.addClass("""
package foo;
public class Foo {
public static int abcfield = 2
static final int fieldThatsNotVisible = 3
}
""")
doTest "class Bar {{ abcf<caret> }}", false, """import foo.Foo;
class Bar {{ Foo.abcfield<caret> }}"""
}
public void testQualifiedMethodName() throws Exception {
myFixture.addClass("""
package foo;
@@ -235,8 +235,7 @@ public class GroovyCompletionContributor extends CompletionContributor {
final PsiElement position = parameters.getPosition();
if (((GrReferenceElement)position.getParent()).getQualifier() != null) return;
final String s = result.getPrefixMatcher().getPrefix();
if (StringUtil.isEmpty(s) || !Character.isLowerCase(s.charAt(0))) return;
if (StringUtil.isEmpty(result.getPrefixMatcher().getPrefix())) return;
completeStaticMembers(position).processStaticMethodsGlobally(result);
}
@@ -493,6 +492,18 @@ public class GroovyCompletionContributor extends CompletionContributor {
return new JavaGlobalMemberLookupElement(overloads, containingClass, QUALIFIED_METHOD_INSERT_HANDLER, STATIC_IMPORT_INSERT_HANDLER,
shouldImport);
}
@Override
protected boolean isAccessible(PsiMember member) {
boolean result = super.isAccessible(member);
if (!result && member instanceof GrField) {
GrAccessorMethod[] getters = ((GrField)member).getGetters();
return getters.length > 0 && super.isAccessible(getters[0]);
}
return result;
}
};
final PsiFile file = position.getContainingFile();
if (file instanceof GroovyFile) {
@@ -128,6 +128,46 @@ def foo() {
}
public void testImportedStaticField() throws Exception {
myFixture.addFileToProject("b.groovy", """
class Foo {
static def abcfield1
static def abcfield2
}""")
myFixture.configureByText("a.groovy", """def foo() {
abcfi<caret>
}""")
def item = myFixture.complete(CompletionType.CLASS_NAME)[0]
((StaticallyImportable) item).shouldBeImported = true
myFixture.type('\n')
myFixture.checkResult """import static Foo.abcfield1
def foo() {
abcfield1<caret>
}"""
}
public void testImportedInterfaceConstant() throws Exception {
myFixture.addFileToProject("b.groovy", """
interface Foo {
static def abcfield1 = 2
static def abcfield2 = 3
}""")
myFixture.configureByText("a.groovy", """def foo() {
abcfi<caret>
}""")
def item = myFixture.complete(CompletionType.CLASS_NAME)[0]
((StaticallyImportable) item).shouldBeImported = true
myFixture.type('\n')
myFixture.checkResult """import static Foo.abcfield1
def foo() {
abcfield1<caret>
}"""
}
public void testQualifiedStaticMethod() throws Exception {
myFixture.addFileToProject("foo/b.groovy", """package foo
class Foo {