diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java index 92ddd84ed..725c6a82b 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java @@ -16,6 +16,7 @@ package com.android.ide.eclipse.adt.internal.editors; +import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; @@ -30,6 +31,7 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.UiFlagAttributeNode; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.sdklib.SdkConstants; +import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; @@ -74,7 +76,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { /** Regexp to detect an element tag name */ private static Pattern sFirstElementWord = Pattern.compile("^[a-zA-Z0-9_:]+"); //$NON-NLS-1$ - + /** Regexp to detect whitespace */ private static Pattern sWhitespace = Pattern.compile("\\s+"); //$NON-NLS-1$ @@ -86,11 +88,11 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { private ElementDescriptor mRootDescriptor; private final int mDescriptorId; - + private AndroidEditor mEditor; /** - * Constructor for AndroidContentAssist + * Constructor for AndroidContentAssist * @param descriptorId An id for {@link AndroidTargetData#getDescriptorProvider(int)}. * The Id can be one of {@link AndroidTargetData#DESCRIPTOR_MANIFEST}, * {@link AndroidTargetData#DESCRIPTOR_LAYOUT}, @@ -110,17 +112,22 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { * @param viewer the viewer whose document is used to compute the proposals * @param offset an offset within the document for which completions should be computed * @return an array of completion proposals or null if no proposals are possible - * + * * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int) */ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { - + if (mEditor == null) { mEditor = getAndroidEditor(viewer); + if (mEditor == null) { + // This should not happen. Duck and forget. + AdtPlugin.log(IStatus.ERROR, "Editor not found during completion"); + return null; + } } UiElementNode rootUiNode = mEditor.getUiRootNode(); - + Object[] choices = null; /* An array of ElementDescriptor, or AttributeDescriptor or String or null */ String parent = ""; //$NON-NLS-1$ @@ -136,7 +143,12 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { // check to see if we can find a UiElementNode matching this XML node UiElementNode currentUiNode = rootUiNode == null ? null : rootUiNode.findXmlNode(currentNode); - + + if (currentNode == null) { + // Should not happen (an XML doc always has at least a doc node). Just give up. + return null; + } + if (currentNode.getNodeType() == Node.ELEMENT_NODE) { parent = currentNode.getNodeName(); @@ -154,7 +166,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { // We're editing attributes in an element node (either the attributes' names // or their values). choices = getChoicesForAttribute(parent, currentNode, currentUiNode, info); - + if (info.correctedPrefix != null) { wordPrefix = info.correctedPrefix; } @@ -180,7 +192,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { needTag = '<'; } } - + // get the selection length int selectionLength = 0; ISelection selection = viewer.getSelectionProvider().getSelection(); @@ -196,7 +208,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { /** * Returns the namespace prefix matching the Android Resource URI. * If no such declaration is found, returns the default "android" prefix. - * + * * @param node The current node. Must not be null. * @param nsUri The namespace URI of which the prefix is to be found, * e.g. {@link SdkConstants#NS_RESOURCES} @@ -210,9 +222,9 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { if (XmlnsAttributeDescriptor.XMLNS_URI.equals(nsUri)) { return "xmlns"; //$NON-NLS-1$ } - + HashSet visited = new HashSet(); - + String prefix = null; for (; prefix == null && node != null && @@ -230,7 +242,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { } } } - + // Use a sensible default prefix if we can't find one. // We need to make sure the prefix is not one that was declared in the scope // visited above. @@ -251,7 +263,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { *

* Example: returns the list of all elements that * can be found under , of which is one of the choices. - * + * * @return an ElementDescriptor[] or null if no valid element was found. */ private Object[] getChoicesForElement(String parent, Node current_node) { @@ -287,8 +299,8 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { * lenient as suitable for attribute values. * - AttribInfo.needTag will be non-zero if we find that the attribute completion proposal * must be double-quoted. - * @param currentUiNode - * + * @param currentUiNode + * * @return an AttributeDescriptor[] if the user is editing an attribute name. * a String[] if the user is editing an attribute value with some known values, * or null if nothing is known about the context. @@ -309,7 +321,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { } else { attrInfo.needTag = '"'; } - + if (currentUiNode != null) { // look for an UI attribute matching the current attribute name String attrName = attrInfo.name; @@ -329,7 +341,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { if (currAttrNode != null) { choices = currAttrNode.getPossibleValues(value); - + if (currAttrNode instanceof UiFlagAttributeNode) { // A "flag" can consist of several values separated by "or" (|). // If the correct prefix contains such a pipe character, we change @@ -345,7 +357,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { if (choices == null) { // fallback on the older descriptor-only based lookup. - + // in order to properly handle the special case of the name attribute in // the action tag, we need the grandparent of the action node, to know // what type of actions we need. @@ -358,7 +370,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { greatGrandParentName = greatGrandParent.getLocalName(); } } - + AndroidTargetData data = mEditor.getTargetData(); if (data != null) { choices = data.getAttributeValues(parent, attrInfo.name, greatGrandParentName); @@ -382,7 +394,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { * This means the user is editing outside of any XML element or attribute. * Simply return the list of XML elements that can be present there, based on the * parent of the current node. - * + * * @return An ElementDescriptor[] or null. */ private Object[] getChoicesForTextNode(Node currentNode) { @@ -413,7 +425,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { * - AttributeDescriptor: a possible attribute descriptor which XML name should be completed. * - String: string values to display as-is to the user. Typically those are possible * values for a given attribute. - * + * * @return The ICompletionProposal[] to display to the user. */ private ICompletionProposal[] computeProposals(int offset, Node currentNode, @@ -421,7 +433,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { boolean is_attribute, int selectionLength) { ArrayList proposals = new ArrayList(); HashMap nsUriMap = new HashMap(); - + for (Object choice : choices) { String keyword = null; String nsPrefix = null; @@ -441,7 +453,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { if (choice instanceof TextAttributeDescriptor) { tooltip = ((TextAttributeDescriptor) choice).getTooltip(); } - + // Get the namespace URI for the attribute. Note that some attributes // do not have a namespace and thus return null here. String nsUri = ((AttributeDescriptor)choice).getNamespaceUri(); @@ -455,13 +467,13 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { if (nsPrefix != null) { nsPrefix += ":"; //$NON-NLS-1$ } - + } else if (choice instanceof String) { keyword = (String) choice; } else { continue; // discard unknown choice } - + String nsKeyword = nsPrefix == null ? keyword : (nsPrefix + keyword); if (keyword.startsWith(wordPrefix) || @@ -499,7 +511,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { proposals.add(proposal); } } - + return proposals.toArray(new ICompletionProposal[proposals.size()]); } @@ -510,7 +522,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { *

* Elements can have children if the descriptor has children element descriptors * or if one of the attributes is a TextValueDescriptor. - * + * * @param descriptor An ElementDescriptor or an AttributeDescriptor * @return True if the descriptor is an ElementDescriptor that can have children or a text value */ @@ -548,7 +560,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { /** * Returns the characters which when entered by the user should * automatically trigger the presentation of possible completions. - * + * * In our case, we auto-activate on opening tags and attributes namespace. * * @return the auto activation characters for completion proposal or null @@ -569,7 +581,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { public String getErrorMessage() { return null; } - + /** * Heuristically extracts the prefix used for determining template relevance * from the viewer's document. The default implementation returns the String from @@ -578,7 +590,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { * * The part were we access the docment was extracted from * org.eclipse.jface.text.templatesTemplateCompletionProcessor and adapted to our needs. - * + * * @param viewer the viewer * @param offset offset into document * @return the prefix to consider @@ -611,7 +623,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { return ""; //$NON-NLS-1$ } } - + /** * Extracts the character at the given offset. * Returns 0 if the offset is invalid. @@ -676,7 +688,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { // text will contain the full string of the current element, // i.e. whatever is after the "<" to the current cursor String text = document.get(offset, n - offset); - + // Normalize whitespace to single spaces text = sWhitespace.matcher(text).replaceAll(" "); //$NON-NLS-1$ @@ -684,13 +696,13 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { // any whitespace. If there's nothing left, no attribute has been defined yet. // Be sure to keep any whitespace after the initial word if any, as it matters. text = sFirstElementWord.matcher(text).replaceFirst(""); //$NON-NLS-1$ - + // There MUST be space after the element name. If not, the cursor is still // defining the element name. if (!text.startsWith(" ")) { //$NON-NLS-1$ return null; } - + // Remove full attributes: // Syntax: // name = "..." quoted string with all but < and " @@ -709,7 +721,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { // merged with the previous one. // - string with an = sign, optionally followed by a quote (' or "): the user is // writing the value of the attribute. - int pos_equal = text.indexOf('='); + int pos_equal = text.indexOf('='); if (pos_equal == -1) { info.isInValue = false; info.name = text.trim(); @@ -749,7 +761,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { return node; } - + /** * Computes (if needed) and returns the root descriptor. */ @@ -758,17 +770,17 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { AndroidTargetData data = mEditor.getTargetData(); if (data != null) { IDescriptorProvider descriptorProvider = data.getDescriptorProvider(mDescriptorId); - + if (descriptorProvider != null) { mRootDescriptor = new ElementDescriptor("", descriptorProvider.getRootElementDescriptors()); } } } - + return mRootDescriptor; } - + /** * Returns the active {@link AndroidEditor} matching this source viewer. */ @@ -789,7 +801,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { return null; } - - + + }