2005-08-25 23:06:28 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package processing.app.syntax;
|
|
|
|
|
|
|
|
import javax.swing.text.Segment;
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
2006-01-13 00:24:12 +01:00
|
|
|
* @version $Id$
|
2005-08-25 23:06:28 +02:00
|
|
|
*
|
|
|
|
* @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;
|
|
|
|
}
|
|
|
|
}
|