/** * Aptana Studio * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.editor.haml; import java.util.List; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.contentassist.IContentAssistProcessor; import org.eclipse.jface.text.presentation.PresentationReconciler; import org.eclipse.jface.text.rules.DefaultDamagerRepairer; import org.eclipse.jface.text.rules.IPredicateRule; import org.eclipse.jface.text.rules.IRule; import org.eclipse.jface.text.rules.IToken; import org.eclipse.jface.text.rules.ITokenScanner; import org.eclipse.jface.text.rules.MultiLineRule; import org.eclipse.jface.text.rules.RuleBasedScanner; import org.eclipse.jface.text.rules.SingleLineRule; import org.eclipse.jface.text.rules.Token; import org.eclipse.jface.text.source.ISourceViewer; import com.aptana.core.util.StringUtil; import com.aptana.editor.common.AbstractThemeableEditor; import com.aptana.editor.common.CommonEditorPlugin; import com.aptana.editor.common.CommonUtil; import com.aptana.editor.common.IPartitioningConfiguration; import com.aptana.editor.common.ISourceViewerConfiguration; import com.aptana.editor.common.TextUtils; import com.aptana.editor.common.scripting.IContentTypeTranslator; import com.aptana.editor.common.scripting.QualifiedContentType; import com.aptana.editor.common.text.SingleTokenScanner; import com.aptana.editor.common.text.rules.CharacterMapRule; import com.aptana.editor.common.text.rules.CommentScanner; import com.aptana.editor.common.text.rules.ISubPartitionScanner; import com.aptana.editor.common.text.rules.MultiCharacterRule; import com.aptana.editor.common.text.rules.SingleCharacterRule; import com.aptana.editor.common.text.rules.ThemeingDamagerRepairer; import com.aptana.editor.haml.internal.HAMLElementScanner; import com.aptana.editor.haml.internal.HAMLSubPartitionScanner; import com.aptana.editor.haml.internal.RubyAttributesSourceConfiguration; import com.aptana.editor.haml.internal.text.rules.HAMLElementRule; import com.aptana.editor.haml.internal.text.rules.HAMLEscapeRule; import com.aptana.editor.haml.internal.text.rules.HAMLSingleLineRule; import com.aptana.editor.haml.internal.text.rules.RubyEvaluationElementRule; import com.aptana.editor.ruby.RubySourceConfiguration; import com.aptana.ruby.core.IRubyConstants; /** * @author Max Stepanov * @author Chris Williams */ public class HAMLSourceConfiguration implements IPartitioningConfiguration, ISourceViewerConfiguration { private final static String PREFIX = "__haml_"; //$NON-NLS-1$ public final static String DEFAULT = PREFIX + IDocument.DEFAULT_CONTENT_TYPE; public final static String DOCTYPE = PREFIX + "doctype"; //$NON-NLS-1$ public final static String ELEMENT = PREFIX + "element"; //$NON-NLS-1$ public final static String RUBY_EVALUATION = PREFIX + "ruby_evaluation"; //$NON-NLS-1$ public final static String RUBY_ATTRIBUTES = PREFIX + "ruby_attributes"; //$NON-NLS-1$ public final static String RUBY_ATTRIBUTES_CLOSE = PREFIX + "ruby_attributes_close"; //$NON-NLS-1$ public final static String HTML_ATTRIBUTES = PREFIX + "html_attributes"; //$NON-NLS-1$ public final static String OBJECT = PREFIX + "object"; //$NON-NLS-1$ public final static String INTERPOLATION = PREFIX + "interpolation"; //$NON-NLS-1$ public final static String HTML_COMMENT = PREFIX + "html_comment"; //$NON-NLS-1$ public final static String HAML_COMMENT = PREFIX + "haml_comment"; //$NON-NLS-1$ public static final String[] CONTENT_TYPES = new String[] { DEFAULT, HTML_COMMENT, HAML_COMMENT, DOCTYPE, ELEMENT, INTERPOLATION, RUBY_EVALUATION, HTML_ATTRIBUTES, RUBY_ATTRIBUTES, RUBY_ATTRIBUTES_CLOSE, OBJECT }; private static final String[] SPELLING_CONTENT_TYPES = new String[] { DEFAULT, HTML_COMMENT, HAML_COMMENT }; private static final String[][] TOP_CONTENT_TYPES = new String[][] { { IHAMLConstants.CONTENT_TYPE_HAML }, { IHAMLConstants.CONTENT_TYPE_HAML, IRubyConstants.CONTENT_TYPE_RUBY } }; private final IPredicateRule[] partitioningRules = new IPredicateRule[] { new HAMLSingleLineRule("/", getToken(HTML_COMMENT)), //$NON-NLS-1$ new HAMLSingleLineRule("-#", getToken(HAML_COMMENT)), //$NON-NLS-1$ new HAMLSingleLineRule("!!!", getToken(DOCTYPE)), //$NON-NLS-1$ new HAMLEscapeRule(getToken(StringUtil.EMPTY)), new SingleLineRule("#{", "}", getToken(INTERPOLATION)), //$NON-NLS-1$ //$NON-NLS-2$ new HAMLElementRule(getToken(ELEMENT)), new RubyEvaluationElementRule(new Token(RUBY_EVALUATION)), new SingleCharacterRule('{', getToken(RUBY_ATTRIBUTES)), new SingleCharacterRule('}', getToken(RUBY_ATTRIBUTES_CLOSE)), new SingleLineRule("[", "]", getToken(OBJECT)), //$NON-NLS-1$ //$NON-NLS-2$ new MultiLineRule("(", ")", getToken(HTML_ATTRIBUTES)), //$NON-NLS-1$ //$NON-NLS-2$ }; private static HAMLSourceConfiguration instance; static { IContentTypeTranslator c = CommonEditorPlugin.getDefault().getContentTypeTranslator(); c.addTranslation(new QualifiedContentType(IHAMLConstants.CONTENT_TYPE_HAML), new QualifiedContentType( IHAMLConstants.TEXT_SCOPE)); c.addTranslation(new QualifiedContentType(HAML_COMMENT), new QualifiedContentType( IHAMLConstants.HAML_COMMENT_SCOPE)); c.addTranslation(new QualifiedContentType(HTML_COMMENT), new QualifiedContentType( IHAMLConstants.HTML_COMMENT_SCOPE)); c.addTranslation(new QualifiedContentType(DOCTYPE), new QualifiedContentType(IHAMLConstants.DOCTYPE_SCOPE)); c.addTranslation(new QualifiedContentType(ELEMENT), new QualifiedContentType(IHAMLConstants.TAG_SCOPE)); c.addTranslation(new QualifiedContentType(HTML_ATTRIBUTES), new QualifiedContentType( IHAMLConstants.RUBY_ATTRIBUTES_SCOPE)); c.addTranslation(new QualifiedContentType(RUBY_ATTRIBUTES), new QualifiedContentType( IHAMLConstants.RUBY_ATTRIBUTES_SCOPE)); c.addTranslation(new QualifiedContentType(RUBY_EVALUATION), new QualifiedContentType( IHAMLConstants.RUBY_EVAL_SCOPE)); c.addTranslation(new QualifiedContentType(OBJECT), new QualifiedContentType(IHAMLConstants.OBJECT_SCOPE)); c.addTranslation(new QualifiedContentType(INTERPOLATION), new QualifiedContentType( IHAMLConstants.INTERPOLATION_SCOPE)); c.addTranslation(new QualifiedContentType(IHAMLConstants.CONTENT_TYPE_HAML, IRubyConstants.CONTENT_TYPE_RUBY), new QualifiedContentType(IHAMLConstants.TEXT_SCOPE, IHAMLConstants.RUBY_EVAL_SCOPE, IHAMLConstants.EMBEDDED_RUBY_SCOPE)); } public synchronized static HAMLSourceConfiguration getDefault() { if (instance == null) { instance = new HAMLSourceConfiguration(); } return instance; } private HAMLSourceConfiguration() { } /* * (non-Javadoc) * @see com.aptana.editor.common.IPartitioningConfiguration#getContentTypes() */ public String[] getContentTypes() { return TextUtils.combine(new String[][] { CONTENT_TYPES, RubySourceConfiguration.CONTENT_TYPES, RubyAttributesSourceConfiguration.CONTENT_TYPES }); } /* * (non-Javadoc) * @see com.aptana.editor.common.ITopContentTypesProvider#getTopContentTypes() */ public String[][] getTopContentTypes() { return TOP_CONTENT_TYPES; } /* * (non-Javadoc) * @see com.aptana.editor.common.ISourceViewerConfiguration#getSpellingContentTypes () */ public String[] getSpellingContentTypes() { return SPELLING_CONTENT_TYPES; } /* * (non-Javadoc) * @see com.aptana.editor.common.IPartitioningConfiguration#getPartitioningRules () */ public IPredicateRule[] getPartitioningRules() { return partitioningRules; } /* * (non-Javadoc) * @see com.aptana.editor.common.IPartitioningConfiguration#createSubPartitionScanner () */ public ISubPartitionScanner createSubPartitionScanner() { return new HAMLSubPartitionScanner(); } /* * (non-Javadoc) * @see com.aptana.editor.common.IPartitioningConfiguration# getDocumentDefaultContentType() */ public String getDocumentContentType(String contentType) { if (contentType.startsWith(PREFIX)) { return IHAMLConstants.CONTENT_TYPE_HAML; } String result = RubySourceConfiguration.getDefault().getDocumentContentType(contentType); if (result != null) { return result; } result = RubyAttributesSourceConfiguration.getDefault().getDocumentContentType(contentType); if (result != null) { return result; } return null; } /* * (non-Javadoc) * @see com.aptana.editor.common.ISourceViewerConfiguration# * setupPresentationReconciler(org.eclipse.jface.text.presentation .PresentationReconciler, * org.eclipse.jface.text.source.ISourceViewer) */ public void setupPresentationReconciler(PresentationReconciler reconciler, ISourceViewer sourceViewer) { RubySourceConfiguration.getDefault().setupPresentationReconciler(reconciler, sourceViewer); RubyAttributesSourceConfiguration.getDefault().setupPresentationReconciler(reconciler, sourceViewer); DefaultDamagerRepairer dr = new ThemeingDamagerRepairer(getTextScanner()); reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE); reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE); reconciler.setDamager(dr, DEFAULT); reconciler.setRepairer(dr, DEFAULT); dr = new ThemeingDamagerRepairer(getHTMLCommentScanner()); reconciler.setDamager(dr, HTML_COMMENT); reconciler.setRepairer(dr, HTML_COMMENT); dr = new ThemeingDamagerRepairer(getHAMLCommentScanner()); reconciler.setDamager(dr, HAML_COMMENT); reconciler.setRepairer(dr, HAML_COMMENT); dr = new ThemeingDamagerRepairer(getDocTypeScanner()); reconciler.setDamager(dr, DOCTYPE); reconciler.setRepairer(dr, DOCTYPE); dr = new ThemeingDamagerRepairer(getElementScanner()); reconciler.setDamager(dr, ELEMENT); reconciler.setRepairer(dr, ELEMENT); dr = new ThemeingDamagerRepairer(getInterpolationScanner()); reconciler.setDamager(dr, INTERPOLATION); reconciler.setRepairer(dr, INTERPOLATION); dr = new ThemeingDamagerRepairer(getObjectScanner()); reconciler.setDamager(dr, OBJECT); reconciler.setRepairer(dr, OBJECT); dr = new ThemeingDamagerRepairer(getHTMLAttributesScanner()); reconciler.setDamager(dr, HTML_ATTRIBUTES); reconciler.setRepairer(dr, HTML_ATTRIBUTES); dr = new ThemeingDamagerRepairer(getRubyEvaluationScanner()); reconciler.setDamager(dr, RUBY_EVALUATION); reconciler.setRepairer(dr, RUBY_EVALUATION); } protected ITokenScanner getRubyEvaluationScanner() { return new SingleTokenScanner(getToken(StringUtil.EMPTY)); } /* * (non-Javadoc) * @see com.aptana.editor.common.ISourceViewerConfiguration#getContentAssistProcessor (com.aptana.editor.common. * AbstractThemeableEditor, java.lang.String) */ public IContentAssistProcessor getContentAssistProcessor(AbstractThemeableEditor editor, String contentType) { return null; } private ITokenScanner getTextScanner() { RuleBasedScanner textScanner = new RuleBasedScanner(); textScanner.setRules(new IRule[] { new CharacterMapRule().add('/', getToken(IHAMLConstants.TAG_TERMINATOR_PUNCTUATION_SCOPE)) .add('/', getToken(IHAMLConstants.TAG_TERMINATOR_PUNCTUATION_SCOPE)) .add('>', getToken(IHAMLConstants.TAG_OTHER_PUNCTUATION_SCOPE)) .add('<', getToken(IHAMLConstants.TAG_OTHER_PUNCTUATION_SCOPE)) .add('&', getToken(IHAMLConstants.TAG_OTHER_PUNCTUATION_SCOPE)) .add('!', getToken(IHAMLConstants.TAG_OTHER_PUNCTUATION_SCOPE)), new HAMLEscapeRule(getToken(IHAMLConstants.META_ESCAPE_SCOPE)) }); textScanner.setDefaultReturnToken(getToken(IHAMLConstants.TEXT_SCOPE)); return textScanner; } private ITokenScanner getElementScanner() { return new HAMLElementScanner(); } private ITokenScanner getInterpolationScanner() { RuleBasedScanner interpolationScanner = new RuleBasedScanner(); interpolationScanner.setRules(new IRule[] { new MultiCharacterRule("#{", getToken(IHAMLConstants.SECTION_EMBEDDED_PUNCTUATION_SCOPE)), //$NON-NLS-1$ new SingleCharacterRule('}', getToken(IHAMLConstants.SECTION_EMBEDDED_PUNCTUATION_SCOPE)) }); interpolationScanner.setDefaultReturnToken(getToken(IHAMLConstants.EMBEDDED_RUBY_SOURCE_SCOPE)); return interpolationScanner; } private ITokenScanner getObjectScanner() { RuleBasedScanner objectScanner = new RuleBasedScanner(); // @formatter:off objectScanner.setRules(new IRule[] { new CharacterMapRule() .add('[', getToken(IHAMLConstants.SECTION_OTHER_PUNCTUATION_SCOPE)) .add(']', getToken(IHAMLConstants.SECTION_OTHER_PUNCTUATION_SCOPE)), // TODO: add word rules here for: // - variable.other.readwrite.instance.ruby // - constant.other.symbol.ruby // - comma }); // @formatter:on objectScanner.setDefaultReturnToken(getToken(IHAMLConstants.OBJECT_SCOPE)); return objectScanner; } private ITokenScanner getHTMLAttributesScanner() { RuleBasedScanner htmlAttributesScanner = new RuleBasedScanner(); // @formatter:off htmlAttributesScanner.setRules(new IRule[] { new CharacterMapRule() .add('(', getToken(IHAMLConstants.SECTION_OTHER_PUNCTUATION_SCOPE)) .add(')', getToken(IHAMLConstants.SECTION_OTHER_PUNCTUATION_SCOPE)), // TODO: add word rules here for: // - single quoted string // - double quoted string // - an HTML attribute name // - equal sign }); // @formatter:on htmlAttributesScanner.setDefaultReturnToken(getToken(IHAMLConstants.OBJECT_SCOPE)); return htmlAttributesScanner; } private ITokenScanner getHTMLCommentScanner() { // FIXME Use CommentScanner and subclass! RuleBasedScanner commentScanner = new RuleBasedScanner(); commentScanner = new CommentScanner(getToken(IHAMLConstants.HTML_COMMENT_SCOPE)) { @Override protected List<IRule> createRules() { List<IRule> rules = super.createRules(); rules.add(new SingleCharacterRule('/', getToken(IHAMLConstants.COMMENT_PUNCTUATION_SCOPE))); return rules; } }; return commentScanner; } private ITokenScanner getHAMLCommentScanner() { return new CommentScanner(getToken(IRubyConstants.LINE_COMMENT_SCOPE)); } private ITokenScanner getDocTypeScanner() { RuleBasedScanner docTypeScanner = new RuleBasedScanner(); docTypeScanner.setRules(new IRule[] { new SingleCharacterRule('!', getToken(IHAMLConstants.PROLOG_DEF_PUNCTUATION_SCOPE)) }); docTypeScanner.setDefaultReturnToken(getToken(IHAMLConstants.DOCTYPE_SCOPE)); return docTypeScanner; } private static IToken getToken(String tokenName) { return CommonUtil.getToken(tokenName); } }