Methods Summary |
---|
public org.eclipse.jface.text.contentassist.ICompletionProposal[] | computeCompletionProposals(org.eclipse.jface.text.ITextViewer viewer, int offset)Returns a list of completion proposals based on the
specified location within the document that corresponds
to the current cursor position within the text viewer.
if (mEditor == null) {
mEditor = getAndroidEditor(viewer);
}
UiElementNode rootUiNode = mEditor.getUiRootNode();
Object[] choices = null; /* An array of ElementDescriptor, or AttributeDescriptor
or String or null */
String parent = ""; //$NON-NLS-1$
String wordPrefix = extractElementPrefix(viewer, offset);
char needTag = 0;
boolean isElement = false;
boolean isAttribute = false;
Node currentNode = getNode(viewer, offset);
if (currentNode == null)
return null;
// check to see if we can find a UiElementNode matching this XML node
UiElementNode currentUiNode =
rootUiNode == null ? null : rootUiNode.findXmlNode(currentNode);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
parent = currentNode.getNodeName();
if (wordPrefix.equals(parent)) {
// We are still editing the element's tag name, not the attributes
// (the element's tag name may not even be complete)
isElement = true;
choices = getChoicesForElement(parent, currentNode);
} else {
// We're not editing the current node name, so we might be editing its
// attributes instead...
isAttribute = true;
AttribInfo info = parseAttributeInfo(viewer, offset);
if (info != null) {
// 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;
}
needTag = info.needTag;
}
}
} else if (currentNode.getNodeType() == Node.TEXT_NODE) {
isElement = true;
// Examine the parent of the text node.
choices = getChoicesForTextNode(currentNode);
}
// Abort if we can't recognize the context or there are no completion choices
if (choices == null || choices.length == 0) return null;
if (isElement) {
// If we found some suggestions, do we need to add an opening "<" bracket
// for the element? We don't if the cursor is right after "<" or "</".
// Per XML Spec, there's no whitespace between "<" or "</" and the tag name.
int offset2 = offset - wordPrefix.length() - 1;
int c1 = extractChar(viewer, offset2);
if (!((c1 == '<") || (c1 == '/" && extractChar(viewer, offset2 - 1) == '<"))) {
needTag = '<";
}
}
// get the selection length
int selectionLength = 0;
ISelection selection = viewer.getSelectionProvider().getSelection();
if (selection instanceof TextSelection) {
TextSelection textSelection = (TextSelection)selection;
selectionLength = textSelection.getLength();
}
return computeProposals(offset, currentNode, choices, wordPrefix, needTag,
isAttribute, selectionLength);
|
public org.eclipse.jface.text.contentassist.IContextInformation[] | computeContextInformation(org.eclipse.jface.text.ITextViewer viewer, int offset)
return null;
|
private org.eclipse.jface.text.contentassist.ICompletionProposal[] | computeProposals(int offset, org.w3c.dom.Node currentNode, java.lang.Object[] choices, java.lang.String wordPrefix, char need_tag, boolean is_attribute, int selectionLength)Given a list of choices found, generates the proposals to be displayed to the user.
Choices is an object array. Items of the array can be:
- ElementDescriptor: a possible element descriptor which XML name should be completed.
- 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.
ArrayList<CompletionProposal> proposals = new ArrayList<CompletionProposal>();
HashMap<String, String> nsUriMap = new HashMap<String, String>();
for (Object choice : choices) {
String keyword = null;
String nsPrefix = null;
Image icon = null;
String tooltip = null;
if (choice instanceof ElementDescriptor) {
keyword = ((ElementDescriptor)choice).getXmlName();
icon = ((ElementDescriptor)choice).getIcon();
tooltip = DescriptorsUtils.formatTooltip(((ElementDescriptor)choice).getTooltip());
} else if (choice instanceof TextValueDescriptor) {
continue; // Value nodes are not part of the completion choices
} else if (choice instanceof SeparatorAttributeDescriptor) {
continue; // not real attribute descriptors
} else if (choice instanceof AttributeDescriptor) {
keyword = ((AttributeDescriptor)choice).getXmlLocalName();
icon = ((AttributeDescriptor)choice).getIcon();
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();
if (nsUri != null) {
nsPrefix = nsUriMap.get(nsUri);
if (nsPrefix == null) {
nsPrefix = lookupNamespacePrefix(currentNode, nsUri);
nsUriMap.put(nsUri, nsPrefix);
}
}
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) ||
(nsPrefix != null && keyword.startsWith(nsPrefix)) ||
(nsPrefix != null && nsKeyword.startsWith(wordPrefix))) {
if (nsPrefix != null) {
keyword = nsPrefix + keyword;
}
String end_tag = ""; //$NON-NLS-1$
if (need_tag != 0) {
if (need_tag == '"") {
keyword = need_tag + keyword;
end_tag = String.valueOf(need_tag);
} else if (need_tag == '<") {
if (elementCanHaveChildren(choice)) {
end_tag = String.format("></%1$s>", keyword); //$NON-NLS-1$
keyword = need_tag + keyword;
} else {
keyword = need_tag + keyword;
end_tag = "/>"; //$NON-NLS-1$
}
}
}
CompletionProposal proposal = new CompletionProposal(
keyword + end_tag, // String replacementString
offset - wordPrefix.length(), // int replacementOffset
wordPrefix.length() + selectionLength, // int replacementLength
keyword.length(), // int cursorPosition (rel. to rplcmntOffset)
icon, // Image image
null, // String displayString
null, // IContextInformation contextInformation
tooltip // String additionalProposalInfo
);
proposals.add(proposal);
}
}
return proposals.toArray(new ICompletionProposal[proposals.size()]);
|
private boolean | elementCanHaveChildren(java.lang.Object descriptor)Indicates whether this descriptor describes an element that can potentially
have children (either sub-elements or text value). If an element can have children,
we want to explicitly write an opening and a separate closing tag.
Elements can have children if the descriptor has children element descriptors
or if one of the attributes is a TextValueDescriptor.
if (descriptor instanceof ElementDescriptor) {
ElementDescriptor desc = (ElementDescriptor) descriptor;
if (desc.hasChildren()) {
return true;
}
for (AttributeDescriptor attr_desc : desc.getAttributes()) {
if (attr_desc instanceof TextValueDescriptor) {
return true;
}
}
}
return false;
|
protected char | extractChar(org.eclipse.jface.text.ITextViewer viewer, int offset)Extracts the character at the given offset.
Returns 0 if the offset is invalid.
IDocument document = viewer.getDocument();
if (offset > document.getLength()) return 0;
try {
return document.getChar(offset);
} catch (BadLocationException e) {
return 0;
}
|
protected java.lang.String | extractElementPrefix(org.eclipse.jface.text.ITextViewer viewer, int offset)Heuristically extracts the prefix used for determining template relevance
from the viewer's document. The default implementation returns the String from
offset backwards that forms a potential XML element name, attribute name or
attribute value.
The part were we access the docment was extracted from
org.eclipse.jface.text.templatesTemplateCompletionProcessor and adapted to our needs.
int i = offset;
IDocument document = viewer.getDocument();
if (i > document.getLength()) return ""; //$NON-NLS-1$
try {
for (; i > 0; --i) {
char ch = document.getChar(i - 1);
// We want all characters that can form a valid:
// - element name, e.g. anything that is a valid Java class/variable literal.
// - attribute name, including : for the namespace
// - attribute value.
// Before we were inclusive and that made the code fragile. So now we're
// going to be exclusive: take everything till we get one of:
// - any form of whitespace
// - any xml separator, e.g. < > ' " and =
if (Character.isWhitespace(ch) ||
ch == '<" || ch == '>" || ch == '\'" || ch == '"" || ch == '=") {
break;
}
}
return document.get(i, offset - i);
} catch (BadLocationException e) {
return ""; //$NON-NLS-1$
}
|
private AndroidEditor | getAndroidEditor(org.eclipse.jface.text.ITextViewer viewer)Returns the active {@link AndroidEditor} matching this source viewer.
IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (wwin != null) {
IWorkbenchPage page = wwin.getActivePage();
if (page != null) {
IEditorPart editor = page.getActiveEditor();
if (editor instanceof AndroidEditor) {
ISourceViewer ssviewer = ((AndroidEditor) editor).getStructuredSourceViewer();
if (ssviewer == viewer) {
return (AndroidEditor) editor;
}
}
}
}
return null;
|
private java.lang.Object[] | getChoicesForAttribute(java.lang.String parent, org.w3c.dom.Node currentNode, com.android.ide.eclipse.editors.uimodel.UiElementNode currentUiNode, com.android.ide.eclipse.editors.AndroidContentAssist$AttribInfo attrInfo)Gets the choices when the user is editing an XML attribute.
In input, attrInfo contains details on the analyzed context, namely whether the
user is editing an attribute value (isInValue) or an attribute name.
In output, attrInfo also contains two possible new values (this is a hack to circumvent
the lack of out-parameters in Java):
- AttribInfo.correctedPrefix if the user has been editing an attribute value and it has
been detected that what the user typed is different from what extractElementPrefix()
predicted. This happens because extractElementPrefix() stops when a character that
cannot be an element name appears whereas parseAttributeInfo() uses a grammar more
lenient as suitable for attribute values.
- AttribInfo.needTag will be non-zero if we find that the attribute completion proposal
must be double-quoted.
Object[] choices = null;
if (attrInfo.isInValue) {
// Editing an attribute's value... Get the attribute name and then the
// possible choice for the tuple(parent,attribute)
String value = attrInfo.value;
if (value.startsWith("'") || value.startsWith("\"")) { //$NON-NLS-1$ //$NON-NLS-2$
value = value.substring(1);
// The prefix that was found at the beginning only scan for characters
// valid of tag name. We now know the real prefix for this attribute's
// value, which is needed to generate the completion choices below.
attrInfo.correctedPrefix = value;
} else {
attrInfo.needTag = '"";
}
if (currentUiNode != null) {
// look for an UI attribute matching the current attribute name
String attrName = attrInfo.name;
// remove any namespace prefix from the attribute name
int pos = attrName.indexOf(':");
if (pos >= 0) {
attrName = attrName.substring(pos + 1);
}
UiAttributeNode currAttrNode = null;
for (UiAttributeNode attrNode : currentUiNode.getUiAttributes()) {
if (attrNode.getDescriptor().getXmlLocalName().equals(attrName)) {
currAttrNode = attrNode;
break;
}
}
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
// it so that only the currently edited value is completed.
pos = value.indexOf('|");
if (pos >= 0) {
attrInfo.correctedPrefix = value = value.substring(pos + 1);
attrInfo.needTag = 0;
}
}
}
}
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.
// e.g. activity -> intent-filter -> action[@name]
String greatGrandParentName = null;
Node grandParent = currentNode.getParentNode();
if (grandParent != null) {
Node greatGrandParent = grandParent.getParentNode();
if (greatGrandParent != null) {
greatGrandParentName = greatGrandParent.getLocalName();
}
}
AndroidTargetData data = mEditor.getTargetData();
if (data != null) {
choices = data.getAttributeValues(parent, attrInfo.name, greatGrandParentName);
}
}
} else {
// Editing an attribute's name... Get attributes valid for the parent node.
if (currentUiNode != null) {
choices = currentUiNode.getAttributeDescriptors();
} else {
ElementDescriptor parent_desc = getDescriptor(parent);
choices = parent_desc.getAttributes();
}
}
return choices;
|
private java.lang.Object[] | getChoicesForElement(java.lang.String parent, org.w3c.dom.Node current_node)Gets the choices when the user is editing the name of an XML element.
The user is editing the name of an element (the "parent").
Find the grand-parent and if one is found, return its children element list.
The name which is being edited should be one of those.
Example: returns the list of all elements that
can be found under , of which is one of the choices.
ElementDescriptor grandparent = null;
if (current_node.getParentNode().getNodeType() == Node.ELEMENT_NODE) {
grandparent = getDescriptor(current_node.getParentNode().getNodeName());
} else if (current_node.getParentNode().getNodeType() == Node.DOCUMENT_NODE) {
grandparent = getRootDescriptor();
}
if (grandparent != null) {
for (ElementDescriptor e : grandparent.getChildren()) {
if (e.getXmlName().startsWith(parent)) {
return grandparent.getChildren();
}
}
}
return null;
|
private java.lang.Object[] | getChoicesForTextNode(org.w3c.dom.Node currentNode)Gets the choices when the user is editing an XML text node.
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.
Object[] choices = null;
String parent;
Node parent_node = currentNode.getParentNode();
if (parent_node.getNodeType() == Node.ELEMENT_NODE) {
// We're editing a text node which parent is an element node. Limit
// content assist to elements valid for the parent.
parent = parent_node.getNodeName();
ElementDescriptor desc = getDescriptor(parent);
if (desc != null) {
choices = desc.getChildren();
}
} else if (parent_node.getNodeType() == Node.DOCUMENT_NODE) {
// We're editing a text node at the first level (i.e. root node).
// Limit content assist to the only valid root elements.
choices = getRootDescriptor().getChildren();
}
return choices;
|
public char[] | getCompletionProposalAutoActivationCharacters()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 new char[]{ '<", ':", '=" };
|
public char[] | getContextInformationAutoActivationCharacters()
return null;
|
public org.eclipse.jface.text.contentassist.IContextInformationValidator | getContextInformationValidator()
return null;
|
private com.android.ide.eclipse.editors.descriptors.ElementDescriptor | getDescriptor(java.lang.String nodeName)Returns the element descriptor matching a given XML node name or null if it can't be
found.
This is simplistic; ideally we should consider the parent's chain to make sure we
can differentiate between different hierarchy trees. Right now the first match found
is returned.
return getRootDescriptor().findChildrenDescriptor(nodeName, true /* recursive */);
|
public java.lang.String | getErrorMessage()
return null;
|
protected org.w3c.dom.Node | getNode(org.eclipse.jface.text.ITextViewer viewer, int offset)Returns the XML DOM node corresponding to the given offset of the given document.
Node node = null;
try {
IModelManager mm = StructuredModelManager.getModelManager();
if (mm != null) {
IStructuredModel model = mm.getExistingModelForRead(viewer.getDocument());
if (model != null) {
for(; offset >= 0 && node == null; --offset) {
node = (Node) model.getIndexedRegion(offset);
}
}
}
} catch (Exception e) {
// Ignore exceptions.
}
return node;
|
private com.android.ide.eclipse.editors.descriptors.ElementDescriptor | getRootDescriptor()Computes (if needed) and returns the root descriptor.
if (mRootDescriptor == null) {
AndroidTargetData data = mEditor.getTargetData();
if (data != null) {
IDescriptorProvider descriptorProvider = data.getDescriptorProvider(mDescriptorId);
if (descriptorProvider != null) {
mRootDescriptor = new ElementDescriptor("",
descriptorProvider.getRootElementDescriptors());
}
}
}
return mRootDescriptor;
|
private java.lang.String | lookupNamespacePrefix(org.w3c.dom.Node node, java.lang.String nsUri)Returns the namespace prefix matching the Android Resource URI.
If no such declaration is found, returns the default "android" prefix.
// Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java
// The following emulates this:
// String prefix = node.lookupPrefix(SdkConstants.NS_RESOURCES);
if (XmlnsAttributeDescriptor.XMLNS_URI.equals(nsUri)) {
return "xmlns"; //$NON-NLS-1$
}
HashSet<String> visited = new HashSet<String>();
String prefix = null;
for (; prefix == null &&
node != null &&
node.getNodeType() == Node.ELEMENT_NODE;
node = node.getParentNode()) {
NamedNodeMap attrs = node.getAttributes();
for (int n = attrs.getLength() - 1; n >= 0; --n) {
Node attr = attrs.item(n);
if ("xmlns".equals(attr.getPrefix())) { //$NON-NLS-1$
String uri = attr.getNodeValue();
if (SdkConstants.NS_RESOURCES.equals(uri)) {
return attr.getLocalName();
}
visited.add(uri);
}
}
}
// 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.
prefix = SdkConstants.NS_RESOURCES.equals(nsUri) ? "android" : "ns"; //$NON-NLS-1$ //$NON-NLS-2$
String base = prefix;
for (int i = 1; visited.contains(prefix); i++) {
prefix = base + Integer.toString(i);
}
return prefix;
|
private com.android.ide.eclipse.editors.AndroidContentAssist$AttribInfo | parseAttributeInfo(org.eclipse.jface.text.ITextViewer viewer, int offset)Try to guess if the cursor is editing an element's name or an attribute following an
element. If it's an attribute, try to find if an attribute name is being defined or
its value.
This is currently *only* called when we know the cursor is after a complete element
tag name, so it should never return null.
Reference for XML syntax: http://www.w3.org/TR/2006/REC-xml-20060816/#sec-starttags
AttribInfo info = new AttribInfo();
IDocument document = viewer.getDocument();
int n = document.getLength();
if (offset <= n) {
try {
n = offset;
for (;offset > 0; --offset) {
char ch = document.getChar(offset - 1);
if (ch == '<") break;
}
// 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$
// Remove the leading element name. By spec, it must be after the < without
// 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 "
// or:
// name = '...' quoted string with all but < and '
String temp;
do {
temp = text;
text = sFirstAttribute.matcher(temp).replaceFirst(""); //$NON-NLS-1$
} while(!temp.equals(text));
// Now we're left with 3 cases:
// - nothing: either there is no attribute definition or the cursor located after
// a completed attribute definition.
// - a string with no =: the user is writing an attribute name. This case can be
// 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('=");
if (pos_equal == -1) {
info.isInValue = false;
info.name = text.trim();
} else {
info.isInValue = true;
info.name = text.substring(0, pos_equal).trim();
info.value = text.substring(pos_equal + 1).trim();
}
return info;
} catch (BadLocationException e) {
// pass
}
}
return null;
|