/*******************************************************************************
* Copyright (c) 2008 Ralf Ebert
* 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:
* Ralf Ebert - initial API and implementation
*******************************************************************************/
package com.swtxml.ide;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.swt.widgets.Layout;
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.xml.core.internal.provisional.document.IDOMNode;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
import org.eclipse.wst.xml.ui.internal.contentassist.ContentAssistRequest;
import org.eclipse.wst.xml.ui.internal.contentassist.XMLContentAssistProcessor;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import com.swtxml.adapter.IAdaptable;
import com.swtxml.definition.IAttributeDefinition;
import com.swtxml.definition.INamespaceDefinition;
import com.swtxml.definition.ITagDefinition;
import com.swtxml.definition.ITagScope;
import com.swtxml.extensions.ExtensionsNamespaceResolver;
import com.swtxml.swt.types.LayoutType;
import com.swtxml.util.context.Context;
import com.swtxml.util.parser.Strictness;
import com.swtxml.util.proposals.Match;
import com.swtxml.util.types.IContentAssistable;
import com.swtxml.util.types.IType;
@SuppressWarnings("restriction")
public class SwtXmlContentAssistProcessor extends XMLContentAssistProcessor {
public SwtXmlContentAssistProcessor() {
super();
}
@Override
protected void addTagNameProposals(final ContentAssistRequest contentAssistRequest,
int childPosition) {
Match match = createMatch(contentAssistRequest);
// TODO: cache this
DocumentNamespaceBrowser namespaces = getNamespaceBrowser(contentAssistRequest);
Node parentNode = contentAssistRequest.getNode().getParentNode();
INamespaceDefinition parentNamespace = namespaces.getByURI(parentNode.getNamespaceURI());
if (parentNamespace == null) {
return;
}
ITagDefinition parentTag = parentNamespace.getTag(parentNode.getLocalName());
if (parentTag == null) {
return;
}
// TODO: there should be a non-ide API for find valid tags
List<String> filteredTags = new ArrayList<String>();
for (INamespaceDefinition namespace : namespaces.getAllDefinitions()) {
for (String tagname : namespace.getTagNames()) {
ITagDefinition tag = namespace.getTag(tagname);
boolean allowedInContext = !(tag instanceof ITagScope)
|| ((ITagScope) tag).isAllowedIn(parentTag);
if (allowedInContext) {
filteredTags.add(namespaces.getPrefix(namespace) + tag.getName());
}
}
}
if (contentAssistRequest.getNode() instanceof Text) {
match = match.insertAroundMatch("", "/>");
}
addProposals(contentAssistRequest, match.propose(filteredTags));
super.addTagNameProposals(contentAssistRequest, childPosition);
}
private DocumentNamespaceBrowser getNamespaceBrowser(
final ContentAssistRequest contentAssistRequest) {
return new DocumentNamespaceBrowser(contentAssistRequest.getNode().getOwnerDocument());
}
@Override
protected void addAttributeNameProposals(final ContentAssistRequest contentAssistRequest) {
// TODO: cache this
DocumentNamespaceBrowser namespaces = getNamespaceBrowser(contentAssistRequest);
Node node = contentAssistRequest.getNode();
INamespaceDefinition namespace = getNamespace(node);
if (namespace == null) {
return;
}
ITagDefinition tag = namespace.getTag(node.getLocalName());
if (tag == null) {
return;
}
Match match = createMatch(contentAssistRequest);
match = match.insertAroundMatch("", "=\"\"");
List<Match> proposals = match.propose(tag.getAttributeNames());
// TODO: there should be a non-ide API for finding valid attribute names
List<String> foreignAttributeNames = new ArrayList<String>();
for (INamespaceDefinition ns : namespaces.getAllDefinitions()) {
for (String foreignAttributeName : ns.getForeignAttributeNames()) {
IAttributeDefinition foreignAttribute = ns
.getForeignAttribute(foreignAttributeName);
if (!(foreignAttribute instanceof ITagScope)
|| ((ITagScope) foreignAttribute).isAllowedIn(tag)) {
foreignAttributeNames
.add(namespaces.getPrefix(ns) + foreignAttribute.getName());
}
}
}
proposals.addAll(match.propose(foreignAttributeNames));
// TODO: Create match API suitable for xxx=""
for (Match proposal : proposals) {
proposal.moveCursor(2);
}
addProposals(contentAssistRequest, proposals);
}
private INamespaceDefinition getNamespace(Node node) {
return new ExtensionsNamespaceResolver().resolveNamespace(node.getNamespaceURI());
}
@Override
protected void addAttributeValueProposals(final ContentAssistRequest contentAssistRequest) {
IDOMNode node = (IDOMNode) contentAssistRequest.getNode();
INamespaceDefinition namespace = getNamespace(node);
if (namespace == null) {
return;
}
ITagDefinition tag = namespace.getTag(node.getLocalName());
if (tag == null) {
return;
}
// Find the attribute region and name for which this position should
// have a value proposed
IStructuredDocumentRegion open = node.getFirstStructuredDocumentRegion();
ITextRegionList openRegions = open.getRegions();
int i = openRegions.indexOf(contentAssistRequest.getRegion());
if (i < 0) {
return;
}
ITextRegion nameRegion = null;
while (i >= 0) {
nameRegion = openRegions.get(i--);
if (nameRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) {
break;
}
}
// the name region is REQUIRED to do anything useful
if (nameRegion == null) {
return;
}
String attributeName = open.getText(nameRegion);
IAttributeDefinition attribute = tag.getAttribute(attributeName);
if (attribute == null) {
return;
}
// TODO: refactor this out
if ("layoutData".equals(attributeName)) {
Context.addAdapter(new IAdaptable() {
@SuppressWarnings("unchecked")
public <T> T getAdapter(Class<T> adapterClass) {
if (adapterClass == Layout.class) {
Node layoutNode = contentAssistRequest.getNode().getParentNode()
.getAttributes().getNamedItem("layout");
if (layoutNode != null) {
return (T) new LayoutType().convert(layoutNode.getNodeValue(),
Strictness.NICE);
}
}
return null;
}
});
}
IType<?> type = attribute.getType();
if (type instanceof IContentAssistable) {
Match match = createMatch(contentAssistRequest).handleQuotes();
addProposals(contentAssistRequest, ((IContentAssistable) type).getProposals(match));
}
Context.clear();
}
private void addProposals(final ContentAssistRequest contentAssistRequest, List<Match> proposals) {
for (Match proposal : proposals) {
CompletionProposal newProposal = new CompletionProposal(proposal.getReplacementText(),
contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest
.getReplacementLength(), proposal.getReplacementCursorPos(), null,
proposal.getText(), null, null);
contentAssistRequest.addProposal(newProposal);
}
}
private Match createMatch(final ContentAssistRequest contentAssistRequest) {
return new Match(contentAssistRequest.getText(), contentAssistRequest.getMatchString()
.length());
}
}