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