| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Eclipse Public License, Version 1.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.eclipse.org/org/documents/epl-v10.php |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.ide.eclipse.adt.internal.editors; |
| |
| import static com.android.SdkConstants.XMLNS; |
| |
| import com.android.ide.common.api.IAttributeInfo; |
| import com.android.ide.eclipse.adt.AdtPlugin; |
| import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; |
| import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; |
| import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; |
| import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider; |
| import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor; |
| import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; |
| import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; |
| import com.android.utils.XmlUtils; |
| |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.jdt.core.ISourceRange; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.jface.text.contentassist.ICompletionProposal; |
| import org.eclipse.jface.text.contentassist.IContextInformation; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NamedNodeMap; |
| |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Just like {@link org.eclipse.jface.text.contentassist.CompletionProposal}, |
| * but computes the documentation string lazily since they are typically only |
| * displayed for a small subset (the currently focused item) of the available |
| * proposals, and producing the strings requires some computation. |
| * <p> |
| * It also attempts to compute documentation for value strings like |
| * ?android:attr/dividerHeight. |
| * <p> |
| * TODO: Enhance this to compute documentation for additional values, such as |
| * the various enum values (which are available in the attrs.xml file, but not |
| * in the AttributeInfo objects for each enum value). To do this, I should |
| * basically keep around the maps computed by the attrs.xml parser. |
| */ |
| class CompletionProposal implements ICompletionProposal { |
| private static final Pattern ATTRIBUTE_PATTERN = |
| Pattern.compile("[@?]android:attr/(.*)"); //$NON-NLS-1$ |
| |
| private final AndroidContentAssist mAssist; |
| private final Object mChoice; |
| private final int mCursorPosition; |
| private int mReplacementOffset; |
| private final int mReplacementLength; |
| private final String mReplacementString; |
| private final Image mImage; |
| private final String mDisplayString; |
| private final IContextInformation mContextInformation; |
| private final String mNsPrefix; |
| private final String mNsUri; |
| private String mAdditionalProposalInfo; |
| |
| 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) { |
| assert replacementString != null; |
| assert replacementOffset >= 0; |
| assert replacementLength >= 0; |
| assert cursorPosition >= 0; |
| |
| mAssist = assist; |
| mChoice = choice; |
| mCursorPosition = cursorPosition; |
| mReplacementOffset = replacementOffset; |
| mReplacementLength = replacementLength; |
| mReplacementString = replacementString; |
| mImage = image; |
| mDisplayString = displayString; |
| mContextInformation = contextInformation; |
| mAdditionalProposalInfo = additionalProposalInfo; |
| mNsPrefix = nsPrefix; |
| mNsUri = nsUri; |
| } |
| |
| @Override |
| public Point getSelection(IDocument document) { |
| return new Point(mReplacementOffset + mCursorPosition, 0); |
| } |
| |
| @Override |
| public IContextInformation getContextInformation() { |
| return mContextInformation; |
| } |
| |
| @Override |
| public Image getImage() { |
| return mImage; |
| } |
| |
| @Override |
| public String getDisplayString() { |
| if (mDisplayString != null) { |
| return mDisplayString; |
| } |
| return mReplacementString; |
| } |
| |
| @Override |
| public String getAdditionalProposalInfo() { |
| if (mAdditionalProposalInfo == null) { |
| if (mChoice instanceof ElementDescriptor) { |
| String tooltip = ((ElementDescriptor)mChoice).getTooltip(); |
| mAdditionalProposalInfo = DescriptorsUtils.formatTooltip(tooltip); |
| } else if (mChoice instanceof TextAttributeDescriptor) { |
| mAdditionalProposalInfo = ((TextAttributeDescriptor) mChoice).getTooltip(); |
| } else if (mChoice instanceof String) { |
| // Try to produce it lazily for strings like @android |
| String value = (String) mChoice; |
| Matcher matcher = ATTRIBUTE_PATTERN.matcher(value); |
| if (matcher.matches()) { |
| String attrName = matcher.group(1); |
| AndroidTargetData data = mAssist.getEditor().getTargetData(); |
| if (data != null) { |
| IDescriptorProvider descriptorProvider = |
| data.getDescriptorProvider(mAssist.getRootDescriptorId()); |
| if (descriptorProvider != null) { |
| ElementDescriptor[] rootElementDescriptors = |
| descriptorProvider.getRootElementDescriptors(); |
| for (ElementDescriptor elementDesc : rootElementDescriptors) { |
| for (AttributeDescriptor desc : elementDesc.getAttributes()) { |
| String name = desc.getXmlLocalName(); |
| if (attrName.equals(name)) { |
| IAttributeInfo attributeInfo = desc.getAttributeInfo(); |
| if (attributeInfo != null) { |
| return attributeInfo.getJavaDoc(); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| } |
| } else if (mChoice instanceof IType) { |
| IType type = (IType) mChoice; |
| try { |
| ISourceRange javadocRange = type.getJavadocRange(); |
| if (javadocRange != null && javadocRange.getLength() > 0) { |
| ISourceRange sourceRange = type.getSourceRange(); |
| if (sourceRange != null) { |
| String source = type.getSource(); |
| int start = javadocRange.getOffset() - sourceRange.getOffset(); |
| int length = javadocRange.getLength(); |
| String doc = source.substring(start, start + length); |
| return doc; |
| } |
| } |
| return type.getAttachedJavadoc(new NullProgressMonitor()); |
| } catch (JavaModelException e) { |
| AdtPlugin.log(e, null); |
| } |
| } |
| } |
| |
| return mAdditionalProposalInfo; |
| } |
| |
| @Override |
| public void apply(IDocument document) { |
| try { |
| Position position = new Position(mReplacementOffset); |
| document.addPosition(position); |
| |
| // Ensure that the namespace is defined in the document |
| String prefix = mNsPrefix; |
| if (mNsUri != null && prefix != null) { |
| Document dom = DomUtilities.getDocument(mAssist.getEditor()); |
| if (dom != null) { |
| Element root = dom.getDocumentElement(); |
| if (root != null) { |
| // Is the namespace already defined? |
| boolean found = false; |
| NamedNodeMap attributes = root.getAttributes(); |
| for (int i = 0, n = attributes.getLength(); i < n; i++) { |
| Attr attribute = (Attr) attributes.item(i); |
| String name = attribute.getName(); |
| if (name.startsWith(XMLNS) && mNsUri.equals(attribute.getValue())) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| if (prefix.endsWith(":")) { //$NON-NLS-1$ |
| prefix = prefix.substring(0, prefix.length() - 1); |
| } |
| XmlUtils.lookupNamespacePrefix(root, mNsUri, prefix, true); |
| } |
| } |
| } |
| } |
| |
| mReplacementOffset = position.getOffset(); |
| document.removePosition(position); |
| document.replace(mReplacementOffset, mReplacementLength, mReplacementString); |
| } catch (BadLocationException x) { |
| // ignore |
| } |
| } |
| } |