/* * 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. *

* It also attempts to compute documentation for value strings like * ?android:attr/dividerHeight. *

* 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 } } }