Home | History | Annotate | Download | only in editors
      1 /*
      2  * Copyright (C) 2012 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 package com.android.ide.eclipse.adt.internal.editors;
     17 
     18 import static com.android.SdkConstants.XMLNS;
     19 
     20 import com.android.ide.common.api.IAttributeInfo;
     21 import com.android.ide.eclipse.adt.AdtPlugin;
     22 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
     23 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
     24 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
     25 import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider;
     26 import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
     27 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
     28 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     29 import com.android.utils.XmlUtils;
     30 
     31 import org.eclipse.core.runtime.NullProgressMonitor;
     32 import org.eclipse.jdt.core.ISourceRange;
     33 import org.eclipse.jdt.core.IType;
     34 import org.eclipse.jdt.core.JavaModelException;
     35 import org.eclipse.jface.text.BadLocationException;
     36 import org.eclipse.jface.text.IDocument;
     37 import org.eclipse.jface.text.Position;
     38 import org.eclipse.jface.text.contentassist.ICompletionProposal;
     39 import org.eclipse.jface.text.contentassist.IContextInformation;
     40 import org.eclipse.swt.graphics.Image;
     41 import org.eclipse.swt.graphics.Point;
     42 import org.w3c.dom.Attr;
     43 import org.w3c.dom.Document;
     44 import org.w3c.dom.Element;
     45 import org.w3c.dom.NamedNodeMap;
     46 
     47 import java.util.regex.Matcher;
     48 import java.util.regex.Pattern;
     49 
     50 /**
     51  * Just like {@link org.eclipse.jface.text.contentassist.CompletionProposal},
     52  * but computes the documentation string lazily since they are typically only
     53  * displayed for a small subset (the currently focused item) of the available
     54  * proposals, and producing the strings requires some computation.
     55  * <p>
     56  * It also attempts to compute documentation for value strings like
     57  * ?android:attr/dividerHeight.
     58  * <p>
     59  * TODO: Enhance this to compute documentation for additional values, such as
     60  * the various enum values (which are available in the attrs.xml file, but not
     61  * in the AttributeInfo objects for each enum value). To do this, I should
     62  * basically keep around the maps computed by the attrs.xml parser.
     63  */
     64 class CompletionProposal implements ICompletionProposal {
     65     private static final Pattern ATTRIBUTE_PATTERN =
     66             Pattern.compile("[@?]android:attr/(.*)"); //$NON-NLS-1$
     67 
     68     private final AndroidContentAssist mAssist;
     69     private final Object mChoice;
     70     private final int mCursorPosition;
     71     private int mReplacementOffset;
     72     private final int mReplacementLength;
     73     private final String mReplacementString;
     74     private final Image mImage;
     75     private final String mDisplayString;
     76     private final IContextInformation mContextInformation;
     77     private final String mNsPrefix;
     78     private final String mNsUri;
     79     private String mAdditionalProposalInfo;
     80 
     81     CompletionProposal(AndroidContentAssist assist,
     82             Object choice, String replacementString, int replacementOffset,
     83             int replacementLength, int cursorPosition, Image image, String displayString,
     84             IContextInformation contextInformation, String additionalProposalInfo,
     85             String nsPrefix, String nsUri) {
     86         assert replacementString != null;
     87         assert replacementOffset >= 0;
     88         assert replacementLength >= 0;
     89         assert cursorPosition >= 0;
     90 
     91         mAssist = assist;
     92         mChoice = choice;
     93         mCursorPosition = cursorPosition;
     94         mReplacementOffset = replacementOffset;
     95         mReplacementLength = replacementLength;
     96         mReplacementString = replacementString;
     97         mImage = image;
     98         mDisplayString = displayString;
     99         mContextInformation = contextInformation;
    100         mAdditionalProposalInfo = additionalProposalInfo;
    101         mNsPrefix = nsPrefix;
    102         mNsUri = nsUri;
    103     }
    104 
    105     @Override
    106     public Point getSelection(IDocument document) {
    107         return new Point(mReplacementOffset + mCursorPosition, 0);
    108     }
    109 
    110     @Override
    111     public IContextInformation getContextInformation() {
    112         return mContextInformation;
    113     }
    114 
    115     @Override
    116     public Image getImage() {
    117         return mImage;
    118     }
    119 
    120     @Override
    121     public String getDisplayString() {
    122         if (mDisplayString != null) {
    123             return mDisplayString;
    124         }
    125         return mReplacementString;
    126     }
    127 
    128     @Override
    129     public String getAdditionalProposalInfo() {
    130         if (mAdditionalProposalInfo == null) {
    131             if (mChoice instanceof ElementDescriptor) {
    132                 String tooltip = ((ElementDescriptor)mChoice).getTooltip();
    133                 mAdditionalProposalInfo = DescriptorsUtils.formatTooltip(tooltip);
    134             } else if (mChoice instanceof TextAttributeDescriptor) {
    135                 mAdditionalProposalInfo = ((TextAttributeDescriptor) mChoice).getTooltip();
    136             } else if (mChoice instanceof String) {
    137                 // Try to produce it lazily for strings like @android
    138                 String value = (String) mChoice;
    139                 Matcher matcher = ATTRIBUTE_PATTERN.matcher(value);
    140                 if (matcher.matches()) {
    141                     String attrName = matcher.group(1);
    142                     AndroidTargetData data = mAssist.getEditor().getTargetData();
    143                     if (data != null) {
    144                         IDescriptorProvider descriptorProvider =
    145                             data.getDescriptorProvider(mAssist.getRootDescriptorId());
    146                         if (descriptorProvider != null) {
    147                             ElementDescriptor[] rootElementDescriptors =
    148                                 descriptorProvider.getRootElementDescriptors();
    149                             for (ElementDescriptor elementDesc : rootElementDescriptors) {
    150                                 for (AttributeDescriptor desc : elementDesc.getAttributes()) {
    151                                     String name = desc.getXmlLocalName();
    152                                     if (attrName.equals(name)) {
    153                                         IAttributeInfo attributeInfo = desc.getAttributeInfo();
    154                                         if (attributeInfo != null) {
    155                                             return attributeInfo.getJavaDoc();
    156                                         }
    157                                     }
    158                                 }
    159                             }
    160                         }
    161                     }
    162 
    163                 }
    164             } else if (mChoice instanceof IType) {
    165                 IType type = (IType) mChoice;
    166                 try {
    167                     ISourceRange javadocRange = type.getJavadocRange();
    168                     if (javadocRange != null && javadocRange.getLength() > 0) {
    169                         ISourceRange sourceRange = type.getSourceRange();
    170                         if (sourceRange != null) {
    171                             String source = type.getSource();
    172                             int start = javadocRange.getOffset() - sourceRange.getOffset();
    173                             int length = javadocRange.getLength();
    174                             String doc = source.substring(start, start + length);
    175                             return doc;
    176                         }
    177                     }
    178                     return type.getAttachedJavadoc(new NullProgressMonitor());
    179                 } catch (JavaModelException e) {
    180                     AdtPlugin.log(e, null);
    181                 }
    182             }
    183         }
    184 
    185         return mAdditionalProposalInfo;
    186     }
    187 
    188     @Override
    189     public void apply(IDocument document) {
    190         try {
    191             Position position = new Position(mReplacementOffset);
    192             document.addPosition(position);
    193 
    194             // Ensure that the namespace is defined in the document
    195             String prefix = mNsPrefix;
    196             if (mNsUri != null && prefix != null) {
    197                 Document dom = DomUtilities.getDocument(mAssist.getEditor());
    198                 if (dom != null) {
    199                     Element root = dom.getDocumentElement();
    200                     if (root != null) {
    201                         // Is the namespace already defined?
    202                         boolean found = false;
    203                         NamedNodeMap attributes = root.getAttributes();
    204                         for (int i = 0, n = attributes.getLength(); i < n; i++) {
    205                             Attr attribute = (Attr) attributes.item(i);
    206                             String name = attribute.getName();
    207                             if (name.startsWith(XMLNS) && mNsUri.equals(attribute.getValue())) {
    208                                 found = true;
    209                                 break;
    210                             }
    211                         }
    212                         if (!found) {
    213                             if (prefix.endsWith(":")) { //$NON-NLS-1$
    214                                 prefix = prefix.substring(0, prefix.length() - 1);
    215                             }
    216                             XmlUtils.lookupNamespacePrefix(root, mNsUri, prefix, true);
    217                         }
    218                     }
    219                 }
    220             }
    221 
    222             mReplacementOffset = position.getOffset();
    223             document.removePosition(position);
    224             document.replace(mReplacementOffset, mReplacementLength, mReplacementString);
    225         } catch (BadLocationException x) {
    226             // ignore
    227         }
    228     }
    229 }