/* * Copyright (C) 2007 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.values; import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX; import static com.android.SdkConstants.ANDROID_PREFIX; import static com.android.SdkConstants.ATTR_NAME; import static com.android.SdkConstants.ATTR_TYPE; import static com.android.SdkConstants.PREFIX_RESOURCE_REF; import static com.android.SdkConstants.TAG_ITEM; import static com.android.SdkConstants.TAG_STYLE; import static com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor.ATTRIBUTE_ICON_FILENAME; import static com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.DESCRIPTOR_LAYOUT; import com.android.annotations.VisibleForTesting; import com.android.ide.eclipse.adt.internal.editors.AndroidContentAssist; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; 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.SeparatorAttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiResourceAttributeNode; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import org.eclipse.jface.text.contentassist.CompletionProposal; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.w3c.dom.Element; import org.w3c.dom.Node; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Content Assist Processor for /res/values and /res/drawable XML files * <p> * Further enhancements: * <ul> * <li> Complete prefixes in the style element itself for the name attribute * <li> Complete parent names * </ul> */ @VisibleForTesting public class ValuesContentAssist extends AndroidContentAssist { /** * Constructor for ResourcesContentAssist */ public ValuesContentAssist() { super(AndroidTargetData.DESCRIPTOR_RESOURCES); } @Override protected void computeAttributeValues(List<ICompletionProposal> proposals, int offset, String parentTagName, String attributeName, Node node, String wordPrefix, boolean skipEndTag, int replaceLength) { super.computeAttributeValues(proposals, offset, parentTagName, attributeName, node, wordPrefix, skipEndTag, replaceLength); if (parentTagName.equals(TAG_ITEM) && ATTR_NAME.equals(attributeName)) { // Special case: the user is code completing inside // <style><item name="^"/></style> // In this case, ALL attributes are valid so we need to synthesize // a choice list from all the layout descriptors // Add in android: as a completion item? if (startsWith(ANDROID_NS_NAME_PREFIX, wordPrefix)) { proposals.add(new CompletionProposal(ANDROID_NS_NAME_PREFIX, offset - wordPrefix.length(), // replacementOffset wordPrefix.length() + replaceLength, // replacementLength ANDROID_NS_NAME_PREFIX.length(), // cursorPosition IconFactory.getInstance().getIcon(ATTRIBUTE_ICON_FILENAME), null, null, null)); } String attributePrefix = wordPrefix; if (startsWith(attributePrefix, ANDROID_NS_NAME_PREFIX)) { attributePrefix = attributePrefix.substring(ANDROID_NS_NAME_PREFIX.length()); } AndroidTargetData data = mEditor.getTargetData(); if (data != null) { IDescriptorProvider descriptorProvider = data.getDescriptorProvider( AndroidTargetData.DESCRIPTOR_LAYOUT); if (descriptorProvider != null) { ElementDescriptor[] rootElementDescriptors = descriptorProvider.getRootElementDescriptors(); Map<String, AttributeDescriptor> matches = new HashMap<String, AttributeDescriptor>(180); for (ElementDescriptor elementDesc : rootElementDescriptors) { for (AttributeDescriptor desc : elementDesc.getAttributes()) { if (desc instanceof SeparatorAttributeDescriptor) { continue; } String name = desc.getXmlLocalName(); if (startsWith(name, attributePrefix)) { matches.put(name, desc); } } } List<AttributeDescriptor> sorted = new ArrayList<AttributeDescriptor>(matches.size()); sorted.addAll(matches.values()); Collections.sort(sorted); char needTag = 0; addMatchingProposals(proposals, sorted.toArray(), offset, node, wordPrefix, needTag, true /* isAttribute */, false /* isNew */, skipEndTag /* skipEndTag */, replaceLength); return; } } } } @Override protected void computeTextValues(List<ICompletionProposal> proposals, int offset, Node parentNode, Node currentNode, UiElementNode uiParent, String prefix) { super.computeTextValues(proposals, offset, parentNode, currentNode, uiParent, prefix); if (parentNode.getNodeName().equals(TAG_ITEM) && parentNode.getParentNode() != null && TAG_STYLE.equals(parentNode.getParentNode().getNodeName())) { // Special case: the user is code completing inside // <style><item name="android:foo"/>|</style> // In this case, we need to find the right AttributeDescriptor // for the given attribute and offer its values AndroidTargetData data = mEditor.getTargetData(); if (data != null) { IDescriptorProvider descriptorProvider = data.getDescriptorProvider(DESCRIPTOR_LAYOUT); if (descriptorProvider != null) { Element element = (Element) parentNode; String attrName = element.getAttribute(ATTR_NAME); int pos = attrName.indexOf(':'); if (pos >= 0) { attrName = attrName.substring(pos + 1); } // Search for an attribute match ElementDescriptor[] rootElementDescriptors = descriptorProvider.getRootElementDescriptors(); for (ElementDescriptor elementDesc : rootElementDescriptors) { for (AttributeDescriptor desc : elementDesc.getAttributes()) { if (desc.getXmlLocalName().equals(attrName)) { // Make a ui parent node such that we can attach our // newfound attribute node to something (the code we delegate // to for looking up attribute completions will look at the // parent node and ask for its editor etc.) if (uiParent == null) { DocumentDescriptor documentDescriptor = data.getLayoutDescriptors().getDescriptor(); uiParent = documentDescriptor.createUiNode(); uiParent.setEditor(mEditor); } UiAttributeNode currAttrNode = desc.createUiNode(uiParent); AttribInfo attrInfo = new AttribInfo(); Object[] values = getAttributeValueChoices(currAttrNode, attrInfo, prefix); char needTag = attrInfo.needTag; if (attrInfo.correctedPrefix != null) { prefix = attrInfo.correctedPrefix; } boolean isAttribute = true; boolean isNew = false; int replaceLength = computeTextReplaceLength(currentNode, offset); addMatchingProposals(proposals, values, offset, currentNode, prefix, needTag, isAttribute, isNew, false /* skipEndTag */, replaceLength); return; } } } } } } if (parentNode.getNodeName().equals(TAG_ITEM)) { // Completing text content inside an <item> tag: offer @resource completion. if (prefix.startsWith(PREFIX_RESOURCE_REF) || prefix.trim().length() == 0) { String[] choices = UiResourceAttributeNode.computeResourceStringMatches( mEditor, null /*attributeDescriptor*/, prefix); if (choices == null || choices.length == 0) { return; } // If the parent item tag specifies a type, filter the results Node typeNode = parentNode.getAttributes().getNamedItem(ATTR_TYPE); if (typeNode != null) { String value = typeNode.getNodeValue(); List<String> filtered = new ArrayList<String>(); for (String s : choices) { if (s.startsWith(ANDROID_PREFIX) || s.startsWith(PREFIX_RESOURCE_REF+ value)) { filtered.add(s); } } if (filtered.size() > 0) { choices = filtered.toArray(new String[filtered.size()]); } } int replaceLength = computeTextReplaceLength(currentNode, offset); addMatchingProposals(proposals, choices, offset, currentNode, prefix, (char) 0 /*needTag*/, true /* isAttribute */, false /*isNew*/, false /* skipEndTag*/, replaceLength); } } } }