1/* 2 * Copyright (C) 2012 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 */ 16package com.android.ide.eclipse.adt.internal.editors.layout.properties; 17 18import static com.android.SdkConstants.ANDROID_PKG; 19import static com.android.SdkConstants.ANDROID_PREFIX; 20import static com.android.SdkConstants.ANDROID_THEME_PREFIX; 21import static com.android.SdkConstants.NEW_ID_PREFIX; 22import static com.android.SdkConstants.PREFIX_RESOURCE_REF; 23import static com.android.SdkConstants.PREFIX_THEME_REF; 24 25import com.android.ide.common.resources.ResourceItem; 26import com.android.ide.common.resources.ResourceRepository; 27import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; 28import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor; 29import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; 30import com.android.ide.eclipse.adt.internal.editors.uimodel.UiResourceAttributeNode; 31import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; 32import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; 33import com.android.resources.ResourceType; 34import com.android.utils.SdkUtils; 35 36import org.eclipse.core.resources.IProject; 37import org.eclipse.jface.fieldassist.ContentProposal; 38import org.eclipse.jface.fieldassist.IContentProposal; 39import org.eclipse.jface.fieldassist.IContentProposalProvider; 40 41import java.util.ArrayList; 42import java.util.Collections; 43import java.util.List; 44 45/** 46 * Resource value completion for the given property 47 * <p> 48 * TODO: 49 * <ul> 50 * <li>also offer other values seen in the app 51 * <li>also offer previously set values for this property 52 * <li>also complete on properties 53 * </ul> 54 */ 55class ResourceValueCompleter implements IContentProposalProvider { 56 protected final XmlProperty xmlProperty; 57 58 ResourceValueCompleter(XmlProperty xmlProperty) { 59 this.xmlProperty = xmlProperty; 60 } 61 62 @Override 63 public IContentProposal[] getProposals(String contents, int position) { 64 if (contents.startsWith(PREFIX_RESOURCE_REF)) { 65 CommonXmlEditor editor = this.xmlProperty.getXmlEditor(); 66 if (editor != null) { 67 String[] matches = computeResourceStringMatches( 68 editor, 69 this.xmlProperty.mDescriptor, contents.substring(0, position)); 70 List<IContentProposal> proposals = null; 71 if (matches != null && matches.length > 0) { 72 proposals = new ArrayList<IContentProposal>(matches.length); 73 for (String match : matches) { 74 proposals.add(new ContentProposal(match)); 75 } 76 return proposals.toArray(new IContentProposal[proposals.size()]); 77 } 78 } 79 } 80 81 return new IContentProposal[0]; 82 } 83 84 /** 85 * Similar to {@link UiResourceAttributeNode#computeResourceStringMatches} 86 * but computes complete results up front rather than dividing it up into 87 * smaller chunks like @{code @android:}, {@code string/}, and {@code ok}. 88 */ 89 static String[] computeResourceStringMatches(AndroidXmlEditor editor, 90 AttributeDescriptor attributeDescriptor, String prefix) { 91 List<String> results = new ArrayList<String>(200); 92 93 // System matches: only do this if the value already matches at least @a, 94 // and doesn't start with something that can't possibly be @android 95 if (prefix.startsWith("@a") && //$NON-NLS-1$ 96 prefix.regionMatches(true /* ignoreCase */, 0, ANDROID_PREFIX, 0, 97 Math.min(prefix.length() - 1, ANDROID_PREFIX.length()))) { 98 AndroidTargetData data = editor.getTargetData(); 99 if (data != null) { 100 ResourceRepository repository = data.getFrameworkResources(); 101 addMatches(repository, prefix, true /* isSystem */, results); 102 } 103 } else if (prefix.startsWith("?") && //$NON-NLS-1$ 104 prefix.regionMatches(true /* ignoreCase */, 0, ANDROID_THEME_PREFIX, 0, 105 Math.min(prefix.length() - 1, ANDROID_THEME_PREFIX.length()))) { 106 AndroidTargetData data = editor.getTargetData(); 107 if (data != null) { 108 ResourceRepository repository = data.getFrameworkResources(); 109 addMatches(repository, prefix, true /* isSystem */, results); 110 } 111 } 112 113 114 // When completing project resources skip framework resources unless 115 // the prefix possibly completes both, such as "@an" which can match 116 // both the project resource @animator as well as @android:string 117 if (!prefix.startsWith("@and") && !prefix.startsWith("?and")) { //$NON-NLS-1$ //$NON-NLS-2$ 118 IProject project = editor.getProject(); 119 if (project != null) { 120 // get the resource repository for this project and the system resources. 121 ResourceManager manager = ResourceManager.getInstance(); 122 ResourceRepository repository = manager.getProjectResources(project); 123 if (repository != null) { 124 // We have a style name and a repository. Find all resources that match this 125 // type and recreate suggestions out of them. 126 addMatches(repository, prefix, false /* isSystem */, results); 127 } 128 129 } 130 } 131 132 if (attributeDescriptor != null) { 133 UiResourceAttributeNode.sortAttributeChoices(attributeDescriptor, results); 134 } else { 135 Collections.sort(results); 136 } 137 138 return results.toArray(new String[results.size()]); 139 } 140 141 private static void addMatches(ResourceRepository repository, String prefix, boolean isSystem, 142 List<String> results) { 143 int typeStart = isSystem 144 ? ANDROID_PREFIX.length() : PREFIX_RESOURCE_REF.length(); 145 146 for (ResourceType type : repository.getAvailableResourceTypes()) { 147 if (prefix.regionMatches(typeStart, type.getName(), 0, 148 Math.min(type.getName().length(), prefix.length() - typeStart))) { 149 StringBuilder sb = new StringBuilder(); 150 if (prefix.length() == 0 || prefix.startsWith(PREFIX_RESOURCE_REF)) { 151 sb.append(PREFIX_RESOURCE_REF); 152 } else { 153 if (type != ResourceType.ATTR) { 154 continue; 155 } 156 sb.append(PREFIX_THEME_REF); 157 } 158 159 if (type == ResourceType.ID && prefix.startsWith(NEW_ID_PREFIX)) { 160 sb.append('+'); 161 } 162 163 if (isSystem) { 164 sb.append(ANDROID_PKG).append(':'); 165 } 166 167 sb.append(type.getName()).append('/'); 168 String base = sb.toString(); 169 170 int nameStart = typeStart + type.getName().length() + 1; // +1: add "/" divider 171 String namePrefix = 172 prefix.length() <= nameStart ? "" : prefix.substring(nameStart); 173 for (ResourceItem item : repository.getResourceItemsOfType(type)) { 174 String name = item.getName(); 175 if (SdkUtils.startsWithIgnoreCase(name, namePrefix)) { 176 results.add(base + name); 177 } 178 } 179 } 180 } 181 } 182} 183