001: /*
002: * Copyright (C) 2005 - 2008 JasperSoft Corporation. All rights reserved.
003: * http://www.jaspersoft.com.
004: *
005: * Unless you have purchased a commercial license agreement from JasperSoft,
006: * the following license terms apply:
007: *
008: * This program is free software; you can redistribute it and/or modify
009: * it under the terms of the GNU General Public License version 2 as published by
010: * the Free Software Foundation.
011: *
012: * This program is distributed WITHOUT ANY WARRANTY; and without the
013: * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
014: * See the GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, see http://www.gnu.org/licenses/gpl.txt
018: * or write to:
019: *
020: * Free Software Foundation, Inc.,
021: * 59 Temple Place - Suite 330,
022: * Boston, MA USA 02111-1307
023: *
024: *
025: *
026: *
027: * TokenMarker.java
028: *
029: */
030:
031: package org.syntax.jedit.tokenmarker;
032:
033: import org.syntax.jedit.*;
034:
035: import javax.swing.text.Segment;
036: import java.util.*;
037:
038: /**
039: * A token marker that splits lines of text into tokens. Each token carries
040: * a length field and an indentification tag that can be mapped to a color
041: * for painting that token.<p>
042: *
043: * For performance reasons, the linked list of tokens is reused after each
044: * line is tokenized. Therefore, the return value of <code>markTokens</code>
045: * should only be used for immediate painting. Notably, it cannot be
046: * cached.
047: *
048: * @author Slava Pestov
049: * @version $Id: TokenMarker.java 1167 2008-01-15 18:49:05Z gtoffoli $
050: * @version $Id: TokenMarker.java 1167 2008-01-15 18:49:05Z gtoffoli $
051: * @see org.syntax.jedit.Token
052: */
053: public abstract class TokenMarker {
054: /**
055: * A wrapper for the lower-level <code>markTokensImpl</code> method
056: * that is called to split a line up into tokens.
057: * @param line The line
058: * @param lineIndex The line number
059: */
060: public Token markTokens(Segment line, int lineIndex) {
061: if (lineIndex >= length) {
062: throw new IllegalArgumentException(
063: "Tokenizing invalid line: " + lineIndex);
064: }
065:
066: lastToken = null;
067:
068: LineInfo info = lineInfo[lineIndex];
069: LineInfo prev;
070: if (lineIndex == 0)
071: prev = null;
072: else
073: prev = lineInfo[lineIndex - 1];
074:
075: byte oldToken = info.token;
076: byte token = markTokensImpl(prev == null ? Token.NULL
077: : prev.token, line, lineIndex);
078:
079: info.token = token;
080:
081: /*
082: * This is a foul hack. It stops nextLineRequested
083: * from being cleared if the same line is marked twice.
084: *
085: * Why is this necessary? It's all JEditTextArea's fault.
086: * When something is inserted into the text, firing a
087: * document event, the insertUpdate() method shifts the
088: * caret (if necessary) by the amount inserted.
089: *
090: * All caret movement is handled by the select() method,
091: * which eventually pipes the new position to scrollTo()
092: * and calls repaint().
093: *
094: * Note that at this point in time, the new line hasn't
095: * yet been painted; the caret is moved first.
096: *
097: * scrollTo() calls offsetToX(), which tokenizes the line
098: * unless it is being called on the last line painted
099: * (in which case it uses the text area's painter cached
100: * token list). What scrollTo() does next is irrelevant.
101: *
102: * After scrollTo() has done it's job, repaint() is
103: * called, and eventually we end up in paintLine(), whose
104: * job is to paint the changed line. It, too, calls
105: * markTokens().
106: *
107: * The problem was that if the line started a multiline
108: * token, the first markTokens() (done in offsetToX())
109: * would set nextLineRequested (because the line end
110: * token had changed) but the second would clear it
111: * (because the line was the same that time) and therefore
112: * paintLine() would never know that it needed to repaint
113: * subsequent lines.
114: *
115: * This bug took me ages to track down, that's why I wrote
116: * all the relevant info down so that others wouldn't
117: * duplicate it.
118: */
119: if (!(lastLine == lineIndex && nextLineRequested))
120: nextLineRequested = (oldToken != token);
121:
122: lastLine = lineIndex;
123:
124: addToken(0, Token.END);
125:
126: return firstToken;
127: }
128:
129: /**
130: * An abstract method that splits a line up into tokens. It
131: * should parse the line, and call <code>addToken()</code> to
132: * add syntax tokens to the token list. Then, it should return
133: * the initial token type for the next line.<p>
134: *
135: * For example if the current line contains the start of a
136: * multiline comment that doesn't end on that line, this method
137: * should return the comment token type so that it continues on
138: * the next line.
139: *
140: * @param token The initial token type for this line
141: * @param line The line to be tokenized
142: * @param lineIndex The index of the line in the document,
143: * starting at 0
144: * @return The initial token type for the next line
145: */
146: protected abstract byte markTokensImpl(byte token, Segment line,
147: int lineIndex);
148:
149: /**
150: * Returns if the token marker supports tokens that span multiple
151: * lines. If this is true, the object using this token marker is
152: * required to pass all lines in the document to the
153: * <code>markTokens()</code> method (in turn).<p>
154: *
155: * The default implementation returns true; it should be overridden
156: * to return false on simpler token markers for increased speed.
157: */
158: public boolean supportsMultilineTokens() {
159: return true;
160: }
161:
162: /**
163: * Informs the token marker that lines have been inserted into
164: * the document. This inserts a gap in the <code>lineInfo</code>
165: * array.
166: * @param index The first line number
167: * @param lines The number of lines
168: */
169: public void insertLines(int index, int lines) {
170: if (lines <= 0)
171: return;
172: length += lines;
173: ensureCapacity(length);
174: int len = index + lines;
175: System.arraycopy(lineInfo, index, lineInfo, len,
176: lineInfo.length - len);
177:
178: for (int i = index + lines - 1; i >= index; i--) {
179: lineInfo[i] = new LineInfo();
180: }
181: }
182:
183: /**
184: * Informs the token marker that line have been deleted from
185: * the document. This removes the lines in question from the
186: * <code>lineInfo</code> array.
187: * @param index The first line number
188: * @param lines The number of lines
189: */
190: public void deleteLines(int index, int lines) {
191: if (lines <= 0)
192: return;
193: int len = index + lines;
194: length -= lines;
195: System.arraycopy(lineInfo, len, lineInfo, index,
196: lineInfo.length - len);
197: }
198:
199: /**
200: * Returns the number of lines in this token marker.
201: */
202: public int getLineCount() {
203: return length;
204: }
205:
206: /**
207: * Returns true if the next line should be repainted. This
208: * will return true after a line has been tokenized that starts
209: * a multiline token that continues onto the next line.
210: */
211: public boolean isNextLineRequested() {
212: return nextLineRequested;
213: }
214:
215: // protected members
216:
217: /**
218: * The first token in the list. This should be used as the return
219: * value from <code>markTokens()</code>.
220: */
221: protected Token firstToken;
222:
223: /**
224: * The last token in the list. New tokens are added here.
225: * This should be set to null before a new line is to be tokenized.
226: */
227: protected Token lastToken;
228:
229: /**
230: * An array for storing information about lines. It is enlarged and
231: * shrunk automatically by the <code>insertLines()</code> and
232: * <code>deleteLines()</code> methods.
233: */
234: protected LineInfo[] lineInfo;
235:
236: /**
237: * The number of lines in the model being tokenized. This can be
238: * less than the length of the <code>lineInfo</code> array.
239: */
240: protected int length;
241:
242: /**
243: * The last tokenized line.
244: */
245: protected int lastLine;
246:
247: /**
248: * True if the next line should be painted.
249: */
250: protected boolean nextLineRequested;
251:
252: protected KeywordLookupIF keywordLookup;
253:
254: /**
255: * Creates a new <code>TokenMarker</code>. This DOES NOT create
256: * a lineInfo array; an initial call to <code>insertLines()</code>
257: * does that.
258: */
259: protected TokenMarker() {
260: lastLine = -1;
261: }
262:
263: /**
264: * Ensures that the <code>lineInfo</code> array can contain the
265: * specified index. This enlarges it if necessary. No action is
266: * taken if the array is large enough already.<p>
267: *
268: * It should be unnecessary to call this under normal
269: * circumstances; <code>insertLine()</code> should take care of
270: * enlarging the line info array automatically.
271: *
272: * @param index The array index
273: */
274: protected void ensureCapacity(int index) {
275: if (lineInfo == null)
276: lineInfo = new LineInfo[index + 1];
277: else if (lineInfo.length <= index) {
278: LineInfo[] lineInfoN = new LineInfo[(index + 1) * 2];
279: System
280: .arraycopy(lineInfo, 0, lineInfoN, 0,
281: lineInfo.length);
282: lineInfo = lineInfoN;
283: }
284: }
285:
286: /**
287: * Adds a token to the token list.
288: * @param length The length of the token
289: * @param id The id of the token
290: */
291: protected void addToken(int length, byte id) {
292: if (id >= Token.INTERNAL_FIRST && id <= Token.INTERNAL_LAST)
293: throw new InternalError("Invalid id: " + id);
294:
295: if (length == 0 && id != Token.END)
296: return;
297:
298: if (firstToken == null) {
299: firstToken = new Token(length, id);
300: lastToken = firstToken;
301: } else if (lastToken == null) {
302: lastToken = firstToken;
303: firstToken.length = length;
304: firstToken.id = id;
305: } else if (lastToken.next == null) {
306: lastToken.next = new Token(length, id);
307: lastToken = lastToken.next;
308: } else {
309: lastToken = lastToken.next;
310: lastToken.length = length;
311: lastToken.id = id;
312: }
313: }
314:
315: /**
316: * Inner class for storing information about tokenized lines.
317: */
318: public class LineInfo {
319: /**
320: * Creates a new LineInfo object with token = Token.NULL
321: * and obj = null.
322: */
323: public LineInfo() {
324: }
325:
326: /**
327: * Creates a new LineInfo object with the specified
328: * parameters.
329: */
330: public LineInfo(byte token, Object obj) {
331: this .token = token;
332: this .obj = obj;
333: }
334:
335: /**
336: * The id of the last token of the line.
337: */
338: public byte token;
339:
340: /**
341: * This is for use by the token marker implementations
342: * themselves. It can be used to store anything that
343: * is an object and that needs to exist on a per-line
344: * basis.
345: */
346: public Object obj;
347: }
348:
349: /** Add your own keywords.
350: */
351: public void setKeywordLookup(KeywordLookupIF keywordLookup) {
352: this.keywordLookup = keywordLookup;
353: }
354: }
|