| /* |
| * 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 boolean 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 true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| @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); |
| |
| } |
| } |
| } |
| } |