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 * */ @SuppressWarnings("deprecation") 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; } }