1 /* 2 * Copyright (C) 2007 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 17 package com.android.ide.eclipse.adt.internal.editors.values; 18 19 import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX; 20 import static com.android.SdkConstants.ANDROID_PREFIX; 21 import static com.android.SdkConstants.ATTR_NAME; 22 import static com.android.SdkConstants.ATTR_TYPE; 23 import static com.android.SdkConstants.PREFIX_RESOURCE_REF; 24 import static com.android.SdkConstants.TAG_ITEM; 25 import static com.android.SdkConstants.TAG_STYLE; 26 import static com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor.ATTRIBUTE_ICON_FILENAME; 27 import static com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.DESCRIPTOR_LAYOUT; 28 29 import com.android.annotations.VisibleForTesting; 30 import com.android.ide.eclipse.adt.internal.editors.AndroidContentAssist; 31 import com.android.ide.eclipse.adt.internal.editors.IconFactory; 32 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; 33 import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; 34 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; 35 import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider; 36 import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor; 37 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode; 38 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; 39 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiResourceAttributeNode; 40 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; 41 42 import org.eclipse.jface.text.contentassist.CompletionProposal; 43 import org.eclipse.jface.text.contentassist.ICompletionProposal; 44 import org.w3c.dom.Element; 45 import org.w3c.dom.Node; 46 47 import java.util.ArrayList; 48 import java.util.Collections; 49 import java.util.HashMap; 50 import java.util.List; 51 import java.util.Map; 52 53 /** 54 * Content Assist Processor for /res/values and /res/drawable XML files 55 * <p> 56 * Further enhancements: 57 * <ul> 58 * <li> Complete prefixes in the style element itself for the name attribute 59 * <li> Complete parent names 60 * </ul> 61 */ 62 @VisibleForTesting 63 public class ValuesContentAssist extends AndroidContentAssist { 64 65 /** 66 * Constructor for ResourcesContentAssist 67 */ 68 public ValuesContentAssist() { 69 super(AndroidTargetData.DESCRIPTOR_RESOURCES); 70 } 71 72 @Override 73 protected boolean computeAttributeValues(List<ICompletionProposal> proposals, int offset, 74 String parentTagName, String attributeName, Node node, String wordPrefix, 75 boolean skipEndTag, int replaceLength) { 76 super.computeAttributeValues(proposals, offset, parentTagName, attributeName, node, 77 wordPrefix, skipEndTag, replaceLength); 78 79 if (parentTagName.equals(TAG_ITEM) && ATTR_NAME.equals(attributeName)) { 80 81 // Special case: the user is code completing inside 82 // <style><item name="^"/></style> 83 // In this case, ALL attributes are valid so we need to synthesize 84 // a choice list from all the layout descriptors 85 86 // Add in android: as a completion item? 87 if (startsWith(ANDROID_NS_NAME_PREFIX, wordPrefix)) { 88 proposals.add(new CompletionProposal(ANDROID_NS_NAME_PREFIX, 89 offset - wordPrefix.length(), // replacementOffset 90 wordPrefix.length() + replaceLength, // replacementLength 91 ANDROID_NS_NAME_PREFIX.length(), // cursorPosition 92 IconFactory.getInstance().getIcon(ATTRIBUTE_ICON_FILENAME), 93 null, null, null)); 94 } 95 96 97 String attributePrefix = wordPrefix; 98 if (startsWith(attributePrefix, ANDROID_NS_NAME_PREFIX)) { 99 attributePrefix = attributePrefix.substring(ANDROID_NS_NAME_PREFIX.length()); 100 } 101 102 AndroidTargetData data = mEditor.getTargetData(); 103 if (data != null) { 104 IDescriptorProvider descriptorProvider = 105 data.getDescriptorProvider( 106 AndroidTargetData.DESCRIPTOR_LAYOUT); 107 if (descriptorProvider != null) { 108 ElementDescriptor[] rootElementDescriptors = 109 descriptorProvider.getRootElementDescriptors(); 110 Map<String, AttributeDescriptor> matches = 111 new HashMap<String, AttributeDescriptor>(180); 112 for (ElementDescriptor elementDesc : rootElementDescriptors) { 113 for (AttributeDescriptor desc : elementDesc.getAttributes()) { 114 if (desc instanceof SeparatorAttributeDescriptor) { 115 continue; 116 } 117 String name = desc.getXmlLocalName(); 118 if (startsWith(name, attributePrefix)) { 119 matches.put(name, desc); 120 } 121 } 122 } 123 124 List<AttributeDescriptor> sorted = 125 new ArrayList<AttributeDescriptor>(matches.size()); 126 sorted.addAll(matches.values()); 127 Collections.sort(sorted); 128 char needTag = 0; 129 addMatchingProposals(proposals, sorted.toArray(), offset, node, wordPrefix, 130 needTag, true /* isAttribute */, false /* isNew */, 131 skipEndTag /* skipEndTag */, replaceLength); 132 return true; 133 } 134 } 135 } 136 137 return false; 138 } 139 140 @Override 141 protected void computeTextValues(List<ICompletionProposal> proposals, int offset, 142 Node parentNode, Node currentNode, UiElementNode uiParent, 143 String prefix) { 144 super.computeTextValues(proposals, offset, parentNode, currentNode, uiParent, 145 prefix); 146 147 if (parentNode.getNodeName().equals(TAG_ITEM) && 148 parentNode.getParentNode() != null && 149 TAG_STYLE.equals(parentNode.getParentNode().getNodeName())) { 150 151 // Special case: the user is code completing inside 152 // <style><item name="android:foo"/>|</style> 153 // In this case, we need to find the right AttributeDescriptor 154 // for the given attribute and offer its values 155 156 AndroidTargetData data = mEditor.getTargetData(); 157 if (data != null) { 158 IDescriptorProvider descriptorProvider = 159 data.getDescriptorProvider(DESCRIPTOR_LAYOUT); 160 if (descriptorProvider != null) { 161 162 Element element = (Element) parentNode; 163 String attrName = element.getAttribute(ATTR_NAME); 164 int pos = attrName.indexOf(':'); 165 if (pos >= 0) { 166 attrName = attrName.substring(pos + 1); 167 } 168 169 // Search for an attribute match 170 ElementDescriptor[] rootElementDescriptors = 171 descriptorProvider.getRootElementDescriptors(); 172 for (ElementDescriptor elementDesc : rootElementDescriptors) { 173 for (AttributeDescriptor desc : elementDesc.getAttributes()) { 174 if (desc.getXmlLocalName().equals(attrName)) { 175 // Make a ui parent node such that we can attach our 176 // newfound attribute node to something (the code we delegate 177 // to for looking up attribute completions will look at the 178 // parent node and ask for its editor etc.) 179 if (uiParent == null) { 180 DocumentDescriptor documentDescriptor = 181 data.getLayoutDescriptors().getDescriptor(); 182 uiParent = documentDescriptor.createUiNode(); 183 uiParent.setEditor(mEditor); 184 } 185 186 UiAttributeNode currAttrNode = desc.createUiNode(uiParent); 187 AttribInfo attrInfo = new AttribInfo(); 188 Object[] values = getAttributeValueChoices(currAttrNode, attrInfo, 189 prefix); 190 char needTag = attrInfo.needTag; 191 if (attrInfo.correctedPrefix != null) { 192 prefix = attrInfo.correctedPrefix; 193 } 194 boolean isAttribute = true; 195 boolean isNew = false; 196 int replaceLength = computeTextReplaceLength(currentNode, offset); 197 addMatchingProposals(proposals, values, offset, currentNode, 198 prefix, needTag, isAttribute, isNew, 199 false /* skipEndTag */, replaceLength); 200 return; 201 } 202 } 203 } 204 } 205 } 206 } 207 208 if (parentNode.getNodeName().equals(TAG_ITEM)) { 209 // Completing text content inside an <item> tag: offer @resource completion. 210 if (prefix.startsWith(PREFIX_RESOURCE_REF) || prefix.trim().length() == 0) { 211 String[] choices = UiResourceAttributeNode.computeResourceStringMatches( 212 mEditor, null /*attributeDescriptor*/, prefix); 213 if (choices == null || choices.length == 0) { 214 return; 215 } 216 217 // If the parent item tag specifies a type, filter the results 218 Node typeNode = parentNode.getAttributes().getNamedItem(ATTR_TYPE); 219 if (typeNode != null) { 220 String value = typeNode.getNodeValue(); 221 List<String> filtered = new ArrayList<String>(); 222 for (String s : choices) { 223 if (s.startsWith(ANDROID_PREFIX) || 224 s.startsWith(PREFIX_RESOURCE_REF+ value)) { 225 filtered.add(s); 226 } 227 } 228 if (filtered.size() > 0) { 229 choices = filtered.toArray(new String[filtered.size()]); 230 } 231 } 232 233 int replaceLength = computeTextReplaceLength(currentNode, offset); 234 addMatchingProposals(proposals, choices, offset, currentNode, 235 prefix, (char) 0 /*needTag*/, true /* isAttribute */, false /*isNew*/, 236 false /* skipEndTag*/, 237 replaceLength); 238 239 } 240 } 241 } 242 } 243