/*
* Created on Jan 30, 2004
*
* The MIT License
* Copyright (c) 2004 Rob Rohan
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.cfeclipse.cfml.editors.partitioner.scanners;
/**
* @author Rob
*
* This scans the overall document and slices it into partitions. Then the
* partition scanners are applied to those partitions
*/
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.cfeclipse.cfml.dictionary.DictionaryManager;
import org.cfeclipse.cfml.dictionary.ISyntaxDictionary;
import org.cfeclipse.cfml.dictionary.SyntaxDictionary;
import org.cfeclipse.cfml.dictionary.Tag;
import org.cfeclipse.cfml.editors.partitioner.scanners.rules.CFScriptComponentEndRule;
import org.cfeclipse.cfml.editors.partitioner.scanners.rules.CFScriptComponentRule;
import org.cfeclipse.cfml.editors.partitioner.scanners.rules.CustomTagRule;
import org.cfeclipse.cfml.editors.partitioner.scanners.rules.NamedTagRule;
import org.cfeclipse.cfml.editors.partitioner.scanners.rules.NestableMultiLineRule;
import org.cfeclipse.cfml.editors.partitioner.scanners.rules.TagRule;
import org.cfeclipse.cfml.editors.partitioner.scanners.rules.TaglibRule;
import org.cfeclipse.cfml.preferences.CFMLPreferenceManager;
import org.eclipse.jface.text.rules.EndOfLineRule;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IPredicateRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.IWordDetector;
import org.eclipse.jface.text.rules.MultiLineRule;
import org.eclipse.jface.text.rules.RuleBasedPartitionScanner;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.rules.WordRule;
public class CFPartitionScanner extends RuleBasedPartitionScanner {
//public final static String CF_DEFAULT = "__cf_default";
public final static String DOCTYPE = "__doctype";
public final static String CF_COMMENT = "__cf_comment";
public final static String CF_SCRIPT_COMMENT_BLOCK = "__cf_script_comment_block";
public final static String CF_SCRIPT_COMMENT = "__cf_script_comment";
public final static String JAVADOC_COMMENT = "__cf_javadoc_comment";
public final static String HTM_COMMENT = "__htm_comment";
public final static String CF_SCRIPT = "__cf_script";
public final static String CF_START_TAG = "__cf_start_tag";
public final static String CF_START_TAG_BEGIN = "__cf_start_tag_begin";
public final static String CF_START_TAG_END = "__cf_start_tag_end";
public final static String CF_TAG_ATTRIBS = "__cf_tag_attribs";
public final static String CF_SET_STATEMENT = "__cf_set_statment";
public final static String CF_RETURN_STATEMENT = "__cf_return_statement";
public final static String CF_BOOLEAN_STATEMENT = "__cf_boolean_statement";
public final static String CF_END_TAG = "__cf_end_tag";
public final static String HTM_START_TAG = "__htm_start_tag";
public final static String HTM_END_TAG = "__htm_end_tag";
public final static String HTM_START_TAG_BEGIN = "__htm_start_tag_begin";
public final static String HTM_START_TAG_END = "__htm_start_tag_end";
public final static String HTM_TAG_ATTRIBS = "__htm_tag_attribs";
public final static String CF_EXPRESSION = "__cf_expression";
public final static String J_SCRIPT = "__jscript";
public final static String CSS = "__css";
public final static String SQL = "__sql";
public final static String TAGLIB_TAG = "__taglib_tag";
public final static String UNK_TAG = "__unk_tag";
//form and table
public final static String FORM_END_TAG = "__form_end_tag";
public final static String FORM_START_TAG = "__form_start_tag";
public final static String FORM_START_TAG_BEGIN = "__form_start_tag_begin";
public final static String FORM_TAG_ATTRIBS = "__form_tag_attribs";
public final static String FORM_START_TAG_END = "__form_start_tag_end";
public final static String TABLE_END_TAG = "__table_end_tag";
public final static String TABLE_START_TAG = "__table_start_tag";
public final static String TABLE_START_TAG_BEGIN = "__table_start_tag_begin";
public final static String TABLE_TAG_ATTRIBS = "__table_tag_attribs";
public final static String TABLE_START_TAG_END = "__table_start_tag_end";
/**
* Detector for empty markup comments.
*/
static class EmptyMarkupCommentDetector implements IWordDetector {
/*
* (non-Javadoc) Method declared on IWordDetector
*/
public boolean isWordStart(char c) {
return (c == '<');
}
/*
* (non-Javadoc) Method declared on IWordDetector
*/
public boolean isWordPart(char c) {
return (c == '-' || c == '!' || c == '>');
}
}
/**
* Detector for empty comments.
*/
static class EmptyCommentDetector implements IWordDetector {
/*
* (non-Javadoc) Method declared on IWordDetector
*/
public boolean isWordStart(char c) {
return (c == '/');
}
/*
* (non-Javadoc) Method declared on IWordDetector
*/
public boolean isWordPart(char c) {
return (c == '*' || c == '/');
}
}
/**
*
*/
static class WordPredicateRule extends WordRule implements IPredicateRule {
private IToken fSuccessToken;
public WordPredicateRule(IToken successToken) {
super(new EmptyCommentDetector());
fSuccessToken = successToken;
addWord("/**/", fSuccessToken); //$NON-NLS-1$
}
/*
* @see org.eclipse.jface.text.rules.IPredicateRule#evaluate(ICharacterScanner, boolean)
*/
public IToken evaluate(ICharacterScanner scanner, boolean resume) {
return super.evaluate(scanner);
}
/*
* @see org.eclipse.jface.text.rules.IPredicateRule#getSuccessToken()
*/
public IToken getSuccessToken() {
return fSuccessToken;
}
}
private CFMLPreferenceManager prefs;
private static boolean fParseCFScriptCFCs;
public CFPartitionScanner()
{
prefs = new CFMLPreferenceManager();
fParseCFScriptCFCs = prefs.parseCFScriptCFCs();
IToken doctype = new Token(DOCTYPE);
IToken cfComment = new Token(CF_COMMENT);
IToken cfscriptCommentBlock = new Token(CF_SCRIPT_COMMENT_BLOCK);
IToken cfscriptComment = new Token(CF_SCRIPT_COMMENT);
IToken javaDocComment = new Token(JAVADOC_COMMENT);
IToken htmComment = new Token(HTM_COMMENT);
IToken taglibtag = new Token(TAGLIB_TAG);
IToken unktag = new Token(UNK_TAG);
List rules = new ArrayList();
//the order here is important. It should go from specific to
//general as the rules are applied in order
// NestableMultiLineRule cfScriptRule = new NestableMultiLineRule("component", "}", cfScript);
// cfScriptRule.setColumnConstraint(0);
// rules.add(cfScriptRule);
rules.add(new CFScriptComponentRule("component", "{", CF_START_TAG, CF_TAG_ATTRIBS));
rules.add(new CFScriptComponentEndRule("}", CF_END_TAG, CF_SCRIPT));
// rules.add(new CFScriptComponentRule("}", "}", CF_SCRIPT, CF_SCRIPT));
rules.add(new WordPredicateRule(cfscriptCommentBlock));
rules.add(new MultiLineRule("/**", "*/", javaDocComment, (char) 0, true));
rules.add(new MultiLineRule("/*", "*/", cfscriptCommentBlock, (char) 0, true));
rules.add(new EndOfLineRule("//", cfscriptComment));
// Partitions in the document will get marked in this order
rules.add(new NestableMultiLineRule("<!---", "--->", cfComment, (char) 0, true));
// rules.add(new TagRule(htmComment));
rules.add(new MultiLineRule("<!--", "-->", htmComment));
//doctype rule
rules.add(new MultiLineRule("<!doctype", ">", doctype));
// Handle the if/elsief/set/return tag partitioning
rules.add(new NamedTagRule("<cfset",">", CF_START_TAG, CF_SET_STATEMENT));
rules.add(new NamedTagRule("<cfif",">", CF_START_TAG, CF_BOOLEAN_STATEMENT));
rules.add(new NamedTagRule("<cfelseif",">", CF_START_TAG, CF_BOOLEAN_STATEMENT));
rules.add(new NamedTagRule("<cfreturn",">", CF_START_TAG, CF_RETURN_STATEMENT));
/*
* TODO: Need to add some code to track the partition changes
* as we run the nextToken() method.
*
* As each partition change occurs it needs to see if the
* default partition needs to change.
*
* Also need to look into some method for having <cfset***> with
* the starter and ender being separate partitions but easily
* identifiable as part of the same tag for parser purposes. Possibly
* through some sort of __expression_wrapper_start,
* __expression_wrapper_end pair that precede and follow the
* __cf_expression partition type. That would allow us to handle ##
* pairs as well, but we need to also think about syntax highlighting
* for those.
*
* Need to provide simple methods in the partitioner or document to
* retrieve the stored partition details.
*
*/
SyntaxDictionary sd = DictionaryManager.getDictionary(DictionaryManager.CFDIC);
Tag tg = null;
try
{
Set elements = ((ISyntaxDictionary)sd).getAllElements();
Iterator it = elements.iterator();
while(it.hasNext())
{
String ename = (String)it.next();
//System.out.println(ename);
tg = sd.getTag(ename);
rules.add(new NamedTagRule("<" + ename,">", CF_START_TAG, CF_TAG_ATTRIBS));
//if(!tg.isSingle())
//{
rules.add(new NamedTagRule("</" + ename,">", CF_END_TAG, CF_END_TAG));
//}
}
}
catch(Exception e)
{
e.printStackTrace(System.err);
}
//these are not really handled in the dictionary because you can call
//normal pages as cf_'s
rules.add(new CustomTagRule("<cf_",">", CF_START_TAG, CF_TAG_ATTRIBS));
rules.add(new CustomTagRule("</cf_",">", CF_END_TAG, CF_END_TAG));
//do the html tags now
sd = DictionaryManager.getDictionary(DictionaryManager.HTDIC);
try
{
Set elements = ((ISyntaxDictionary)sd).getAllElements();
//this is going to be used to tell if we need to add a form, table,
//or normal tag for the html tags
String startTokenType = HTM_START_TAG;
String endTokenType = HTM_END_TAG;
String midTokenType = HTM_TAG_ATTRIBS;
//loop over all the tags in the html dictionary and try to set the
//partition to the correct type
Iterator it = elements.iterator();
while(it.hasNext())
{
String ename = (String)it.next();
tg = sd.getTag(ename);
//colour form and table tags differently...
if(tg.isTableTag()) {
startTokenType = TABLE_START_TAG;
midTokenType = TABLE_TAG_ATTRIBS;
endTokenType = TABLE_END_TAG;
}
else if(tg.isFormTag()) {
startTokenType = FORM_START_TAG;
midTokenType = FORM_TAG_ATTRIBS;
endTokenType = FORM_END_TAG;
}
else {
startTokenType = HTM_START_TAG;
midTokenType = HTM_TAG_ATTRIBS;
endTokenType = HTM_END_TAG;
}
rules.add(new NamedTagRule("<" + ename,">", startTokenType, midTokenType));
rules.add(new NamedTagRule("</" + ename,">", endTokenType, endTokenType));
}
}
catch(Exception e)
{
e.printStackTrace(System.err);
}
// Taglibs that may have been imported
// <foo:bar> type tags
rules.add(new TaglibRule(taglibtag));
//catch any other tags we dont know about (xml etc) and make them
//a different color
rules.add(new TagRule(unktag));
//for debuggin
//rules.add(new ShowWhitespaceRule(new CFWhitespaceDetector()));
IPredicateRule[] rulearry = new IPredicateRule[rules.size()];
rules.toArray(rulearry);
setPredicateRules(rulearry);
}
public int getOffset() {
return this.fOffset;
}
/**
* Runs all configured rules at the current document offset and returns a token
* to indicate what the partition type should be at that offset. The scanner
* offset is automatically updated.
*/
public IToken nextToken() {
/*
* If we are not currently in a partition and
* there are some rules defined we run through
* the rules and look for a token.
*/
if (this.fContentType == null || this.fRules == null) {
IToken token;
// Keep running until we find something.
while (true) {
// Reinitialize the token and column markers.
this.fTokenOffset= this.fOffset;
this.fColumn= UNDEFINED;
if (this.fRules != null) {
int c = -1;
while(true) {
c = read();
if (c == EOF || c == 65535) {
return Token.EOF;
}
if (!fParseCFScriptCFCs) {
/*
* Short-circuit the rule check if the next character
* isn't a '<'. This assumes that every rule has '<'
* as the first character of the start sequence.
*
*/
if (c != '<') {
return this.fDefaultReturnToken;
}
}
break;
}
unread();
for (int i= 0; i < this.fRules.length; i++) {
// Get the token for the current rule.
token= (this.fRules[i].evaluate(this));
// Check if the rule found anything
if (!token.isUndefined()) {
// Return the token.
return token;
}
}
}
// If we got to here, none of the rules found a token.
// Check if we're at the end of the file. If so, return the relevant token.
if (read() == EOF) {
return Token.EOF;
}
return this.fDefaultReturnToken;
}
}
// If we get to here we're already inside a partition
// re-initialize column constraint
this.fColumn= UNDEFINED;
// Check if we are inside a partition.
boolean resume= (this.fPartitionOffset > -1 && this.fPartitionOffset < this.fOffset);
// Set the token offset according to whether we're in a partition or not.
this.fTokenOffset= resume ? this.fPartitionOffset : this.fOffset;
IPredicateRule rule;
IToken token;
// Run through all the rules looking for a match.
for (int i= 0; i < this.fRules.length; i++) {
rule= (IPredicateRule) this.fRules[i];
/*
* Get the success token for this rule so we can
* check if it matches the current content type.
*/
token = rule.getSuccessToken();
//System.out.println("Checking if content type - " + fContentType + " matches " + token.getData());
if (this.fContentType.equals(token.getData().toString())
|| this.fContentType.equals(token.getData().toString() + "_begin")
|| this.fContentType.equals(token.getData().toString() + "_end")
|| this.fContentType.equals(token.getData().toString() + "_attribs")) {
// The content type matches, so we want to run the resume on the rule.
token= rule.evaluate(this, resume);
if (!token.isUndefined()) {
this.fContentType= null;
return token;
}
}
}
// haven't found any rule for this type of partition
this.fContentType= null;
if (resume) {
this.fOffset= this.fPartitionOffset;
}
return super.nextToken();
}
/**
* Return the String ranging from the start of the current partition to the current scanning position. Some rules
* (@see NestedMultiLineRule) need this information to calculate the comment nesting depth.
*
*/
public String getScannedPartitionString() {
try {
String scanned = fDocument.get(fPartitionOffset, fOffset - fPartitionOffset);
return scanned;
} catch (Exception e) {
// Do nothing
}
return "";
}
}