• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CompletionProposal(AndroidContentAssist assist, Object choice, String replacementString, int replacementOffset, int replacementLength, int cursorPosition, Image image, String displayString, IContextInformation contextInformation, String additionalProposalInfo, String nsPrefix, String nsUri)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
getSelection(IDocument document)106     public Point getSelection(IDocument document) {
107         return new Point(mReplacementOffset + mCursorPosition, 0);
108     }
109 
110     @Override
getContextInformation()111     public IContextInformation getContextInformation() {
112         return mContextInformation;
113     }
114 
115     @Override
getImage()116     public Image getImage() {
117         return mImage;
118     }
119 
120     @Override
getDisplayString()121     public String getDisplayString() {
122         if (mDisplayString != null) {
123             return mDisplayString;
124         }
125         return mReplacementString;
126     }
127 
128     @Override
getAdditionalProposalInfo()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
apply(IDocument document)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 }