/*-
* Copyright © 2009 Diamond Light Source Ltd.
*
* This file is part of GDA.
*
* GDA is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License version 3 as published by the Free
* Software Foundation.
*
* GDA is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with GDA. If not, see <http://www.gnu.org/licenses/>.
*/
package uk.ac.gda.richbeans.editors.xml.bean;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextDoubleClickStrategy;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.jface.text.presentation.IPresentationReconciler;
import org.eclipse.jface.text.presentation.PresentationReconciler;
import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.swt.graphics.Image;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.ac.gda.util.schema.SchemaReader;
import com.swtdesigner.SWTResourceManager;
/**
* @author Matthew Gerring
*
*/
public class XMLConfiguration extends SourceViewerConfiguration {
private static final Logger logger = LoggerFactory.getLogger(XMLConfiguration.class);
private XMLDoubleClickStrategy doubleClickStrategy;
private XMLTagScanner tagScanner;
private XMLScanner scanner;
private ColorManager colorManager;
private SchemaReader schemaReader;
/**
* @param colorManager
* @param schemaUrl
* @throws Exception
*/
public XMLConfiguration(ColorManager colorManager, final URL schemaUrl) throws Exception{
this.colorManager = colorManager;
try {
if (schemaUrl!=null) this.schemaReader = new SchemaReader(schemaUrl);
} catch (NullPointerException ne) {
logger.error("Cannot create Schema reader for "+schemaUrl);
}
}
@Override
public String[] getConfiguredContentTypes(ISourceViewer sourceViewer) {
return new String[] {
IDocument.DEFAULT_CONTENT_TYPE,
XMLPartitionScanner.XML_COMMENT,
XMLPartitionScanner.XML_TAG };
}
@Override
public ITextDoubleClickStrategy getDoubleClickStrategy(
ISourceViewer sourceViewer,
String contentType) {
if (doubleClickStrategy == null)
doubleClickStrategy = new XMLDoubleClickStrategy();
return doubleClickStrategy;
}
protected XMLScanner getXMLScanner() {
if (scanner == null) {
scanner = new XMLScanner(colorManager);
scanner.setDefaultReturnToken(
new Token(
new TextAttribute(
colorManager.getColor(IXMLColorConstants.DEFAULT))));
}
return scanner;
}
protected XMLTagScanner getXMLTagScanner() {
if (tagScanner == null) {
tagScanner = new XMLTagScanner(colorManager);
tagScanner.setDefaultReturnToken(
new Token(
new TextAttribute(
colorManager.getColor(IXMLColorConstants.TAG))));
}
return tagScanner;
}
@Override
public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
PresentationReconciler reconciler = new PresentationReconciler();
DefaultDamagerRepairer dr =
new DefaultDamagerRepairer(getXMLTagScanner());
reconciler.setDamager(dr, XMLPartitionScanner.XML_TAG);
reconciler.setRepairer(dr, XMLPartitionScanner.XML_TAG);
dr = new DefaultDamagerRepairer(getXMLScanner());
reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE);
reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE);
NonRuleBasedDamagerRepairer ndr =
new NonRuleBasedDamagerRepairer(
new TextAttribute(
colorManager.getColor(IXMLColorConstants.XML_COMMENT)));
reconciler.setDamager(ndr, XMLPartitionScanner.XML_COMMENT);
reconciler.setRepairer(ndr, XMLPartitionScanner.XML_COMMENT);
return reconciler;
}
@Override
public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
ContentAssistant assistant= new ContentAssistant();
assistant.setContentAssistProcessor(new XMLCompletionProcessor(), IDocument.DEFAULT_CONTENT_TYPE);
assistant.setContentAssistProcessor(new XMLCompletionProcessor(), XMLPartitionScanner.XML_TAG);
assistant.setContentAssistProcessor(new XMLCompletionProcessor(), XMLPartitionScanner.XML_COMMENT);
assistant.enableAutoActivation(true);
assistant.setAutoActivationDelay(1);
//assistant.setProposalPopupOrientation(IContentAssistant.PROPOSAL_OVERLAY);
//assistant.setContextInformationPopupOrientation(IContentAssistant.CONTEXT_INFO_ABOVE);
return assistant;
}
private static Pattern END = Pattern.compile("^.*\\<\\/(.+)\\>.*$");
private static Pattern SIBLING = Pattern.compile("^.*\\<(.+)\\>.*$");
/**
* @param viewer
* @param offset
* @return par tag for position
*/
@SuppressWarnings("unchecked")
public List<String> getParents(final ITextViewer viewer, final int offset) {
try {
final ITypedRegion type = viewer.getDocument().getPartition(offset);
final String seg = viewer.getDocument().get(type.getOffset(), type.getLength());
final String clean = seg.trim().replace("\n", "");
Matcher matcher = END.matcher(clean);
if (matcher.matches()) {
return Arrays.asList(new String[]{matcher.group(1)});
}
matcher = SIBLING.matcher(clean);
if (matcher.matches()) {
final String sibling = matcher.group(1);
return schemaReader!=null ? schemaReader.getParents(sibling) : Collections.EMPTY_LIST;
}
} catch (Exception e) {
logger.debug("Problem determining tag at position.", e);
return null; // We get no suggestions, the developer can then fix them if this is occurring.
}
return null;
}
private void filterCurrentlyTyped( final ITextViewer viewer,
final int offset,
final List<String> allTags) {
try {
final ITypedRegion type = viewer.getDocument().getPartition(offset);
final String seg = viewer.getDocument().get(type.getOffset(), type.getLength());
final String frag = seg.substring(seg.indexOf("<")+1, seg.indexOf('\n')).trim();
if (frag!=null&&!"".equals(frag)) {
for (Iterator<String> it = allTags.iterator(); it.hasNext();) {
String name = it.next();
if (!name.startsWith(frag)) it.remove();
}
}
} catch (Exception e) {
logger.debug("Problem filtering tags.", e);
}
}
private int getNumberCharactersTyped(final ITextViewer viewer, final int offset) {
try {
String c = viewer.getDocument().get(offset, 1);
int chars = 0;
while(!"<".equals(c)) {
++chars;
c = viewer.getDocument().get(offset-chars, 1);
}
return chars-1;
} catch (Exception e) {
logger.debug("Problem filtering tags.", e);
return 0;
}
}
/**
* @author Matthew Gerring
*
*/
public class XMLCompletionProcessor implements IContentAssistProcessor {
private final Image tagImage = SWTResourceManager.getImage(XMLConfiguration.class, "/icons/tag.png");
@Override
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
final List<String> parents = getParents(viewer, offset);
// Currently just add all possible tags
List<String> allTags = new ArrayList<String>(31);
if (schemaReader!=null) try {
for (String par : parents) {
allTags.addAll(schemaReader.getChildTags(par));
}
} catch (Exception e) {
return null;
}
filterCurrentlyTyped(viewer, offset, allTags);
final int pos = getNumberCharactersTyped(viewer, offset);
final ICompletionProposal[] props = new ICompletionProposal[allTags.size()];
int iTag = 0;
for (String tag : allTags) {
final CompletionProposal prop = new CompletionProposal(tag.substring(pos)+"></"+tag+">", offset, 0, tag.length()+1-pos, tagImage, tag+"></"+tag+">", null, null);
props[iTag] = prop;
++iTag;
}
return props;
}
@Override
public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
// TODO Auto-generated method stub
return null;
}
@Override
public char[] getCompletionProposalAutoActivationCharacters() {
return CHARS;
}
private final char [] CHARS = new char[]{'<'};
@Override
public char[] getContextInformationAutoActivationCharacters() {
return null;
}
@Override
public IContextInformationValidator getContextInformationValidator() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getErrorMessage() {
// TODO Auto-generated method stub
return null;
}
}
}