Home | History | Annotate | Download | only in values
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.editors.values;
     18 
     19 import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
     20 import static com.android.SdkConstants.ANDROID_PREFIX;
     21 import static com.android.SdkConstants.ATTR_NAME;
     22 import static com.android.SdkConstants.ATTR_TYPE;
     23 import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
     24 import static com.android.SdkConstants.TAG_ITEM;
     25 import static com.android.SdkConstants.TAG_STYLE;
     26 import static com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor.ATTRIBUTE_ICON_FILENAME;
     27 import static com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.DESCRIPTOR_LAYOUT;
     28 
     29 import com.android.annotations.VisibleForTesting;
     30 import com.android.ide.eclipse.adt.internal.editors.AndroidContentAssist;
     31 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
     32 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
     33 import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
     34 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
     35 import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider;
     36 import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor;
     37 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
     38 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
     39 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiResourceAttributeNode;
     40 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     41 
     42 import org.eclipse.jface.text.contentassist.CompletionProposal;
     43 import org.eclipse.jface.text.contentassist.ICompletionProposal;
     44 import org.w3c.dom.Element;
     45 import org.w3c.dom.Node;
     46 
     47 import java.util.ArrayList;
     48 import java.util.Collections;
     49 import java.util.HashMap;
     50 import java.util.List;
     51 import java.util.Map;
     52 
     53 /**
     54  * Content Assist Processor for /res/values and /res/drawable XML files
     55  * <p>
     56  * Further enhancements:
     57  * <ul>
     58  *   <li> Complete prefixes in the style element itself for the name attribute
     59  *   <li> Complete parent names
     60  * </ul>
     61  */
     62 @VisibleForTesting
     63 public class ValuesContentAssist extends AndroidContentAssist {
     64 
     65     /**
     66      * Constructor for ResourcesContentAssist
     67      */
     68     public ValuesContentAssist() {
     69         super(AndroidTargetData.DESCRIPTOR_RESOURCES);
     70     }
     71 
     72     @Override
     73     protected boolean computeAttributeValues(List<ICompletionProposal> proposals, int offset,
     74             String parentTagName, String attributeName, Node node, String wordPrefix,
     75             boolean skipEndTag, int replaceLength) {
     76         super.computeAttributeValues(proposals, offset, parentTagName, attributeName, node,
     77                 wordPrefix, skipEndTag, replaceLength);
     78 
     79         if (parentTagName.equals(TAG_ITEM) && ATTR_NAME.equals(attributeName)) {
     80 
     81             // Special case: the user is code completing inside
     82             //    <style><item name="^"/></style>
     83             // In this case, ALL attributes are valid so we need to synthesize
     84             // a choice list from all the layout descriptors
     85 
     86             // Add in android: as a completion item?
     87             if (startsWith(ANDROID_NS_NAME_PREFIX, wordPrefix)) {
     88                 proposals.add(new CompletionProposal(ANDROID_NS_NAME_PREFIX,
     89                         offset - wordPrefix.length(), // replacementOffset
     90                         wordPrefix.length() + replaceLength, // replacementLength
     91                         ANDROID_NS_NAME_PREFIX.length(), // cursorPosition
     92                         IconFactory.getInstance().getIcon(ATTRIBUTE_ICON_FILENAME),
     93                         null, null, null));
     94             }
     95 
     96 
     97             String attributePrefix = wordPrefix;
     98             if (startsWith(attributePrefix, ANDROID_NS_NAME_PREFIX)) {
     99                 attributePrefix = attributePrefix.substring(ANDROID_NS_NAME_PREFIX.length());
    100             }
    101 
    102             AndroidTargetData data = mEditor.getTargetData();
    103             if (data != null) {
    104                 IDescriptorProvider descriptorProvider =
    105                     data.getDescriptorProvider(
    106                             AndroidTargetData.DESCRIPTOR_LAYOUT);
    107                 if (descriptorProvider != null) {
    108                     ElementDescriptor[] rootElementDescriptors =
    109                         descriptorProvider.getRootElementDescriptors();
    110                     Map<String, AttributeDescriptor> matches =
    111                         new HashMap<String, AttributeDescriptor>(180);
    112                     for (ElementDescriptor elementDesc : rootElementDescriptors) {
    113                         for (AttributeDescriptor desc : elementDesc.getAttributes()) {
    114                             if (desc instanceof SeparatorAttributeDescriptor) {
    115                                 continue;
    116                             }
    117                             String name = desc.getXmlLocalName();
    118                             if (startsWith(name, attributePrefix)) {
    119                                 matches.put(name, desc);
    120                             }
    121                         }
    122                     }
    123 
    124                     List<AttributeDescriptor> sorted =
    125                         new ArrayList<AttributeDescriptor>(matches.size());
    126                     sorted.addAll(matches.values());
    127                     Collections.sort(sorted);
    128                     char needTag = 0;
    129                     addMatchingProposals(proposals, sorted.toArray(), offset, node, wordPrefix,
    130                             needTag, true /* isAttribute */, false /* isNew */,
    131                             skipEndTag /* skipEndTag */, replaceLength);
    132                     return true;
    133                 }
    134             }
    135         }
    136 
    137         return false;
    138     }
    139 
    140     @Override
    141     protected void computeTextValues(List<ICompletionProposal> proposals, int offset,
    142             Node parentNode, Node currentNode, UiElementNode uiParent,
    143             String prefix) {
    144         super.computeTextValues(proposals, offset, parentNode, currentNode, uiParent,
    145                 prefix);
    146 
    147         if (parentNode.getNodeName().equals(TAG_ITEM) &&
    148             parentNode.getParentNode() != null &&
    149             TAG_STYLE.equals(parentNode.getParentNode().getNodeName())) {
    150 
    151             // Special case: the user is code completing inside
    152             //    <style><item name="android:foo"/>|</style>
    153             // In this case, we need to find the right AttributeDescriptor
    154             // for the given attribute and offer its values
    155 
    156             AndroidTargetData data = mEditor.getTargetData();
    157             if (data != null) {
    158                 IDescriptorProvider descriptorProvider =
    159                     data.getDescriptorProvider(DESCRIPTOR_LAYOUT);
    160                 if (descriptorProvider != null) {
    161 
    162                     Element element = (Element) parentNode;
    163                     String attrName = element.getAttribute(ATTR_NAME);
    164                     int pos = attrName.indexOf(':');
    165                     if (pos >= 0) {
    166                         attrName = attrName.substring(pos + 1);
    167                     }
    168 
    169                     // Search for an attribute match
    170                     ElementDescriptor[] rootElementDescriptors =
    171                         descriptorProvider.getRootElementDescriptors();
    172                     for (ElementDescriptor elementDesc : rootElementDescriptors) {
    173                         for (AttributeDescriptor desc : elementDesc.getAttributes()) {
    174                             if (desc.getXmlLocalName().equals(attrName)) {
    175                                 // Make a ui parent node such that we can attach our
    176                                 // newfound attribute node to something (the code we delegate
    177                                 // to for looking up attribute completions will look at the
    178                                 // parent node and ask for its editor etc.)
    179                                 if (uiParent == null) {
    180                                     DocumentDescriptor documentDescriptor =
    181                                         data.getLayoutDescriptors().getDescriptor();
    182                                     uiParent = documentDescriptor.createUiNode();
    183                                     uiParent.setEditor(mEditor);
    184                                 }
    185 
    186                                 UiAttributeNode currAttrNode = desc.createUiNode(uiParent);
    187                                 AttribInfo attrInfo = new AttribInfo();
    188                                 Object[] values = getAttributeValueChoices(currAttrNode, attrInfo,
    189                                         prefix);
    190                                 char needTag = attrInfo.needTag;
    191                                 if (attrInfo.correctedPrefix != null) {
    192                                     prefix = attrInfo.correctedPrefix;
    193                                 }
    194                                 boolean isAttribute = true;
    195                                 boolean isNew = false;
    196                                 int replaceLength = computeTextReplaceLength(currentNode, offset);
    197                                 addMatchingProposals(proposals, values, offset, currentNode,
    198                                         prefix, needTag, isAttribute, isNew,
    199                                         false /* skipEndTag */, replaceLength);
    200                                 return;
    201                             }
    202                         }
    203                     }
    204                 }
    205             }
    206         }
    207 
    208         if (parentNode.getNodeName().equals(TAG_ITEM)) {
    209             // Completing text content inside an <item> tag: offer @resource completion.
    210             if (prefix.startsWith(PREFIX_RESOURCE_REF) || prefix.trim().length() == 0) {
    211                 String[] choices = UiResourceAttributeNode.computeResourceStringMatches(
    212                         mEditor, null /*attributeDescriptor*/, prefix);
    213                 if (choices == null || choices.length == 0) {
    214                     return;
    215                 }
    216 
    217                 // If the parent item tag specifies a type, filter the results
    218                 Node typeNode = parentNode.getAttributes().getNamedItem(ATTR_TYPE);
    219                 if (typeNode != null) {
    220                     String value = typeNode.getNodeValue();
    221                     List<String> filtered = new ArrayList<String>();
    222                     for (String s : choices) {
    223                         if (s.startsWith(ANDROID_PREFIX) ||
    224                                 s.startsWith(PREFIX_RESOURCE_REF+ value)) {
    225                             filtered.add(s);
    226                         }
    227                     }
    228                     if (filtered.size() > 0) {
    229                         choices = filtered.toArray(new String[filtered.size()]);
    230                     }
    231                 }
    232 
    233                 int replaceLength = computeTextReplaceLength(currentNode, offset);
    234                 addMatchingProposals(proposals, choices, offset, currentNode,
    235                         prefix, (char) 0 /*needTag*/, true /* isAttribute */, false /*isNew*/,
    236                         false /* skipEndTag*/,
    237                         replaceLength);
    238 
    239             }
    240         }
    241     }
    242 }
    243