/* * TokenMarker.java - Generic token marker * Copyright (C) 1998, 1999 Slava Pestov * * This library 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 of the License, or (at your option) any later version. * * This library 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 library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ package textarea.syntax; import javax.swing.text.Segment; /** * 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$ * * @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 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 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; } } /* * ChangeLog: * $Log$ * Revision 1.2 2005/03/17 21:51:15 davidmartinez * Turning into an eclipse project, global import optimize and warning-busting * * Revision 1.1.1.1 2001/09/07 02:47:54 davidmartinez * Initial Checkin of the Alpha tree * * Revision 1.1.1.1 1999/10/20 00:09:24 david * Initial Import * * Revision 1.2 1999/10/20 00:09:24 david * Ran Dos2Unix on all the .java files within the textarea package. * * Revision 1.1.1.1 1999/10/18 15:00:24 david * Initial Check-in * * Revision 1.28 1999/07/29 08:50:21 sp * Misc stuff for 1.7pre7 * * Revision 1.27 1999/07/16 23:45:49 sp * 1.7pre6 BugFree version * * Revision 1.26 1999/07/05 04:38:39 sp * Massive batch of changes... bug fixes, also new text component is in place. * Have fun * * Revision 1.25 1999/06/06 05:05:25 sp * Search and replace tweaks, Perl/Shell Script mode updates * * Revision 1.24 1999/06/05 00:22:58 sp * LGPL'd syntax package * * Revision 1.23 1999/05/03 08:28:14 sp * Documentation updates, key binding editor, syntax text area bug fix * * Revision 1.22 1999/05/03 04:28:01 sp * Syntax colorizing bug fixing, console bug fix for Swing 1.1.1 * * Revision 1.21 1999/05/02 00:07:21 sp * Syntax system tweaks, console bugfix for Swing 1.1.1 * * Revision 1.20 1999/05/01 00:55:11 sp * Option pane updates (new, easier API), syntax colorizing updates * * Revision 1.19 1999/04/30 23:20:38 sp * Improved colorization of multiline tokens * * Revision 1.18 1999/04/27 06:53:38 sp * JARClassLoader updates, shell script token marker update, token marker compiles * now * */