/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.jedit;
/*
* TokenMarker.java - Generic token marker
* Copyright (C) 1998, 1999 Slava Pestov
*
* You may use and modify this package for any purpose. Redistribution is
* permitted, in both source and binary form, provided that this notice
* remains intact in all source distributions of this package.
*/
import javax.swing.text.Segment;
import org.openflexo.toolbox.TokenMarkerStyle;
/**
* A token marker that splits lines of text into tokens. Each token carries a length field and an indentification tag that can be mapped to
* a color for painting that token.
* <p>
*
* For performance reasons, the linked list of tokens is reused after each line is tokenized. Therefore, the return value of
* <code>markTokens</code> should only be used for immediate painting. Notably, it cannot be cached.
*
* @author Slava Pestov
* @version $Id: TokenMarker.java,v 1.2 2011/09/12 11:47:11 gpolet Exp $
*
* @see org.gjt.sp.jedit.syntax.Token
*/
public abstract class TokenMarker {
/**
* A wrapper for the lower-level <code>markTokensImpl</code> method that is called to split a line up into tokens.
*
* @param line
* The line
* @param lineIndex
* The line number
*/
public Token markTokens(Segment line, int lineIndex) {
if (lineIndex >= length) {
throw new IllegalArgumentException("Tokenizing invalid line: " + lineIndex);
}
lastToken = null;
LineInfo info = lineInfo[lineIndex];
LineInfo prev;
if (lineIndex == 0) {
prev = null;
} else {
prev = lineInfo[lineIndex - 1];
}
byte oldToken = info.token;
byte token = markTokensImpl(prev == null ? Token.NULL : prev.token, line, lineIndex);
info.token = token;
/*
* This is a foul hack. It stops nextLineRequested
* from being cleared if the same line is marked twice.
*
* Why is this necessary? It's all JEditTextArea's fault.
* When something is inserted into the text, firing a
* document event, the insertUpdate() method shifts the
* caret (if necessary) by the amount inserted.
*
* All caret movement is handled by the select() method,
* which eventually pipes the new position to scrollTo()
* and calls repaint().
*
* Note that at this point in time, the new line hasn't
* yet been painted; the caret is moved first.
*
* scrollTo() calls offsetToX(), which tokenizes the line
* unless it is being called on the last line painted
* (in which case it uses the text area's painter cached
* token list). What scrollTo() does next is irrelevant.
*
* After scrollTo() has done it's job, repaint() is
* called, and eventually we end up in paintLine(), whose
* job is to paint the changed line. It, too, calls
* markTokens().
*
* The problem was that if the line started a multiline
* token, the first markTokens() (done in offsetToX())
* would set nextLineRequested (because the line end
* token had changed) but the second would clear it
* (because the line was the same that time) and therefore
* paintLine() would never know that it needed to repaint
* subsequent lines.
*
* This bug took me ages to track down, that's why I wrote
* all the relevant info down so that others wouldn't
* duplicate it.
*/
if (!(lastLine == lineIndex && nextLineRequested)) {
nextLineRequested = oldToken != token;
}
lastLine = lineIndex;
addToken(0, Token.END);
return firstToken;
}
/**
* An abstract method that splits a line up into tokens. It should parse the line, and call <code>addToken()</code> to add syntax tokens
* to the token list. Then, it should return the initial token type for the next line.
* <p>
*
* For example if the current line contains the start of a multiline comment that doesn't end on that line, this method should return
* the comment token type so that it continues on the next line.
*
* @param token
* The initial token type for this line
* @param line
* The line to be tokenized
* @param lineIndex
* The index of the line in the document, starting at 0
* @return The initial token type for the next line
*/
protected abstract byte markTokensImpl(byte token, Segment line, int lineIndex);
/**
* Returns if the token marker supports tokens that span multiple lines. If this is true, the object using this token marker is required
* to pass all lines in the document to the <code>markTokens()</code> method (in turn).
* <p>
*
* The default implementation returns true; it should be overridden to return false on simpler token markers for increased speed.
*/
public boolean supportsMultilineTokens() {
return true;
}
/**
* Informs the token marker that lines have been inserted into the document. This inserts a gap in the <code>lineInfo</code> array.
*
* @param index
* The first line number
* @param lines
* The number of lines
*/
public void insertLines(int index, int lines) {
if (lines <= 0) {
return;
}
length += lines;
ensureCapacity(length);
int len = index + lines;
System.arraycopy(lineInfo, index, lineInfo, len, lineInfo.length - len);
for (int i = index + lines - 1; i >= index; i--) {
lineInfo[i] = new LineInfo();
}
}
/**
* Informs the token marker that line have been deleted from the document. This removes the lines in question from the
* <code>lineInfo</code> array.
*
* @param index
* The first line number
* @param lines
* The number of lines
*/
public void deleteLines(int index, int lines) {
if (lines <= 0) {
return;
}
int len = index + lines;
length -= lines;
System.arraycopy(lineInfo, len, lineInfo, index, lineInfo.length - len);
}
/**
* Returns the number of lines in this token marker.
*/
public int getLineCount() {
return length;
}
/**
* Returns true if the next line should be repainted. This will return true after a line has been tokenized that starts a multiline
* token that continues onto the next line.
*/
public boolean isNextLineRequested() {
return nextLineRequested;
}
// protected members
/**
* The first token in the list. This should be used as the return value from <code>markTokens()</code>.
*/
protected Token firstToken;
/**
* The last token in the list. New tokens are added here. This should be set to null before a new line is to be tokenized.
*/
protected Token lastToken;
/**
* An array for storing information about lines. It is enlarged and shrunk automatically by the <code>insertLines()</code> and
* <code>deleteLines()</code> methods.
*/
protected LineInfo[] lineInfo;
/**
* The number of lines in the model being tokenized. This can be less than the length of the <code>lineInfo</code> array.
*/
protected int length;
/**
* The last tokenized line.
*/
protected int lastLine;
/**
* True if the next line should be painted.
*/
protected boolean nextLineRequested;
/**
* Creates a new <code>TokenMarker</code>. This DOES NOT create a lineInfo array; an initial call to <code>insertLines()</code> does
* that.
*/
protected TokenMarker() {
lastLine = -1;
}
/**
* Ensures that the <code>lineInfo</code> array can contain the specified index. This enlarges it if necessary. No action is taken if
* the array is large enough already.
* <p>
*
* It should be unnecessary to call this under normal circumstances; <code>insertLine()</code> should take care of enlarging the line
* info array automatically.
*
* @param index
* The array index
*/
protected void ensureCapacity(int index) {
if (lineInfo == null) {
lineInfo = new LineInfo[index + 1];
} else if (lineInfo.length <= index) {
LineInfo[] lineInfoN = new LineInfo[(index + 1) * 2];
System.arraycopy(lineInfo, 0, lineInfoN, 0, lineInfo.length);
lineInfo = lineInfoN;
}
}
/**
* Adds a token to the token list.
*
* @param length
* The length of the token
* @param id
* The id of the token
*/
protected void addToken(int length, byte id) {
if (id >= Token.INTERNAL_FIRST && id <= Token.INTERNAL_LAST) {
throw new InternalError("Invalid id: " + id);
}
if (length == 0 && id != Token.END) {
return;
}
if (firstToken == null) {
firstToken = new Token(length, id);
lastToken = firstToken;
} else if (lastToken == null) {
lastToken = firstToken;
firstToken.length = length;
firstToken.id = id;
} else if (lastToken.next == null) {
lastToken.next = new Token(length, id);
lastToken = lastToken.next;
} else {
lastToken = lastToken.next;
lastToken.length = length;
lastToken.id = id;
}
}
/**
* Inner class for storing information about tokenized lines.
*/
public class LineInfo {
/**
* Creates a new LineInfo object with token = Token.NULL and obj = null.
*/
public LineInfo() {
}
/**
* Creates a new LineInfo object with the specified parameters.
*/
public LineInfo(byte token, Object obj) {
this.token = token;
this.obj = obj;
}
/**
* The id of the last token of the line.
*/
public byte token;
/**
* This is for use by the token marker implementations themselves. It can be used to store anything that is an object and that needs
* to exist on a per-line basis.
*/
public Object obj;
}
public static TokenMarker makeTokenMarker(TokenMarkerStyle tokenMarkerStyle) {
if (tokenMarkerStyle == TokenMarkerStyle.BatchFile) {
return new BatchFileTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.C) {
return new CTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.CC) {
return new CCTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.IDL) {
return new IDLTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.JavaScript) {
return new JavaScriptTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.Java) {
return new JavaTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.Eiffel) {
return new EiffelTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.HTML) {
return new HTMLTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.Patch) {
return new PatchTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.Perl) {
return new PerlTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.PHP) {
return new PHPTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.Props) {
return new PropsTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.Python) {
return new PythonTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.ShellScript) {
return new ShellScriptTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.SQL) {
return new SQLTokenMarker(null);
} else if (tokenMarkerStyle == TokenMarkerStyle.TSQL) {
return new TSQLTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.TeX) {
return new TeXTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.WOD) {
return new WODTokenMarker();
} else if (tokenMarkerStyle == TokenMarkerStyle.XML) {
return new XMLTokenMarker();
} else {
return null;
}
}
}