Package de.espend.idea.php.annotation.completion

Source Code of de.espend.idea.php.annotation.completion.AnnotationCompletionContributor

package de.espend.idea.php.annotation.completion;

import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.project.Project;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ProcessingContext;
import com.jetbrains.php.PhpIcons;
import com.jetbrains.php.lang.documentation.phpdoc.lexer.PhpDocTokenTypes;
import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment;
import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag;
import com.jetbrains.php.lang.psi.elements.*;
import com.jetbrains.php.lang.psi.resolve.types.PhpType;
import de.espend.idea.php.annotation.completion.insert.AnnotationTagInsertHandler;
import de.espend.idea.php.annotation.completion.lookupelements.PhpAnnotationPropertyLookupElement;
import de.espend.idea.php.annotation.completion.lookupelements.PhpClassAnnotationLookupElement;
import de.espend.idea.php.annotation.dict.AnnotationProperty;
import de.espend.idea.php.annotation.dict.AnnotationPropertyEnum;
import de.espend.idea.php.annotation.dict.AnnotationTarget;
import de.espend.idea.php.annotation.dict.PhpAnnotation;
import de.espend.idea.php.annotation.extension.PhpAnnotationCompletionProvider;
import de.espend.idea.php.annotation.extension.parameter.AnnotationCompletionProviderParameter;
import de.espend.idea.php.annotation.extension.parameter.AnnotationPropertyParameter;
import de.espend.idea.php.annotation.pattern.AnnotationPattern;
import de.espend.idea.php.annotation.util.AnnotationUtil;
import de.espend.idea.php.annotation.util.PhpElementsUtil;
import de.espend.idea.php.annotation.util.PhpIndexUtil;
import de.espend.idea.php.annotation.util.PluginUtil;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.util.Collection;
import java.util.Map;

