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 }