blob: 48689b04e0ea33e663fd385fc980581c5bf76377 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008 Standards for Technology in Automotive Retail and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* David Carver - STAR - bug 213849 - initial API and implementation
* David Carver - STAR - bug 230958 - refactored to fix bug with getting
* the DOM Document for the current editor
* David Carver - STAR - bug 240170 - refactored code to help with narrowing of
* results and easier maintenance.
*
*******************************************************************************/
package org.eclipse.wst.xsl.ui.internal.contentassist;
import java.util.ArrayList;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.ui.internal.IReleasable;
import org.eclipse.wst.sse.ui.internal.contentassist.ContentAssistUtils;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
import org.eclipse.wst.xml.ui.internal.contentassist.AbstractContentAssistProcessor;
import org.eclipse.wst.xml.ui.internal.contentassist.ContentAssistRequest;
import org.eclipse.wst.xml.ui.internal.contentassist.XMLContentAssistProcessor;
import org.eclipse.wst.xsl.core.XSLCore;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
/**
* The XSL Content Assist Processor provides content assistance for various
* attributes values within the XSL Editor. This includes support for xpaths on
* select statements as well as on test and match attributes.
*
* @author David Carver
* @since 1.0
*/
public class XSLContentAssistProcessor implements IContentAssistProcessor,
IReleasable {
/**
* Retrieve all global variables in the stylesheet.
*/
private String errorMessage = "";
private ITextViewer textViewer = null;
/**
* The XSL Content Assist Processor handles XSL specific functionality for
* content assistance. It leverages several XPath selection variables to
* help with the selection of elements and template names.
*
*/
public XSLContentAssistProcessor() {
super();
}
/**
* CONTENT ASSIST STARTS HERE
*
* Return a list of proposed code completions based on the specified
* location within the document that corresponds to the current cursor
* position within the text-editor control.
*
* @param textViewer
* @param documentPosition
* - the cursor location within the document
*
* @return an array of ICompletionProposal
*/
public ICompletionProposal[] computeCompletionProposals(
ITextViewer textViewer, int documentPosition) {
setErrorMessage(null);
this.textViewer = textViewer;
IndexedRegion treeNode = ContentAssistUtils.getNodeAt(textViewer,
documentPosition);
Node node = (Node) treeNode;
while ((node != null) && (node.getNodeType() == Node.TEXT_NODE)
&& (node.getParentNode() != null)) {
node = node.getParentNode();
}
IDOMNode xmlNode = (IDOMNode) node;
IStructuredDocumentRegion sdRegion = getStructuredDocumentRegion(documentPosition);
ITextRegion completionRegion = getCompletionRegion(documentPosition,
node);
AbstractContentAssistProcessor processor = new XMLContentAssistProcessor();
ICompletionProposal proposals[] = processor.computeCompletionProposals(
textViewer, documentPosition);
String matchString = getXPathMatchString(sdRegion, completionRegion,
documentPosition);
ICompletionProposal[] additionalProposals = null;
if (!XSLCore.isXSLNamespace(xmlNode)) {
additionalProposals = new ElementContentAssistRequest(xmlNode, xmlNode.getParentNode(), sdRegion, completionRegion, documentPosition, 0, matchString, textViewer).getCompletionProposals();
}
ICompletionProposal[] xslProposals = null;
if (XSLCore.isXSLNamespace(xmlNode)) {
xslProposals = getXSLProposals(textViewer, documentPosition, xmlNode,
sdRegion, completionRegion, proposals, matchString);
}
ArrayList<ICompletionProposal> proposalList = new ArrayList();
addProposals(proposals, proposalList);
addProposals(additionalProposals, proposalList);
addProposals(xslProposals, proposalList);
ICompletionProposal[] combinedProposals = new ICompletionProposal[proposalList.size()];
proposalList.toArray(combinedProposals);
return combinedProposals;
}
private void addProposals(ICompletionProposal[] proposals,
ArrayList<ICompletionProposal> proposalList) {
if (proposals != null) {
for (int cnt = 0; cnt < proposals.length; cnt++) {
proposalList.add(proposals[cnt]);
}
}
}
protected ICompletionProposal[] getXSLProposals(ITextViewer textViewer,
int documentPosition, IDOMNode xmlNode,
IStructuredDocumentRegion sdRegion, ITextRegion completionRegion,
ICompletionProposal[] proposals, String matchString) {
XSLContentAssistRequestFactory requestFactory = new XSLContentAssistRequestFactory();
ICompletionProposal[] xslProposals = null;
ContentAssistRequest contentAssistRequest = requestFactory
.getContentAssistRequest(textViewer, documentPosition, xmlNode,
sdRegion, completionRegion, proposals, matchString);
xslProposals = contentAssistRequest.getCompletionProposals();
return xslProposals;
}
/**
* StructuredTextViewer must be set before using this.
*
* @param pos
* @return
*/
public IStructuredDocumentRegion getStructuredDocumentRegion(int pos) {
return ContentAssistUtils.getStructuredDocumentRegion(textViewer, pos);
}
/**
* Return the region whose content's require completion. This is something
* of a misnomer as sometimes the user wants to be prompted for contents of
* a non-existent ITextRegion, such as for enumerated attribute values
* following an '=' sign.
*
* Copied from AbstractContentAssist Processor.
*/
protected ITextRegion getCompletionRegion(int documentPosition, Node domnode) {
if (domnode == null) {
return null;
}
ITextRegion region = null;
int offset = documentPosition;
IStructuredDocumentRegion flatNode = null;
IDOMNode node = (IDOMNode) domnode;
if (node.getNodeType() == Node.DOCUMENT_NODE) {
if (node.getStructuredDocument().getLength() == 0) {
return null;
}
ITextRegion result = node.getStructuredDocument()
.getRegionAtCharacterOffset(offset)
.getRegionAtCharacterOffset(offset);
while (result == null) {
offset--;
result = node.getStructuredDocument()
.getRegionAtCharacterOffset(offset)
.getRegionAtCharacterOffset(offset);
}
return result;
}
IStructuredDocumentRegion startTag = node
.getStartStructuredDocumentRegion();
IStructuredDocumentRegion endTag = node
.getEndStructuredDocumentRegion();
if ((startTag != null) && (startTag.getStartOffset() <= offset)
&& (offset < startTag.getStartOffset() + startTag.getLength())) {
flatNode = startTag;
} else if ((endTag != null) && (endTag.getStartOffset() <= offset)
&& (offset < endTag.getStartOffset() + endTag.getLength())) {
flatNode = endTag;
}
if (flatNode != null) {
region = getCompletionRegion(offset, flatNode);
} else {
flatNode = node.getStructuredDocument().getRegionAtCharacterOffset(
offset);
if ((flatNode.getStartOffset() <= documentPosition)
&& (flatNode.getEndOffset() >= documentPosition)) {
if ((offset == flatNode.getStartOffset())
&& (flatNode.getPrevious() != null)
&& (((flatNode
.getRegionAtCharacterOffset(documentPosition) != null) && (flatNode
.getRegionAtCharacterOffset(documentPosition)
.getType() != DOMRegionContext.XML_CONTENT))
|| (flatNode.getPrevious().getLastRegion()
.getType() == DOMRegionContext.XML_TAG_OPEN) || (flatNode
.getPrevious().getLastRegion().getType() == DOMRegionContext.XML_END_TAG_OPEN))) {
region = flatNode.getPrevious().getLastRegion();
} else if (flatNode.getEndOffset() == documentPosition) {
region = flatNode.getLastRegion();
} else {
region = flatNode.getFirstRegion();
}
} else {
region = flatNode.getLastRegion();
}
}
return region;
}
protected ITextRegion getCompletionRegion(int offset,
IStructuredDocumentRegion sdRegion) {
ITextRegion region = sdRegion.getRegionAtCharacterOffset(offset);
if (region == null) {
return null;
}
if (sdRegion.getStartOffset(region) == offset) {
// The offset is at the beginning of the region
if ((sdRegion.getStartOffset(region) == sdRegion.getStartOffset())
&& (sdRegion.getPrevious() != null)
&& (!sdRegion.getPrevious().isEnded())) {
region = sdRegion.getPrevious().getRegionAtCharacterOffset(
offset - 1);
} else {
// Is there no separating whitespace from the previous region?
// If not,
// then that region is the important one
ITextRegion previousRegion = sdRegion
.getRegionAtCharacterOffset(offset - 1);
if ((previousRegion != null)
&& (previousRegion != region)
&& (previousRegion.getTextLength() == previousRegion
.getLength())) {
region = previousRegion;
}
}
} else {
// The offset is NOT at the beginning of the region
if (offset > sdRegion.getStartOffset(region)
+ region.getTextLength()) {
// Is the offset within the whitespace after the text in this
// region?
// If so, use the next region
ITextRegion nextRegion = sdRegion
.getRegionAtCharacterOffset(sdRegion
.getStartOffset(region)
+ region.getLength());
if (nextRegion != null) {
region = nextRegion;
}
} else {
// Is the offset within the important text for this region?
// If so, then we've already got the right one.
}
}
// valid WHITE_SPACE region handler (#179924)
if ((region != null)
&& (region.getType() == DOMRegionContext.WHITE_SPACE)) {
ITextRegion previousRegion = sdRegion
.getRegionAtCharacterOffset(sdRegion.getStartOffset(region) - 1);
if (previousRegion != null) {
region = previousRegion;
}
}
return region;
}
protected String getXPathMatchString(IStructuredDocumentRegion parent,
ITextRegion aRegion, int offset) {
if ((aRegion == null) || isCloseRegion(aRegion)) {
return ""; //$NON-NLS-1$
}
String matchString = null;
String regionType = aRegion.getType();
if ((regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS)
|| (regionType == DOMRegionContext.XML_TAG_OPEN)
|| (offset > parent.getStartOffset(aRegion)
+ aRegion.getTextLength())) {
matchString = ""; //$NON-NLS-1$
} else if (regionType == DOMRegionContext.XML_CONTENT) {
matchString = ""; //$NON-NLS-1$
} else {
if ((parent.getText(aRegion).length() > 0)
&& (parent.getStartOffset(aRegion) < offset)) {
matchString = parent.getText(aRegion).substring(0,
offset - parent.getStartOffset(aRegion));
} else {
matchString = ""; //$NON-NLS-1$
}
}
if (matchString.startsWith("\"")) {
matchString = matchString.substring(1);
}
return matchString;
}
protected boolean isCloseRegion(ITextRegion region) {
String type = region.getType();
return ((type == DOMRegionContext.XML_PI_CLOSE)
|| (type == DOMRegionContext.XML_TAG_CLOSE)
|| (type == DOMRegionContext.XML_EMPTY_TAG_CLOSE)
|| (type == DOMRegionContext.XML_CDATA_CLOSE)
|| (type == DOMRegionContext.XML_COMMENT_CLOSE)
|| (type == DOMRegionContext.XML_ATTLIST_DECL_CLOSE)
|| (type == DOMRegionContext.XML_ELEMENT_DECL_CLOSE)
|| (type == DOMRegionContext.XML_DOCTYPE_DECLARATION_CLOSE) || (type == DOMRegionContext.XML_DECLARATION_CLOSE));
}
/**
* (non-Javadoc)
*
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeContextInformation(org.eclipse.jface.text.ITextViewer,
* int)
*/
public IContextInformation[] computeContextInformation(ITextViewer viewer,
int offset) {
return null;
}
/**
* Returns the characters which when entered by the user should
* automatically trigger the presentation of possible completions.
*
* the auto activation characters for completion proposal or
* <code>null</code> if no auto activation is desired
* @return an array of activation characters
*/
public char[] getCompletionProposalAutoActivationCharacters() {
// TODO: Currently these are hard coded..need to move to preferences.
char[] completionProposals = { '"', '\'', ':', '[', '{', '<' };
return completionProposals;
}
/**
* (non-Javadoc)
*
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationAutoActivationCharacters()
*/
public char[] getContextInformationAutoActivationCharacters() {
// TODO Auto-generated method stub
return null;
}
/**
* (non-Javadoc)
*
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationValidator()
*/
public IContextInformationValidator getContextInformationValidator() {
return null;
}
/**
* (non-Javadoc)
*
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getErrorMessage()
*/
public String getErrorMessage() {
return errorMessage;
}
/**
* (non-Javadoc)
*
* @see org.eclipse.wst.sse.ui.internal.IReleasable#release()
*/
public void release() {
}
/**
* (non-Javadoc)
*
* @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent event) {
// TODO Auto-generated method stub
}
/**
* Sets the error message for why content assistance didn't complete.
*
* @param errorMessage
*/
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
protected boolean hasAttributeAtTextRegion(String attrName,
NamedNodeMap nodeMap, ITextRegion aRegion) {
IDOMAttr attrNode = (IDOMAttr) nodeMap.getNamedItem(attrName);
return attrNode != null
&& attrNode.getValueRegion().getStart() == aRegion.getStart();
}
protected IDOMAttr getAttributeAtTextRegion(String attrName,
NamedNodeMap nodeMap, ITextRegion aRegion) {
IDOMAttr node = (IDOMAttr) nodeMap.getNamedItem(attrName);
if (node != null
&& node.getValueRegion().getStart() == aRegion.getStart()) {
return node;
}
return null;
}
}