/**
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class AnnotationCompletionContributor extends CompletionContributor {

    public AnnotationCompletionContributor() {
        extend(CompletionType.BASIC, AnnotationPattern.getDocBlockTag(), new PhpDocBlockTagAnnotations());
        extend(CompletionType.BASIC, AnnotationPattern.getDocAttribute(), new PhpDocAttributeList());
        extend(CompletionType.BASIC, AnnotationPattern.getTextIdentifier(), new PhpDocAttributeValue());
        extend(CompletionType.BASIC, AnnotationPattern.getDefaultPropertyValue(), new PhpDocDefaultValue());
        extend(CompletionType.BASIC, AnnotationPattern.getDocBlockTagAfterBackslash(), new PhpDocBlockTagAlias());

        // @Route(name=ClassName::<FOO>)
        extend(CompletionType.BASIC, AnnotationPattern.getClassConstant(), new PhpDocClassConstantCompletion());
    }

    private class PhpDocDefaultValue  extends CompletionProvider<CompletionParameters> {

        @Override
        protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) {

            PsiElement psiElement = parameters.getOriginalPosition();
            if(psiElement == null | !PluginUtil.isEnabled(psiElement)) {
                return;
            }

            PhpDocTag phpDocTag = PsiTreeUtil.getParentOfType(parameters.getOriginalPosition(), PhpDocTag.class);
            PhpClass phpClass = AnnotationUtil.getAnnotationReference(phpDocTag);
            if(phpClass == null) {
                return;
            }

            AnnotationPropertyParameter annotationPropertyParameter = new AnnotationPropertyParameter(parameters.getOriginalPosition(), phpClass, AnnotationPropertyParameter.Type.DEFAULT);
            providerWalker(parameters, context, result, annotationPropertyParameter);

        }
    }

    private void providerWalker(CompletionParameters parameters, ProcessingContext context, CompletionResultSet result, AnnotationPropertyParameter annotationPropertyParameter) {
        AnnotationCompletionProviderParameter completionParameter = new AnnotationCompletionProviderParameter(parameters, context, result);

        for(PhpAnnotationCompletionProvider phpAnnotationExtension : AnnotationUtil.EXTENSION_POINT_COMPLETION.getExtensions()) {
            phpAnnotationExtension.getPropertyValueCompletions(annotationPropertyParameter, completionParameter);
        }
    }

    private class PhpDocAttributeValue extends CompletionProvider<CompletionParameters> {

        @Override
        protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) {

            PsiElement psiElement = parameters.getOriginalPosition();
            if(psiElement == null | !PluginUtil.isEnabled(psiElement)) {
                return;
            }

            PsiElement phpDocString = psiElement.getContext();
            if(!(phpDocString instanceof StringLiteralExpression)) {
                return;
            }

            PsiElement propertyName = PhpElementsUtil.getPrevSiblingOfPatternMatch(phpDocString, PlatformPatterns.psiElement(PhpDocTokenTypes.DOC_IDENTIFIER));
            if(propertyName == null) {
                return;
            }

            PhpDocTag phpDocTag = PsiTreeUtil.getParentOfType(parameters.getOriginalPosition(), PhpDocTag.class);
            PhpClass phpClass = AnnotationUtil.getAnnotationReference(phpDocTag);
            if(phpClass == null) {
                return;
            }

            AnnotationPropertyParameter annotationPropertyParameter = new AnnotationPropertyParameter(parameters.getOriginalPosition(), phpClass, propertyName.getText(), AnnotationPropertyParameter.Type.PROPERTY_VALUE);
            providerWalker(parameters, context, result, annotationPropertyParameter);

        }
    }

    private class PhpDocAttributeList extends CompletionProvider<CompletionParameters> {

        @Override
        protected void addCompletions(@NotNull CompletionParameters completionParameters, ProcessingContext processingContext, @NotNull CompletionResultSet completionResultSet) {
            PsiElement psiElement = completionParameters.getOriginalPosition();

            if(psiElement == null || !PluginUtil.isEnabled(psiElement)) {
                return;
            }

            PhpDocTag phpDocTag = PsiTreeUtil.getParentOfType(psiElement, PhpDocTag.class);
            if(phpDocTag == null) {
                return;
            }

            PhpClass phpClass = AnnotationUtil.getAnnotationReference(phpDocTag);
            if(phpClass == null) {
                return;
            }

            for(Field field: phpClass.getFields()) {
                attachLookupElement(completionResultSet, field);
            }

        }

        private void attachLookupElement(CompletionResultSet completionResultSet, Field field) {
            if(field.isConstant()) {
               return;
            }

            String propertyName = field.getName();

            // private $isNillable = false;
            PhpType type = field.getType();
            if(type.toString().equals("bool")) {
                completionResultSet.addElement(new PhpAnnotationPropertyLookupElement(new AnnotationProperty(propertyName, AnnotationPropertyEnum.BOOLEAN)));
                return;
            }

            // private $isNillable = array();
            if(type.toString().equals("array")) {
                completionResultSet.addElement(new PhpAnnotationPropertyLookupElement(new AnnotationProperty(propertyName, AnnotationPropertyEnum.ARRAY)));
                return;
            }

            PhpDocComment docComment = field.getDocComment();
            if(docComment != null) {
                for(PhpDocTag varDocTag: docComment.getTagElementsByName("@var")) {
                    PhpPsiElement phpPsiElement = varDocTag.getFirstPsiChild();
                    if(phpPsiElement != null) {
                        String typeText = phpPsiElement.getText().toLowerCase();
                        if(!StringUtils.isBlank(typeText)) {

                            // @var array<string>
                            if(typeText.startsWith("array")) {
                                completionResultSet.addElement(new PhpAnnotationPropertyLookupElement(new AnnotationProperty(propertyName, AnnotationPropertyEnum.ARRAY)));
                                return;
                            }

                            if(typeText.equals("integer") || typeText.equals("int")) {
                                completionResultSet.addElement(new PhpAnnotationPropertyLookupElement(new AnnotationProperty(propertyName, AnnotationPropertyEnum.INTEGER)));
                                return;
                            }

                            if(typeText.equals("boolean") || typeText.equals("bool")) {
                                completionResultSet.addElement(new PhpAnnotationPropertyLookupElement(new AnnotationProperty(propertyName, AnnotationPropertyEnum.BOOLEAN)));
                                return;
                            }

                        }
                    }
                }
            }

            // public $var = array();
            if(field.getDefaultValue() instanceof ArrayCreationExpression) {
                completionResultSet.addElement(new PhpAnnotationPropertyLookupElement(new AnnotationProperty(propertyName, AnnotationPropertyEnum.ARRAY)));
                return;
            }

            // fallback
            completionResultSet.addElement(new PhpAnnotationPropertyLookupElement(new AnnotationProperty(propertyName, AnnotationPropertyEnum.STRING)));
        }

    }

    private class PhpDocBlockTagAnnotations  extends CompletionProvider<CompletionParameters> {

        @Override
        protected void addCompletions(@NotNull CompletionParameters completionParameters, ProcessingContext processingContext, @NotNull CompletionResultSet completionResultSet) {

            PsiElement psiElement = completionParameters.getOriginalPosition();

            if(psiElement == null || !PluginUtil.isEnabled(psiElement)) {
                return;
            }

            PhpDocComment parentOfType = PsiTreeUtil.getParentOfType(psiElement, PhpDocComment.class);
            if(parentOfType == null) {
                return;
            }

            AnnotationTarget annotationTarget = PhpElementsUtil.findAnnotationTarget(parentOfType);
            if(annotationTarget == null) {
                return;
            }

            Map<String, String> importMap = AnnotationUtil.getUseImportMap(parentOfType);

            Project project = completionParameters.getPosition().getProject();
            attachLookupElements(project, importMap , annotationTarget, completionResultSet);

        }

        private void attachLookupElements(Project project, Map<String, String> importMap, AnnotationTarget foundTarget, CompletionResultSet completionResultSet) {
            for(PhpAnnotation phpClass: getPhpAnnotationTargetClasses(project, foundTarget)) {
                PhpClassAnnotationLookupElement lookupElement = new PhpClassAnnotationLookupElement(phpClass.getPhpClass()).withInsertHandler(AnnotationTagInsertHandler.getInstance());
                String fqnClass = phpClass.getPhpClass().getPresentableFQN();

                if(fqnClass != null) {
                    if(!fqnClass.startsWith("\\")) {
                        fqnClass = "\\" + fqnClass;
                    }

                    for(Map.Entry<String, String> entry: importMap.entrySet()) {
                        if(fqnClass.startsWith(entry.getValue() + "\\")) {
                            lookupElement.withTypeText(entry.getKey() + fqnClass.substring(entry.getValue().length()));
                        }
                    }
                }

                completionResultSet.addElement(lookupElement);
            }
        }

        private Collection<PhpAnnotation> getPhpAnnotationTargetClasses(Project project, AnnotationTarget foundTarget) {
            // @TODO: how handle unknown types
            return AnnotationUtil.getAnnotationsOnTargetMap(project,
                foundTarget,
                AnnotationTarget.ALL,
                AnnotationTarget.UNKNOWN,
                AnnotationTarget.UNDEFINED
            ).values();
        }

    }

    private class PhpDocBlockTagAlias extends CompletionProvider<CompletionParameters> {

        @Override
        protected void addCompletions(@NotNull CompletionParameters completionParameters, ProcessingContext processingContext, @NotNull CompletionResultSet completionResultSet) {
            PsiElement psiElement = completionParameters.getOriginalPosition();
            if(psiElement == null) {
                return;
            }

            PsiElement phpDocTag = psiElement.getParent();
            if(!(phpDocTag instanceof PhpDocTag)) {
                return;
            }

            String name = ((PhpDocTag) phpDocTag).getName();
            if(!(name.startsWith("@"))) {
                return;
            }

            int start = name.indexOf("\\");
            if(start == -1) {
                return;
            }

            name = name.substring(1, start);

            Map<String, String> importMap = AnnotationUtil.getUseImportMap((PhpDocTag) phpDocTag);
            if(!importMap.containsKey(name)) {
                return;
            }

            // find annotation scope, to filter classes
            AnnotationTarget annotationTarget = PhpElementsUtil.findAnnotationTarget(PsiTreeUtil.getParentOfType(psiElement, PhpDocComment.class));
            if(annotationTarget == null) {
                annotationTarget = AnnotationTarget.UNKNOWN;
            }


            // force trailing backslash on namespace
            String namespace = importMap.get(name);
            if(!namespace.startsWith("\\")) {
                namespace = "\\" + namespace;
            }


            Map<String, PhpAnnotation> annotationMap = AnnotationUtil.getAnnotationsOnTargetMap(psiElement.getProject(), AnnotationTarget.ALL, AnnotationTarget.UNDEFINED, AnnotationTarget.UNKNOWN, annotationTarget);

            for(PhpClass phpClass: PhpIndexUtil.getPhpClassInsideNamespace(psiElement.getProject(), namespace)) {
                String fqnName = phpClass.getPresentableFQN();
                if(fqnName != null && annotationMap.containsKey(fqnName)) {
                    PhpAnnotation phpAnnotation = annotationMap.get(fqnName);
                    if(phpAnnotation != null && phpAnnotation.matchOneOf(AnnotationTarget.ALL, AnnotationTarget.UNDEFINED, AnnotationTarget.UNKNOWN, annotationTarget)) {
                        String subNamespace = fqnName.substring(namespace.length());
                        String lookupString = name + "\\" + subNamespace;
                        completionResultSet.addElement(LookupElementBuilder.create(lookupString).withTypeText(phpClass.getPresentableFQN(), true).withIcon(phpClass.getIcon()).withInsertHandler(AnnotationTagInsertHandler.getInstance()));
                    }
                }

            }

        }
    }

    /**
     * Completion for const fields inside phpdoc @Route(name=ClassName::<FOO>)
     */
    private class PhpDocClassConstantCompletion extends CompletionProvider<CompletionParameters> {

        @Override
        protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) {

            PsiElement psiElement = parameters.getOriginalPosition();
            if(psiElement == null | !PluginUtil.isEnabled(psiElement)) {
                return;
            }

            PsiElement docStatic = psiElement.getPrevSibling();
            if(docStatic != null && docStatic.getNode().getElementType() == PhpDocTokenTypes.DOC_STATIC) {
                PsiElement docIdentifier = docStatic.getPrevSibling();
                if(docIdentifier != null && docIdentifier.getNode().getElementType() == PhpDocTokenTypes.DOC_IDENTIFIER) {
                    String className = docIdentifier.getText();
                    PhpClass phpClass = PhpElementsUtil.getClassByContext(psiElement, className);
                    if(phpClass != null) {
                        for(Field field: phpClass.getFields()) {
                            if(field.isConstant()) {
                                result.addElement(LookupElementBuilder.create(field.getName()).withIcon(PhpIcons.FIELD).withTypeText(phpClass.getName(), true));
                            }
                        }
                    }
                }
            }
        }
    }
}
TOP

Related Classes of de.espend.idea.php.annotation.completion.AnnotationCompletionContributor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.