/*
* JBoss by Red Hat
* Copyright 2006-2009, Red Hat Middleware, LLC, and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ide.eclipse.freemarker.editor;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.ITokenScanner;
import org.eclipse.jface.text.rules.Token;
import org.jboss.ide.eclipse.freemarker.Constants;
public class ContentScanner implements ITokenScanner {
private IDocument document;
private int offset;
private int length;
private int endOffset;
private Stack stack = new Stack();
private IToken defaultToken;
private int tokenOffset;
private int tokenLength;
private Stack stringTypes = new Stack();
private List tokens = new ArrayList();
private int currentOffset;
public ContentScanner (IToken defaultToken, ColorManager colorManager) {
this.defaultToken = defaultToken;
STRING_TOKEN = new Token(
new TextAttribute(
colorManager.getColor(Constants.COLOR_STRING)));
INTERPOLATION_TOKEN = new Token(
new TextAttribute(
colorManager.getColor(Constants.COLOR_INTERPOLATION)));
DIRECTIVE_TOKEN = new Token(
new TextAttribute(
colorManager.getColor(Constants.COLOR_DIRECTIVE)));
}
public void setRange(IDocument document, int offset, int length) {
this.document = document;
this.offset = offset;
this.currentOffset = offset;
this.length = length;
this.endOffset = offset + length;
this.stack.clear();
this.stringTypes.clear();
this.tokens.clear();
}
private static String TYPE_UNKNOWN = "UNKNOWN"; //$NON-NLS-1$
private static String TYPE_INTERPOLATION = "INTERPOLATION"; //$NON-NLS-1$
private static String TYPE_DIRECTIVE = "DIRECTIVE"; //$NON-NLS-1$
private static String TYPE_STRING = "STRING"; //$NON-NLS-1$
private static IToken STRING_TOKEN;
private static IToken INTERPOLATION_TOKEN;
private static IToken DIRECTIVE_TOKEN;
public IToken nextToken() {
int offsetStart = currentOffset;
int i = currentOffset;
char directiveTypeChar = Character.MIN_VALUE;
boolean escape = false;
boolean doEscape = false;
try {
char c = document.getChar(i);
char cNext = Character.MIN_VALUE;
if (document.getLength() > i + 2)
cNext = document.getChar(i+1);
if (i >= endOffset) {
return Token.EOF;
}
while (i < endOffset) {
doEscape = false;
if (!escape) {
String type = peek();
if (c == '\\') {
if (type.equals(TYPE_STRING)) doEscape = true;
}
else if (c == '\"' || c == '\'') {
if (type.equals(TYPE_STRING)) {
if (stringTypes.size() > 0 && c == ((Character) stringTypes.peek()).charValue()) {
this.tokenOffset = offsetStart;
this.tokenLength = i - offsetStart + 1;
this.currentOffset = i + 1;
pop();
return STRING_TOKEN;
}
}
else {
if (i == offsetStart) {
push(TYPE_STRING);
stringTypes.push(new Character(c));
}
else {
this.tokenOffset = offsetStart;
this.tokenLength = i - offsetStart;
this.currentOffset = i;
return getToken(type);
}
}
}
else if (c == '$') {
if (cNext == '{') {
// interpolation
this.tokenOffset = offsetStart;
this.tokenLength = i - offsetStart;
this.currentOffset = i;
if (i == offsetStart) {
push(TYPE_INTERPOLATION);
}
else {
return getToken(type);
}
}
}
else if (c == '}') {
if (type.equals(TYPE_INTERPOLATION)) {
this.tokenOffset = offsetStart;
this.tokenLength = i - offsetStart + 1;
this.currentOffset = i + 1;
pop();
return INTERPOLATION_TOKEN;
}
}
else if (c == '(') {
if (type.equals(TYPE_INTERPOLATION)) {
push("("); //$NON-NLS-1$
}
}
else if (c == ')') {
if (type.equals("(")) { //$NON-NLS-1$
pop();
}
}
else if ((c == '<' || c == '[') && !((stack.contains(TYPE_DIRECTIVE) || stack.contains(TYPE_INTERPOLATION)) && stack.contains(TYPE_STRING))) {
if (cNext == '#') {
// directive
if (i == offsetStart) {
directiveTypeChar = c;
push(TYPE_DIRECTIVE);
}
else {
this.tokenOffset = offsetStart;
this.tokenLength = i - offsetStart - 1;
this.currentOffset = i;
return getToken(type);
}
}
else if (cNext == '@') {
// macro
if (i == offsetStart) {
directiveTypeChar = c;
push(TYPE_DIRECTIVE);
}
else {
this.tokenOffset = offsetStart;
this.tokenLength = i - offsetStart - 1;
this.currentOffset = i;
return getToken(type);
}
}
}
else if ((c == ']' || c == '>') && !((stack.contains(TYPE_DIRECTIVE) || stack.contains(TYPE_INTERPOLATION)) && stack.contains(TYPE_STRING))) {
if ((c == ']' && directiveTypeChar == '[') || (c == '>' && directiveTypeChar == '<')
|| directiveTypeChar == Character.MIN_VALUE) {
this.tokenOffset = offsetStart;
this.tokenLength = i - offsetStart + 1;
this.currentOffset = i + 1;
if (directiveTypeChar != Character.MIN_VALUE) {
pop();
return DIRECTIVE_TOKEN;
}
else {
return defaultToken;
}
}
}
}
c = document.getChar(++i);
cNext = Character.MIN_VALUE;
if (document.getLength() > i + 2)
cNext = document.getChar(i+1);
escape = doEscape;
}
}
catch (BadLocationException e) {
this.currentOffset = i;
this.tokenOffset = offsetStart;
this.tokenLength = endOffset - tokenOffset;
if (tokenLength > 0) {
// last token
return defaultToken;
}
else {
return Token.EOF;
}
}
this.currentOffset = i+1;
this.tokenOffset = offsetStart;
this.tokenLength = endOffset - tokenOffset;
return getToken(peek());
}
private String peek () {
if (stack.size() > 0) return (String) stack.peek();
else return TYPE_UNKNOWN;
}
private void push (String s) {
stack.push(s);
}
private String pop () {
if (stack.size() > 0) return (String) stack.pop();
else return TYPE_UNKNOWN;
}
private IToken getToken (String type) {
if (type.equals(TYPE_DIRECTIVE)) return DIRECTIVE_TOKEN;
else if (type.equals(TYPE_INTERPOLATION) || type.equals("(")) return INTERPOLATION_TOKEN; //$NON-NLS-1$
else if (type.equals(TYPE_STRING)) return STRING_TOKEN;
else return defaultToken;
}
public int getTokenOffset() {
return tokenOffset;
}
public int getTokenLength() {
return tokenLength;
}
}