blob: 081ec80690aa865ffabe47363cb4129cd9ca0163 [file] [log] [blame]
/*
* 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.layout.properties;
import static com.android.SdkConstants.ANDROID_PKG;
import static com.android.SdkConstants.ANDROID_PREFIX;
import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
import static com.android.SdkConstants.NEW_ID_PREFIX;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.PREFIX_THEME_REF;
import com.android.ide.common.resources.ResourceItem;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiResourceAttributeNode;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.resources.ResourceType;
import com.android.utils.SdkUtils;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.fieldassist.ContentProposal;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Resource value completion for the given property
* <p>
* TODO:
* <ul>
* <li>also offer other values seen in the app
* <li>also offer previously set values for this property
* <li>also complete on properties
* </ul>
*/
class ResourceValueCompleter implements IContentProposalProvider {
protected final XmlProperty xmlProperty;
ResourceValueCompleter(XmlProperty xmlProperty) {
this.xmlProperty = xmlProperty;
}
@Override
public IContentProposal[] getProposals(String contents, int position) {
if (contents.startsWith(PREFIX_RESOURCE_REF)) {
CommonXmlEditor editor = this.xmlProperty.getXmlEditor();
if (editor != null) {
String[] matches = computeResourceStringMatches(
editor,
this.xmlProperty.mDescriptor, contents.substring(0, position));
List<IContentProposal> proposals = null;
if (matches != null && matches.length > 0) {
proposals = new ArrayList<IContentProposal>(matches.length);
for (String match : matches) {
proposals.add(new ContentProposal(match));
}
return proposals.toArray(new IContentProposal[proposals.size()]);
}
}
}
return new IContentProposal[0];
}
/**
* Similar to {@link UiResourceAttributeNode#computeResourceStringMatches}
* but computes complete results up front rather than dividing it up into
* smaller chunks like @{code @android:}, {@code string/}, and {@code ok}.
*/
static String[] computeResourceStringMatches(AndroidXmlEditor editor,
AttributeDescriptor attributeDescriptor, String prefix) {
List<String> results = new ArrayList<String>(200);
// System matches: only do this if the value already matches at least @a,
// and doesn't start with something that can't possibly be @android
if (prefix.startsWith("@a") && //$NON-NLS-1$
prefix.regionMatches(true /* ignoreCase */, 0, ANDROID_PREFIX, 0,
Math.min(prefix.length() - 1, ANDROID_PREFIX.length()))) {
AndroidTargetData data = editor.getTargetData();
if (data != null) {
ResourceRepository repository = data.getFrameworkResources();
addMatches(repository, prefix, true /* isSystem */, results);
}
} else if (prefix.startsWith("?") && //$NON-NLS-1$
prefix.regionMatches(true /* ignoreCase */, 0, ANDROID_THEME_PREFIX, 0,
Math.min(prefix.length() - 1, ANDROID_THEME_PREFIX.length()))) {
AndroidTargetData data = editor.getTargetData();
if (data != null) {
ResourceRepository repository = data.getFrameworkResources();
addMatches(repository, prefix, true /* isSystem */, results);
}
}
// When completing project resources skip framework resources unless
// the prefix possibly completes both, such as "@an" which can match
// both the project resource @animator as well as @android:string
if (!prefix.startsWith("@and") && !prefix.startsWith("?and")) { //$NON-NLS-1$ //$NON-NLS-2$
IProject project = editor.getProject();
if (project != null) {
// get the resource repository for this project and the system resources.
ResourceManager manager = ResourceManager.getInstance();
ResourceRepository repository = manager.getProjectResources(project);
if (repository != null) {
// We have a style name and a repository. Find all resources that match this
// type and recreate suggestions out of them.
addMatches(repository, prefix, false /* isSystem */, results);
}
}
}
if (attributeDescriptor != null) {
UiResourceAttributeNode.sortAttributeChoices(attributeDescriptor, results);
} else {
Collections.sort(results);
}
return results.toArray(new String[results.size()]);
}
private static void addMatches(ResourceRepository repository, String prefix, boolean isSystem,
List<String> results) {
int typeStart = isSystem
? ANDROID_PREFIX.length() : PREFIX_RESOURCE_REF.length();
for (ResourceType type : repository.getAvailableResourceTypes()) {
if (prefix.regionMatches(typeStart, type.getName(), 0,
Math.min(type.getName().length(), prefix.length() - typeStart))) {
StringBuilder sb = new StringBuilder();
if (prefix.length() == 0 || prefix.startsWith(PREFIX_RESOURCE_REF)) {
sb.append(PREFIX_RESOURCE_REF);
} else {
if (type != ResourceType.ATTR) {
continue;
}
sb.append(PREFIX_THEME_REF);
}
if (type == ResourceType.ID && prefix.startsWith(NEW_ID_PREFIX)) {
sb.append('+');
}
if (isSystem) {
sb.append(ANDROID_PKG).append(':');
}
sb.append(type.getName()).append('/');
String base = sb.toString();
int nameStart = typeStart + type.getName().length() + 1; // +1: add "/" divider
String namePrefix =
prefix.length() <= nameStart ? "" : prefix.substring(nameStart);
for (ResourceItem item : repository.getResourceItemsOfType(type)) {
String name = item.getName();
if (SdkUtils.startsWithIgnoreCase(name, namePrefix)) {
results.add(base + name);
}
}
}
}
}
}