All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.syntax.jedit.tokenmarker.TokenMarker Maven / Gradle / Ivy

The newest version!
/*
 *  soapUI, copyright (C) 2004-2011 smartbear.com 
 *
 *  soapUI is free software; you can redistribute it and/or modify it under the 
 *  terms of version 2.1 of the GNU Lesser General Public License as published by 
 *  the Free Software Foundation.
 *
 *  soapUI 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 at gnu.org.
 */

package org.syntax.jedit.tokenmarker;

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.
 * 

* * For performance reasons, the linked list of tokens is reused after each line * is tokenized. Therefore, the return value of markTokens should * only be used for immediate painting. Notably, it cannot be cached. * * @author Slava Pestov * @version $Id$ * * @see org.syntax.jedit.Token */ public abstract class TokenMarker { /** * A wrapper for the lower-level markTokensImpl 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 addToken() to add syntax tokens to the token * list. Then, it should return the initial token type for the next line. *

* * 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 markTokens() method (in turn). *

* * 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 lineInfo 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 lineInfo 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 * markTokens(). */ 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 insertLines() and * deleteLines() methods. */ protected LineInfo[] lineInfo; /** * The number of lines in the model being tokenized. This can be less than * the length of the lineInfo 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 TokenMarker. This DOES NOT create a lineInfo * array; an initial call to insertLines() does that. */ protected TokenMarker() { lastLine = -1; } /** * Ensures that the lineInfo array can contain the specified * index. This enlarges it if necessary. No action is taken if the array is * large enough already. *

* * It should be unnecessary to call this under normal circumstances; * insertLine() 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy