blob: 0b2afd49fd46696a2fe430f0b6e178e12ca6dcfe [file] [log] [blame]
package org.eclipse.jst.jsf.facelet.ui.internal.contentassist;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jst.jsf.common.runtime.internal.view.model.common.Namespace;
import org.eclipse.jst.jsf.context.resolver.structureddocument.IDOMContextResolver;
import org.eclipse.jst.jsf.context.resolver.structureddocument.IStructuredDocumentContextResolverFactory;
import org.eclipse.jst.jsf.context.resolver.structureddocument.IWorkspaceContextResolver;
import org.eclipse.jst.jsf.context.structureddocument.IStructuredDocumentContext;
import org.eclipse.jst.jsf.context.structureddocument.IStructuredDocumentContextFactory;
import org.eclipse.jst.jsf.designtime.internal.view.model.ITagRegistry;
import org.eclipse.jst.jsf.facelet.core.internal.cm.FaceletDocumentFactory;
import org.eclipse.jst.jsf.facelet.core.internal.util.ViewUtil;
import org.eclipse.jst.jsf.facelet.core.internal.util.ViewUtil.PrefixEntry;
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.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.sse.ui.internal.contentassist.CustomCompletionProposal;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDocument;
import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration;
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.XMLRelevanceConstants;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
/**
* The main entry point for Facelet content assist in html files.
* @author cbateman
*
*/
public class XHTMLContentAssistProcessor extends AbstractContentAssistProcessor
{
private IFile _file;
private FaceletDocumentFactory _factory;
private final static ICompletionProposal[] NO_PROPOSALS = new ICompletionProposal[0];
@Override
public ICompletionProposal[] computeCompletionProposals(
final ITextViewer textViewer, final int documentPosition)
{
ICompletionProposal[] proposals = null;
_file = getFile(textViewer, documentPosition);
if (_file != null && shouldContribute(_file))
{
_factory = new FaceletDocumentFactory(_file.getProject());
proposals = super.computeCompletionProposals(textViewer,
documentPosition);
}
return proposals != null ? proposals : NO_PROPOSALS;
}
@SuppressWarnings("unchecked")
@Override
protected List getAvailableChildElementDeclarations(final Element parent,
final int childPosition, final int kindOfAction)
{
final Map<String, PrefixEntry> namespaces = getDocumentNamespaces(
_factory, childPosition);
final List availableChildElements = new ArrayList();
for (final Map.Entry<String, PrefixEntry> entry : namespaces.entrySet())
{
final String prefix = entry.getValue().getPrefix();
final CMDocument cmDocument = _factory.createCMDocumentForContext(
entry.getValue().getUri(), prefix);
if (cmDocument != null)
{
final Iterator it = cmDocument.getElements().iterator();
while (it.hasNext())
{
availableChildElements.add(it.next());
}
}
}
return availableChildElements;
}
@Override
protected void addAttributeValueProposals(
final ContentAssistRequest contentAssistRequest)
{
final ITextRegion textRegion = findNameRegionIfIsHTMLRoot(contentAssistRequest);
if (textRegion != null)
{
final IDOMNode node = (IDOMNode) contentAssistRequest.getNode();
final NamedNodeMap attributes = node.getAttributes();
final String attrName = node.getFirstStructuredDocumentRegion()
.getText(textRegion);
if (attrName != null)
{
final int colonPos = attrName.indexOf(':');
// must have a colon && it must not be the last char, otherwise
// there is no localName
if (colonPos > -1 && colonPos < attrName.length() - 1)
{
final String prefix = attrName.substring(0, colonPos);
if ("xmlns".equals(prefix))
{
final ITagRegistry tagRegistry = ViewUtil.getHtmlTagRegistry(_file.getProject());
if (tagRegistry != null)
{
final Set<Attr> alreadyUsed = ViewUtil.getDeclaredNamespaces(attributes);
final Collection<? extends Namespace> namespaces = tagRegistry
.getAllTagLibraries();
NAMESPACE_LOOP: for (final Namespace ns : namespaces)
{
final String possibleValue = ns.getNSUri();
if (ViewUtil.hasAttributeValue(alreadyUsed, possibleValue))
{
continue NAMESPACE_LOOP;
}
// we have an attribute of the form xmlns:X in
// the
// html root node. Here we can provide value
// proposals
// for all of the known namespaces.
final String rString = "\"" + possibleValue + "\""; //$NON-NLS-2$//$NON-NLS-1$
final int rOffset = contentAssistRequest
.getReplacementBeginPosition();
final int rLength = contentAssistRequest
.getReplacementLength();
final int cursorAfter = possibleValue.length() + 1;
final String displayString = "\"" + possibleValue + "\""; //$NON-NLS-2$//$NON-NLS-1$
final CustomCompletionProposal proposal = new CustomCompletionProposal(
rString,
rOffset,
rLength,
cursorAfter,
null,
displayString,
null,
null,
XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE);
contentAssistRequest.addProposal(proposal);
}
}
// now bail, since super only adds annoying identity completions
// for this case
return;
}
}
}
}
super.addAttributeValueProposals(contentAssistRequest);
}
private ITextRegion findNameRegionIfIsHTMLRoot(
final ContentAssistRequest contentAssistRequest)
{
final IDOMNode node = (IDOMNode) contentAssistRequest.getNode();
if (node.getNodeType() == Node.ELEMENT_NODE
&& "html".equals(node.getNodeName())
&& node.getOwnerDocument().getDocumentElement() == node)
{
// Find the attribute region and name for which this position should
// have a value proposed
final IStructuredDocumentRegion open = node
.getFirstStructuredDocumentRegion();
final ITextRegionList openRegions = open.getRegions();
int i = openRegions.indexOf(contentAssistRequest.getRegion());
if (i < 0)
{
return null;
}
ITextRegion nameRegion = null;
while (i >= 0)
{
nameRegion = openRegions.get(i--);
if (nameRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME)
{
break;
}
}
return nameRegion;
}
return null;
}
@SuppressWarnings("unchecked")
private Map<String, PrefixEntry> getDocumentNamespaces(
final FaceletDocumentFactory factory, final int offset)
{
final IStructuredDocumentContext context = IStructuredDocumentContextFactory.INSTANCE
.getContext(fTextViewer, offset);
Document doc = null;
if (context != null)
{
final IDOMContextResolver domContextResolver = IStructuredDocumentContextResolverFactory.INSTANCE
.getDOMContextResolver(context);
doc = domContextResolver.getDOMDocument();
if (doc == null)
{
return Collections.EMPTY_MAP;
}
}
return ViewUtil.getDocumentNamespaces(doc);
}
@Override
protected CMElementDeclaration getCMElementDeclaration(final Node node)
{
if (node.getNodeType() == Node.ELEMENT_NODE)
{
if (node.getPrefix() != null)
{
final Element element = (Element) node;
final CMElementDeclaration elementDecl = _factory
.createCMElementDeclaration(element);
if (elementDecl != null)
{
return elementDecl;
}
}
}
return null;
// return super.getCMElementDeclaration(node);
}
private boolean shouldContribute(final IFile file)
{
return ViewUtil.isFaceletVDLFile(file);
}
private IFile getFile(final ITextViewer textViewer,
final int documentPosition)
{
final IStructuredDocumentContext context = IStructuredDocumentContextFactory.INSTANCE
.getContext(textViewer, documentPosition);
if (context != null)
{
final IWorkspaceContextResolver resolver = IStructuredDocumentContextResolverFactory.INSTANCE
.getWorkspaceContextResolver(context);
if (resolver != null)
{
final IResource resource = resolver.getResource();
if (resource != null &&
resource.getType() == IResource.FILE)
{
return (IFile) resource;
}
}
}
return null;
}
}