/*
* 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.LookupElement;
import com.intellij.codeInsight.lookup.LookupItem;
import com.intellij.codeInsight.lookup.LookupValueWithUIHint;
import com.intellij.codeInsight.lookup.PresentableLookupValue;
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.TextRange;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.ObjectPattern;
import com.intellij.psi.*;
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.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 CompletionContributor}
*/
@SuppressWarnings("deprecation")
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 List<CompletionVariant> myCompletionVariants = new ArrayList<>();
protected CompletionData(){ }
private boolean isScopeAcceptable(PsiElement scope){
for (final CompletionVariant variant : myCompletionVariants) {
if (variant.isScopeAcceptable(scope)) {
return true;
}
}
return false;
}
/**
* @deprecated
* @see 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 CompletionVariant[] variants = findVariants(position, file);
boolean hasApplicableVariants = false;
for (CompletionVariant variant : variants) {
if (variant.hasReferenceFilter()) {
variant.addReferenceCompletions(reference, position, set, file, this);
hasApplicableVariants = true;
}
}
if (!hasApplicableVariants) {
myGenericVariant.addReferenceCompletions(reference, position, set, file, this);
}
}
public void addKeywordVariants(Set<CompletionVariant> set, PsiElement position, final PsiFile file) {
ContainerUtil.addAll(set, findVariants(position, file));
}
void completeKeywordsBySet(final Set<LookupElement> set, Set<CompletionVariant> variants){
for (final CompletionVariant variant : variants) {
variant.addKeywords(set, 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<>();
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)
break;
scope = scope.getContext();
if (scope instanceof PsiDirectory) break;
}
return variants.toArray(new CompletionVariant[variants.size()]);
}
protected final CompletionVariant myGenericVariant = new CompletionVariant() {
@Override
void addReferenceCompletions(PsiReference reference, PsiElement position, Set<LookupElement> set, final PsiFile file,
final CompletionData completionData) {
completeReference(reference, position, set, TailType.NONE, 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(final @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.getClass());
}
LookupItem item = new LookupItem(object, s);
if (object instanceof LookupValueWithUIHint && ((LookupValueWithUIHint) object).isBold()) {
item.setBold();
}
item.setAttribute(LookupItem.TAIL_TYPE_ATTR, tailType);
return item;
}
protected void addLookupItem(Set<LookupElement> set, TailType tailType, @NotNull Object completion, 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(PsiReference reference, PsiElement position, Set<LookupElement> set, TailType tailType, ElementFilter filter, CompletionVariant variant) {
if (reference instanceof PsiMultiReference) {
for (PsiReference ref : getReferences((PsiMultiReference)reference)) {
completeReference(ref, position, set, tailType, filter, variant);
}
}
else if (reference instanceof PsiDynaReference) {
for (PsiReference ref : ((PsiDynaReference<?>)reference).getReferences()) {
completeReference(ref, position, set, tailType, 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));
continue;
}
if (completion instanceof PsiElement) {
final PsiElement psiElement = (PsiElement)completion;
if (filter.isClassAcceptable(psiElement.getClass()) && filter.isAcceptable(psiElement, position)) {
addLookupItem(set, tailType, completion, 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;
}
}
try {
addLookupItem(set, tailType, completion, variant);
}
catch (AssertionError e) {
LOG.error("Caused by variant from reference: " + reference.getClass(), e);
}
}
}
}
}
protected static PsiReference[] getReferences(final PsiMultiReference multiReference) {
final PsiReference[] references = multiReference.getReferences();
final List<PsiReference> hard = ContainerUtil.findAll(references, object -> !object.isSoft());
if (!hard.isEmpty()) {
return hard.toArray(new PsiReference[hard.size()]);
}
return references;
}
void addKeywords(Set<LookupElement> set, CompletionVariant variant, Object comp, TailType tailType) {
if (!(comp instanceof String)) return;
for (final LookupElement item : set) {
if (item.getObject().toString().equals(comp)) {
return;
}
}
addLookupItem(set, tailType, comp, variant);
}
}