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 }