| /* |
| * Copyright 2000-2014 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. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.intellij.codeInsight.completion; |
| |
| import com.intellij.codeInsight.TailType; |
| import com.intellij.codeInsight.lookup.*; |
| import com.intellij.codeInsight.template.Template; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.paths.PsiDynaReference; |
| import com.intellij.openapi.project.IndexNotReadyException; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.patterns.ElementPattern; |
| import com.intellij.patterns.ObjectPattern; |
| import com.intellij.psi.*; |
| import com.intellij.psi.filters.ContextGetter; |
| import com.intellij.psi.filters.ElementFilter; |
| import com.intellij.psi.filters.TrueFilter; |
| import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference; |
| import com.intellij.psi.meta.PsiMetaData; |
| import com.intellij.psi.util.PsiUtilCore; |
| import com.intellij.util.ReflectionUtil; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| import static com.intellij.patterns.StandardPatterns.character; |
| import static com.intellij.patterns.StandardPatterns.not; |
| |
| /** |
| * @deprecated see {@link com.intellij.codeInsight.completion.CompletionContributor} |
| */ |
| public class CompletionData { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CompletionData"); |
| public static final ObjectPattern.Capture<Character> NOT_JAVA_ID = not(character().javaIdentifierPart()); |
| private final Set<Class> myFinalScopes = new HashSet<Class>(); |
| private final List<CompletionVariant> myCompletionVariants = new ArrayList<CompletionVariant>(); |
| |
| protected CompletionData(){ } |
| |
| protected final void declareFinalScope(Class scopeClass){ |
| myFinalScopes.add(scopeClass); |
| } |
| |
| protected boolean isScopeFinal(Class scopeClass){ |
| if(myFinalScopes.contains(scopeClass)) |
| return true; |
| |
| for (final Class myFinalScope : myFinalScopes) { |
| if (ReflectionUtil.isAssignable(myFinalScope, scopeClass)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean isScopeAcceptable(PsiElement scope){ |
| |
| for (final CompletionVariant variant : myCompletionVariants) { |
| if (variant.isScopeAcceptable(scope)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| protected void defineScopeEquivalence(Class scopeClass, Class equivClass){ |
| final Iterator<CompletionVariant> iter = myCompletionVariants.iterator(); |
| if(isScopeFinal(scopeClass)){ |
| declareFinalScope(equivClass); |
| } |
| |
| while(iter.hasNext()){ |
| final CompletionVariant variant = iter.next(); |
| if(variant.isScopeClassAcceptable(scopeClass)){ |
| variant.includeScopeClass(equivClass, variant.isScopeClassFinal(scopeClass)); |
| } |
| } |
| } |
| |
| /** |
| * @deprecated |
| * @see com.intellij.codeInsight.completion.CompletionContributor |
| */ |
| protected void registerVariant(CompletionVariant variant){ |
| myCompletionVariants.add(variant); |
| } |
| |
| public void completeReference(final PsiReference reference, final Set<LookupElement> set, @NotNull final PsiElement position, final PsiFile file, |
| final int offset){ |
| final CompletionVariant[] variants = findVariants(position, file); |
| boolean hasApplicableVariants = false; |
| for (CompletionVariant variant : variants) { |
| if (variant.hasReferenceFilter()) { |
| variant.addReferenceCompletions(reference, position, set, file, CompletionData.this); |
| hasApplicableVariants = true; |
| } |
| } |
| |
| if (!hasApplicableVariants) { |
| myGenericVariant.addReferenceCompletions(reference, position, set, file, CompletionData.this); |
| } |
| } |
| |
| public void addKeywordVariants(Set<CompletionVariant> set, PsiElement position, final PsiFile file) { |
| ContainerUtil.addAll(set, findVariants(position, file)); |
| } |
| |
| public void completeKeywordsBySet(final Set<LookupElement> set, Set<CompletionVariant> variants, final PsiElement position, |
| final PrefixMatcher matcher, |
| final PsiFile file){ |
| for (final CompletionVariant variant : variants) { |
| variant.addKeywords(set, position, matcher, file, CompletionData.this); |
| } |
| } |
| |
| public String findPrefix(PsiElement insertedElement, int offsetInFile){ |
| return findPrefixStatic(insertedElement, offsetInFile); |
| } |
| |
| public CompletionVariant[] findVariants(final PsiElement position, final PsiFile file){ |
| final List<CompletionVariant> variants = new ArrayList<CompletionVariant>(); |
| PsiElement scope = position; |
| if(scope == null){ |
| scope = file; |
| } |
| while (scope != null) { |
| boolean breakFlag = false; |
| if (isScopeAcceptable(scope)){ |
| |
| for (final CompletionVariant variant : myCompletionVariants) { |
| if (variant.isVariantApplicable(position, scope) && !variants.contains(variant)) { |
| variants.add(variant); |
| if (variant.isScopeFinal(scope)) { |
| breakFlag = true; |
| } |
| } |
| } |
| } |
| if(breakFlag || isScopeFinal(scope.getClass())) |
| break; |
| scope = scope.getContext(); |
| if (scope instanceof PsiDirectory) break; |
| } |
| return variants.toArray(new CompletionVariant[variants.size()]); |
| } |
| |
| protected final CompletionVariant myGenericVariant = new CompletionVariant() { |
| @Override |
| public void addReferenceCompletions(PsiReference reference, PsiElement position, Set<LookupElement> set, final PsiFile file, |
| final CompletionData completionData) { |
| completeReference(reference, position, set, TailType.NONE, file, TrueFilter.INSTANCE, this); |
| } |
| }; |
| |
| @Nullable |
| public static String getReferencePrefix(@NotNull PsiElement insertedElement, int offsetInFile) { |
| try { |
| final PsiReference ref = insertedElement.getContainingFile().findReferenceAt(offsetInFile); |
| if(ref != null) { |
| final List<TextRange> ranges = ReferenceRange.getRanges(ref); |
| final PsiElement element = ref.getElement(); |
| final int elementStart = element.getTextRange().getStartOffset(); |
| for (TextRange refRange : ranges) { |
| if (refRange.contains(offsetInFile - elementStart)) { |
| final int endIndex = offsetInFile - elementStart; |
| final int beginIndex = refRange.getStartOffset(); |
| if (beginIndex > endIndex) { |
| LOG.error("Inconsistent reference (found at offset not included in its range): ref=" + ref + " element=" + element + " text=" + element.getText()); |
| } |
| if (beginIndex < 0) { |
| LOG.error("Inconsistent reference (begin < 0): ref=" + ref + " element=" + element + "; begin=" + beginIndex + " text=" + element.getText()); |
| } |
| LOG.assertTrue(endIndex >= 0); |
| return element.getText().substring(beginIndex, endIndex); |
| } |
| } |
| } |
| } |
| catch (IndexNotReadyException ignored) { |
| } |
| return null; |
| } |
| |
| public static String findPrefixStatic(final PsiElement insertedElement, final int offsetInFile, ElementPattern<Character> prefixStartTrim) { |
| if(insertedElement == null) return ""; |
| |
| final Document document = insertedElement.getContainingFile().getViewProvider().getDocument(); |
| assert document != null; |
| LOG.assertTrue(!PsiDocumentManager.getInstance(insertedElement.getProject()).isUncommited(document), "Uncommitted"); |
| |
| final String prefix = getReferencePrefix(insertedElement, offsetInFile); |
| if (prefix != null) return prefix; |
| |
| if (insertedElement instanceof PsiPlainText || insertedElement instanceof PsiComment) { |
| return CompletionUtil.findJavaIdentifierPrefix(insertedElement, offsetInFile); |
| } |
| |
| return findPrefixDefault(insertedElement, offsetInFile, prefixStartTrim); |
| } |
| |
| public static String findPrefixStatic(final PsiElement insertedElement, final int offsetInFile) { |
| return findPrefixStatic(insertedElement, offsetInFile, NOT_JAVA_ID); |
| } |
| |
| public static String findPrefixDefault(final PsiElement insertedElement, final int offset, @NotNull final ElementPattern trimStart) { |
| String substr = insertedElement.getText().substring(0, offset - insertedElement.getTextRange().getStartOffset()); |
| if (substr.length() == 0 || Character.isWhitespace(substr.charAt(substr.length() - 1))) return ""; |
| |
| substr = substr.trim(); |
| |
| int i = 0; |
| while (substr.length() > i && trimStart.accepts(substr.charAt(i))) i++; |
| return substr.substring(i).trim(); |
| } |
| |
| public static LookupElement objectToLookupItem(@NotNull Object object) { |
| if (object instanceof LookupElement) return (LookupElement)object; |
| |
| String s = null; |
| TailType tailType = TailType.NONE; |
| if (object instanceof PsiElement){ |
| s = PsiUtilCore.getName((PsiElement)object); |
| } |
| else if (object instanceof PsiMetaData) { |
| s = ((PsiMetaData)object).getName(); |
| } |
| else if (object instanceof String) { |
| s = (String)object; |
| } |
| else if (object instanceof Template) { |
| s = ((Template) object).getKey(); |
| } |
| else if (object instanceof PresentableLookupValue) { |
| s = ((PresentableLookupValue)object).getPresentation(); |
| } |
| if (s == null) { |
| throw new AssertionError("Null string for object: " + object + " of class " + (object != null ? object.getClass() : null)); |
| } |
| |
| LookupItem item = new LookupItem(object, s); |
| |
| if (object instanceof LookupValueWithUIHint && ((LookupValueWithUIHint) object).isBold()) { |
| item.setBold(); |
| } |
| if (object instanceof LookupValueWithTail) { |
| item.setAttribute(LookupItem.TAIL_TEXT_ATTR, " " + ((LookupValueWithTail)object).getTailText()); |
| } |
| item.setAttribute(CompletionUtil.TAIL_TYPE_ATTR, tailType); |
| return item; |
| } |
| |
| |
| protected void addLookupItem(Set<LookupElement> set, TailType tailType, @NotNull Object completion, final PsiFile file, |
| final CompletionVariant variant) { |
| LookupElement ret = objectToLookupItem(completion); |
| if (ret == null) return; |
| if (!(ret instanceof LookupItem)) { |
| set.add(ret); |
| return; |
| } |
| |
| LookupItem item = (LookupItem)ret; |
| |
| final InsertHandler insertHandler = variant.getInsertHandler(); |
| if(insertHandler != null && item.getInsertHandler() == null) { |
| item.setInsertHandler(insertHandler); |
| item.setTailType(TailType.UNKNOWN); |
| } |
| else if (tailType != TailType.NONE) { |
| item.setTailType(tailType); |
| } |
| final Map<Object, Object> itemProperties = variant.getItemProperties(); |
| for (final Object key : itemProperties.keySet()) { |
| item.setAttribute(key, itemProperties.get(key)); |
| } |
| |
| set.add(ret); |
| } |
| |
| protected void completeReference(final PsiReference reference, final PsiElement position, final Set<LookupElement> set, final TailType tailType, |
| final PsiFile file, |
| final ElementFilter filter, |
| final CompletionVariant variant) { |
| if (reference instanceof PsiMultiReference) { |
| for (PsiReference ref : getReferences((PsiMultiReference)reference)) { |
| completeReference(ref, position, set, tailType, file, filter, variant); |
| } |
| } |
| else if (reference instanceof PsiDynaReference) { |
| for (PsiReference ref : ((PsiDynaReference<?>)reference).getReferences()) { |
| completeReference(ref, position, set, tailType, file, filter, variant); |
| } |
| } |
| else{ |
| final Object[] completions = reference.getVariants(); |
| for (Object completion : completions) { |
| if (completion == null) { |
| LOG.error("Position=" + position + "\n;Reference=" + reference + "\n;variants=" + Arrays.toString(completions)); |
| } |
| if (completion instanceof PsiElement) { |
| final PsiElement psiElement = (PsiElement)completion; |
| if (filter.isClassAcceptable(psiElement.getClass()) && filter.isAcceptable(psiElement, position)) { |
| addLookupItem(set, tailType, completion, file, variant); |
| } |
| } |
| else { |
| if (completion instanceof LookupItem) { |
| final Object o = ((LookupItem)completion).getObject(); |
| if (o instanceof PsiElement) { |
| if (!filter.isClassAcceptable(o.getClass()) || !filter.isAcceptable(o, position)) continue; |
| } |
| } |
| addLookupItem(set, tailType, completion, file, variant); |
| } |
| } |
| } |
| } |
| |
| protected static PsiReference[] getReferences(final PsiMultiReference multiReference) { |
| final PsiReference[] references = multiReference.getReferences(); |
| final List<PsiReference> hard = ContainerUtil.findAll(references, new Condition<PsiReference>() { |
| @Override |
| public boolean value(final PsiReference object) { |
| return !object.isSoft(); |
| } |
| }); |
| if (!hard.isEmpty()) { |
| return hard.toArray(new PsiReference[hard.size()]); |
| } |
| return references; |
| } |
| |
| protected void addKeywords(final Set<LookupElement> set, final PsiElement position, final PrefixMatcher matcher, final PsiFile file, |
| final CompletionVariant variant, final Object comp, final TailType tailType) { |
| if (comp instanceof String) { |
| addKeyword(set, tailType, comp, matcher, file, variant); |
| } |
| else { |
| final CompletionContext context = position.getUserData(CompletionContext.COMPLETION_CONTEXT_KEY); |
| if (comp instanceof ContextGetter) { |
| final Object[] elements = ((ContextGetter)comp).get(position, context); |
| for (Object element : elements) { |
| addLookupItem(set, tailType, element, file, variant); |
| } |
| } |
| } |
| } |
| |
| private void addKeyword(Set<LookupElement> set, final TailType tailType, final Object comp, final PrefixMatcher matcher, |
| final PsiFile file, |
| final CompletionVariant variant) { |
| for (final LookupElement item : set) { |
| if (item.getObject().toString().equals(comp.toString())) { |
| return; |
| } |
| } |
| addLookupItem(set, tailType, comp, file, variant); |
| } |
| } |