PageRenderTime 66ms CodeModel.GetById 14ms app.highlight 43ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/textarea/ChunkCache.java

#
Java | 835 lines | 496 code | 95 blank | 244 comment | 105 complexity | 2fb4034ebbc025b803732bb8c414d994 MD5 | raw file
  1/*
  2 * ChunkCache.java - Intermediate layer between token lists from a TokenMarker
  3 * and what you see on screen
  4 * :tabSize=8:indentSize=8:noTabs=false:
  5 * :folding=explicit:collapseFolds=1:
  6 *
  7 * Copyright (C) 2001, 2005 Slava Pestov
  8 *
  9 * This program is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU General Public License
 11 * as published by the Free Software Foundation; either version 2
 12 * of the License, or any later version.
 13 *
 14 * This program is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17 * GNU General Public License for more details.
 18 *
 19 * You should have received a copy of the GNU General Public License
 20 * along with this program; if not, write to the Free Software
 21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 22 */
 23
 24package org.gjt.sp.jedit.textarea;
 25
 26//{{{ Imports
 27import java.util.*;
 28
 29import javax.swing.text.TabExpander;
 30
 31import org.gjt.sp.jedit.buffer.JEditBuffer;
 32import org.gjt.sp.jedit.Debug;
 33import org.gjt.sp.jedit.syntax.*;
 34import org.gjt.sp.util.Log;
 35//}}}
 36
 37/**
 38 * Manages low-level text display tasks - the visible lines in the TextArea.
 39 * The ChunkCache contains an array of LineInfo object.
 40 * Each LineInfo object is associated to one screen line of the TextArea and
 41 * contains informations about this line.
 42 * The array is resized when the TextArea geometry changes  
 43 *
 44 * @author Slava Pestov
 45 * @version $Id: ChunkCache.java 18774 2010-10-14 20:26:12Z shlomy $
 46 */
 47class ChunkCache
 48{
 49	//{{{ ChunkCache constructor
 50	ChunkCache(TextArea textArea)
 51	{
 52		this.textArea = textArea;
 53		out = new ArrayList<Chunk>();
 54		tokenHandler = new DisplayTokenHandler();
 55	} //}}}
 56
 57	//{{{ getMaxHorizontalScrollWidth() method
 58	/**
 59	 * Returns the max line width of the textarea.
 60	 * It will check all lines the first invalid line.
 61	 *
 62	 * @return the max line width
 63	 */
 64	int getMaxHorizontalScrollWidth()
 65	{
 66		int max = 0;
 67		for(int i = 0; i < firstInvalidLine; i++)
 68		{
 69			LineInfo info = lineInfo[i];
 70			if(info.width > max)
 71				max = info.width;
 72		}
 73		return max;
 74	} //}}}
 75
 76	//{{{ getScreenLineOfOffset() method
 77	/**
 78	 * @param line physical line number of document 
 79	 * @param offset number of characters from the left of the line. 
 80	 * @return returns the screen line number where the line and offset are.
 81	 * It returns -1 if this position is not currently visible
 82	 */
 83	int getScreenLineOfOffset(int line, int offset)
 84	{
 85		if(lineInfo.length == 0)
 86			return -1;
 87		if(line < textArea.getFirstPhysicalLine())
 88			return -1;
 89		if(line == textArea.getFirstPhysicalLine()
 90			&& offset < getLineInfo(0).offset)
 91			return -1;
 92		if(line > textArea.getLastPhysicalLine())
 93			return -1;
 94		
 95		if(line == lastScreenLineP)
 96		{
 97			LineInfo last = getLineInfo(lastScreenLine);
 98
 99			if(offset >= last.offset
100				&& offset < last.offset + last.length)
101			{
102				return lastScreenLine;
103			}
104		}
105
106		int screenLine = -1;
107
108		// Find the screen line containing this offset
109		for(int i = 0; i < textArea.getVisibleLines(); i++)
110		{
111			LineInfo info = getLineInfo(i);
112			if(info.physicalLine > line)
113			{
114				// line is invisible?
115				return i - 1;
116				//return -1;
117			}
118			if(info.physicalLine == line)
119			{
120				if(offset >= info.offset
121					&& offset < info.offset + info.length)
122				{
123					screenLine = i;
124					break;
125				}
126			}
127		}
128
129		if(screenLine == -1)
130			return -1;
131
132
133		lastScreenLineP = line;
134		lastScreenLine = screenLine;
135
136		return screenLine;
137	} //}}}
138
139	//{{{ recalculateVisibleLines() method
140	/**
141	 * Recalculate visible lines.
142	 * This is called when the TextArea geometry is changed or when the font is changed.
143	 */
144	void recalculateVisibleLines()
145	{
146		LineInfo[] newLineInfo = new LineInfo[textArea.getVisibleLines()];
147
148		int start;
149		if(lineInfo == null)
150			start = 0;
151		else
152		{
153			start = Math.min(lineInfo.length,newLineInfo.length);
154			System.arraycopy(lineInfo,0,newLineInfo,0,start);
155		}
156
157		for(int i = start; i < newLineInfo.length; i++)
158			newLineInfo[i] = new LineInfo();
159
160		lineInfo = newLineInfo;
161
162		lastScreenLine = lastScreenLineP = -1;
163	} //}}}
164
165	//{{{ setBuffer() method
166	void setBuffer(JEditBuffer buffer)
167	{
168		this.buffer = buffer;
169		lastScreenLine = lastScreenLineP = -1;
170	} //}}}
171
172	//{{{ scrollDown() method
173	void scrollDown(int amount)
174	{
175		int visibleLines = textArea.getVisibleLines();
176
177		System.arraycopy(lineInfo,amount,lineInfo,0,visibleLines - amount);
178
179		for(int i = visibleLines - amount; i < visibleLines; i++)
180		{
181			lineInfo[i] = new LineInfo();
182		}
183
184		firstInvalidLine -= amount;
185		if(firstInvalidLine < 0)
186			firstInvalidLine = 0;
187
188		if(Debug.CHUNK_CACHE_DEBUG)
189		{
190			System.err.println("f > t.f: only " + amount
191				+ " need updates");
192		}
193
194		lastScreenLine = lastScreenLineP = -1;
195	} //}}}
196
197	//{{{ scrollUp() method
198	void scrollUp(int amount)
199	{
200		System.arraycopy(lineInfo,0,lineInfo,amount,
201			textArea.getVisibleLines() - amount);
202
203		for(int i = 0; i < amount; i++)
204		{
205			lineInfo[i] = new LineInfo();
206		}
207
208		// don't try this at home
209		int oldFirstInvalidLine = firstInvalidLine;
210		firstInvalidLine = 0;
211		updateChunksUpTo(amount);
212		firstInvalidLine = oldFirstInvalidLine + amount;
213		if(firstInvalidLine > textArea.getVisibleLines())
214			firstInvalidLine = textArea.getVisibleLines();
215
216		if(Debug.CHUNK_CACHE_DEBUG)
217		{
218			Log.log(Log.DEBUG,this,"f > t.f: only " + amount
219				+ " need updates");
220		}
221
222		lastScreenLine = lastScreenLineP = -1;
223	} //}}}
224
225	//{{{ invalidateAll() method
226	void invalidateAll()
227	{
228		firstInvalidLine = 0;
229		lastScreenLine = lastScreenLineP = -1;
230	} //}}}
231
232	//{{{ invalidateChunksFromPhys() method
233	void invalidateChunksFromPhys(int physicalLine)
234	{
235		for(int i = 0; i < firstInvalidLine; i++)
236		{
237			LineInfo info = lineInfo[i];
238			if(info.physicalLine == -1 || info.physicalLine >= physicalLine)
239			{
240				firstInvalidLine = i;
241				if(i <= lastScreenLine)
242					lastScreenLine = lastScreenLineP = -1;
243				break;
244			}
245		}
246	} //}}}
247
248	//{{{ getLineInfo() method
249	/**
250	 * Returns the line informations for a given screen line
251	 * @param screenLine the screen line
252	 * @return the LineInfo for the screenLine
253	 */
254	LineInfo getLineInfo(int screenLine)
255	{
256		updateChunksUpTo(screenLine);
257		return lineInfo[screenLine];
258	} //}}}
259
260	//{{{ getLineSubregionCount() method
261	/**
262	 * Returns the number of subregions of a physical line
263	 * @param physicalLine a physical line
264	 * @return the number of subregions of this physical line
265	 */
266	int getLineSubregionCount(int physicalLine)
267	{
268		if(!textArea.softWrap)
269			return 1;
270
271		out.clear();
272		lineToChunkList(physicalLine,out);
273
274		int size = out.size();
275		if(size == 0)
276			return 1;
277		else
278			return size;
279	} //}}}
280
281	//{{{ getSubregionOfOffset() method
282	/**
283	 * Returns the subregion containing the specified offset. A subregion
284	 * is a subset of a physical line. Each screen line corresponds to one
285	 * subregion. Unlike the {@link #getScreenLineOfOffset(int, int)} method,
286	 * this method works with non-visible lines too.
287	 *
288	 * @param offset the offset
289	 * @param lineInfos a lineInfos array. Usualy the array is the result of
290	 *	{@link #getLineInfosForPhysicalLine(int)} call
291	 *
292	 * @return the subregion of the offset, or -1 if the offset was not in one of the given lineInfos
293	 */
294	static int getSubregionOfOffset(int offset, LineInfo[] lineInfos)
295	{
296		for(int i = 0; i < lineInfos.length; i++)
297		{
298			LineInfo info = lineInfos[i];
299			if(offset >= info.offset && offset < info.offset + info.length)
300				return i;
301		}
302
303		return -1;
304	} //}}}
305
306	//{{{ xToSubregionOffset() method
307	/**
308	 * Converts an x co-ordinate within a subregion into an offset from the
309	 * start of that subregion.
310	 * @param physicalLine The physical line number
311	 * @param subregion The subregion; if -1, then this is the last
312	 * subregion.
313	 * @param x The x co-ordinate
314	 * @param round Round up to next character if x is past the middle of a
315	 * character?
316	 * @return the offset from the start of the subregion
317	 */
318	int xToSubregionOffset(int physicalLine, int subregion, int x,
319		boolean round)
320	{
321		LineInfo[] infos = getLineInfosForPhysicalLine(physicalLine);
322		if(subregion == -1)
323			subregion += infos.length;
324		return xToSubregionOffset(infos[subregion],x,round);
325	} //}}}
326
327	//{{{ xToSubregionOffset() method
328	/**
329	 * Converts an x co-ordinate within a subregion into an offset from the
330	 * start of that subregion.
331	 * @param info The line info object
332	 * @param x The x co-ordinate
333	 * @param round Round up to next character if x is past the middle of a
334	 * character?
335	 * @return the offset from the start of the subregion
336	 */
337	static int xToSubregionOffset(LineInfo info, int x,
338		boolean round)
339	{
340		int offset = Chunk.xToOffset(info.chunks,x,round);
341		if(offset == -1 || offset == info.offset + info.length)
342			offset = info.offset + info.length - 1;
343
344		return offset;
345	} //}}}
346
347	//{{{ subregionOffsetToX() method
348	/**
349	 * Converts an offset within a subregion into an x co-ordinate.
350	 * @param physicalLine The physical line
351	 * @param offset The offset
352	 * @return the x co-ordinate of the offset within a subregion
353	 */
354	int subregionOffsetToX(int physicalLine, int offset)
355	{
356		LineInfo[] infos = getLineInfosForPhysicalLine(physicalLine);
357		LineInfo info = infos[getSubregionOfOffset(offset,infos)];
358		return subregionOffsetToX(info,offset);
359	} //}}}
360
361	//{{{ subregionOffsetToX() method
362	/**
363	 * Converts an offset within a subregion into an x co-ordinate.
364	 * @param info The line info object
365	 * @param offset The offset
366	 * @return the x co-ordinate of the offset within a subregion
367	 */
368	static int subregionOffsetToX(LineInfo info, int offset)
369	{
370		return (int)Chunk.offsetToX(info.chunks,offset);
371	} //}}}
372
373	//{{{ getSubregionStartOffset() method
374	/**
375	 * Returns the start offset of the specified subregion of the specified
376	 * physical line.
377	 * @param line The physical line number
378	 * @param offset An offset
379	 * @return the start offset of the subregion of the line
380	 */
381	int getSubregionStartOffset(int line, int offset)
382	{
383		LineInfo[] lineInfos = getLineInfosForPhysicalLine(line);
384		LineInfo info = lineInfos[getSubregionOfOffset(offset,lineInfos)];
385		return textArea.getLineStartOffset(info.physicalLine)
386			+ info.offset;
387	} //}}}
388
389	//{{{ getSubregionEndOffset() method
390	/**
391	 * Returns the end offset of the specified subregion of the specified
392	 * physical line.
393	 * @param line The physical line number
394	 * @param offset An offset
395	 * @return the end offset of the subregion of the line
396	 */
397	int getSubregionEndOffset(int line, int offset)
398	{
399		LineInfo[] lineInfos = getLineInfosForPhysicalLine(line);
400		LineInfo info = lineInfos[getSubregionOfOffset(offset,lineInfos)];
401		return textArea.getLineStartOffset(info.physicalLine)
402			+ info.offset + info.length;
403	} //}}}
404
405	//{{{ getBelowPosition() method
406	/**
407	 * @param physicalLine The physical line number
408	 * @param offset The offset
409	 * @param x The location
410	 * @param ignoreWrap If true, behave as if soft wrap is off even if it
411	 * is on
412	 */
413	int getBelowPosition(int physicalLine, int offset, int x,
414		boolean ignoreWrap)
415	{
416		LineInfo[] lineInfos = getLineInfosForPhysicalLine(physicalLine);
417
418		int subregion = getSubregionOfOffset(offset,lineInfos);
419
420		if(subregion != lineInfos.length - 1 && !ignoreWrap)
421		{
422			return textArea.getLineStartOffset(physicalLine)
423				+ xToSubregionOffset(lineInfos[subregion + 1],
424				x,true);
425		}
426		else
427		{
428			int nextLine = textArea.displayManager
429				.getNextVisibleLine(physicalLine);
430
431			if(nextLine == -1)
432				return -1;
433			else
434			{
435				return textArea.getLineStartOffset(nextLine)
436					+ xToSubregionOffset(nextLine,0,
437					x,true);
438			}
439		}
440	} //}}}
441
442	//{{{ getAbovePosition() method
443	/**
444	 * @param physicalLine The physical line number
445	 * @param offset The offset
446	 * @param x The location
447	 * @param ignoreWrap If true, behave as if soft wrap is off even if it
448	 * is on
449	 */
450	int getAbovePosition(int physicalLine, int offset, int x,
451		boolean ignoreWrap)
452	{
453		LineInfo[] lineInfos = getLineInfosForPhysicalLine(physicalLine);
454
455		int subregion = getSubregionOfOffset(offset,lineInfos);
456
457		if(subregion != 0 && !ignoreWrap)
458		{
459			return textArea.getLineStartOffset(physicalLine)
460				+ xToSubregionOffset(lineInfos[subregion - 1],
461				x,true);
462		}
463		else
464		{
465			int prevLine = textArea.displayManager
466				.getPrevVisibleLine(physicalLine);
467
468			if(prevLine == -1)
469				return -1;
470			else
471			{
472				return textArea.getLineStartOffset(prevLine)
473					+ xToSubregionOffset(prevLine,-1,
474					x,true);
475			}
476		}
477	} //}}}
478
479	//{{{ needFullRepaint() method
480	/**
481	 * The needFullRepaint variable becomes true when the number of screen
482	 * lines in a physical line changes.
483	 * @return true if the TextArea needs full repaint
484	 */
485	boolean needFullRepaint()
486	{
487		boolean retVal = needFullRepaint;
488		needFullRepaint = false;
489		return retVal;
490	} //}}}
491
492	//{{{ getLineInfosForPhysicalLine() method
493	LineInfo[] getLineInfosForPhysicalLine(int physicalLine)
494	{
495		out.clear();
496
497		if(!buffer.isLoading())
498			lineToChunkList(physicalLine,out);
499
500		if(out.isEmpty())
501			out.add(null);
502
503		List<LineInfo> returnValue = new ArrayList<LineInfo>(out.size());
504		getLineInfosForPhysicalLine(physicalLine,returnValue);
505		return returnValue.toArray(new LineInfo[out.size()]);
506	} //}}}
507
508	//{{{ Private members
509
510	//{{{ Instance variables
511	private final TextArea textArea;
512	private JEditBuffer buffer;
513	/**
514	 * The lineInfo array. There is LineInfo for each line that is visible in the textArea.
515	 * it can be resized by {@link #recalculateVisibleLines()}.
516	 * The content is valid from 0 to {@link #firstInvalidLine}
517	 */
518	private LineInfo[] lineInfo;
519	private final List<Chunk> out;
520
521	/** The first invalid line. All lines before this one are valid. */
522	private int firstInvalidLine;
523	private int lastScreenLineP;
524	private int lastScreenLine;
525
526	private boolean needFullRepaint;
527
528	private final DisplayTokenHandler tokenHandler;
529	//}}}
530
531	//{{{ getLineInfosForPhysicalLine() method
532	private void getLineInfosForPhysicalLine(int physicalLine, List<LineInfo> list)
533	{
534		for(int i = 0; i < out.size(); i++)
535		{
536			Chunk chunks = out.get(i);
537			LineInfo info = new LineInfo();
538			info.physicalLine = physicalLine;
539			if(i == 0)
540			{
541				info.firstSubregion = true;
542				info.offset = 0;
543			}
544			else
545				info.offset = chunks.offset;
546
547			if(i == out.size() - 1)
548			{
549				info.lastSubregion = true;
550				info.length = textArea.getLineLength(physicalLine)
551					- info.offset + 1;
552			}
553			else
554			{
555				info.length = out.get(i + 1).offset
556					- info.offset;
557			}
558
559			info.chunks = chunks;
560
561			list.add(info);
562		}
563	} //}}}
564
565	//{{{ getFirstScreenLine() method
566	/**
567	 * Find a valid line closest to the last screen line.
568	 */
569	private int getFirstScreenLine()
570	{
571		for(int i = firstInvalidLine - 1; i >= 0; i--)
572		{
573			if(lineInfo[i].lastSubregion)
574				return i + 1;
575		}
576
577		return 0;
578	} //}}}
579
580	//{{{ getUpdateStartLine() method
581	/**
582	 * Return a physical line number.
583	 */
584	private int getUpdateStartLine(int firstScreenLine)
585	{
586		// for the first line displayed, take its physical line to be
587		// the text area's first physical line
588		if(firstScreenLine == 0)
589		{
590			return textArea.getFirstPhysicalLine();
591		}
592		// otherwise, determine the next visible line
593		else
594		{
595			int prevPhysLine = lineInfo[
596				firstScreenLine - 1]
597				.physicalLine;
598			// if -1, the empty space at the end of the text area
599			// when the buffer has less lines than are visible
600			if(prevPhysLine == -1)
601				return -1;
602			else
603			{
604				return textArea.displayManager
605					.getNextVisibleLine(prevPhysLine);
606			}
607		}
608	} //}}}
609
610	//{{{ updateChunksUpTo() method
611	private void updateChunksUpTo(int lastScreenLine)
612	{
613		// this method is a nightmare
614		if(lastScreenLine >= lineInfo.length)
615			throw new ArrayIndexOutOfBoundsException(lastScreenLine);
616
617		// if one line's chunks are invalid, remaining lines are also
618		// invalid
619		if(lastScreenLine < firstInvalidLine)
620			return;
621
622		int firstScreenLine = getFirstScreenLine();
623		int physicalLine = getUpdateStartLine(firstScreenLine);
624
625		if(Debug.CHUNK_CACHE_DEBUG)
626		{
627			Log.log(Log.DEBUG,this,"Updating chunks from " + firstScreenLine
628				+ " to " + lastScreenLine);
629		}
630
631		// Note that we rely on the fact that when a physical line is
632		// invalidated, all screen lines/subregions of that line are
633		// invalidated as well. See below comment for code that tries
634		// to uphold this assumption.
635
636		out.clear();
637
638		int offset;
639		int length;
640
641		for(int i = firstScreenLine; i <= lastScreenLine; i++)
642		{
643			LineInfo info = lineInfo[i];
644
645			Chunk chunks;
646
647			// get another line of chunks
648			if(out.isEmpty())
649			{
650				// unless this is the first time, increment
651				// the line number
652				if(physicalLine != -1 && i != firstScreenLine)
653				{
654					physicalLine = textArea.displayManager
655						.getNextVisibleLine(physicalLine);
656				}
657
658				// empty space
659				if(physicalLine == -1)
660				{
661					info.chunks = null;
662					info.physicalLine = -1;
663					// fix the bug where the horiz.
664					// scroll bar was not updated
665					// after creating a new file.
666					info.width = 0;
667					continue;
668				}
669
670				// chunk the line.
671				lineToChunkList(physicalLine,out);
672
673				info.firstSubregion = true;
674
675				// if the line has no text, out.size() == 0
676				if(out.isEmpty())
677				{
678					if(i == 0)
679					{
680						if(textArea.displayManager.firstLine.skew > 0)
681						{
682							Log.log(Log.ERROR,this,"BUG: skew=" + textArea.displayManager.firstLine.skew + ",out.size()=" + out.size());
683							textArea.displayManager.firstLine.skew = 0;
684							needFullRepaint = true;
685							lastScreenLine = lineInfo.length - 1;
686						}
687					}
688					chunks = null;
689					offset = 0;
690					length = 1;
691				}
692				// otherwise, the number of subregions
693				else
694				{
695					if(i == 0)
696					{
697						int skew = textArea.displayManager.firstLine.skew;
698						if(skew >= out.size())
699						{
700							// The skew cannot be greater than the chunk count of the line
701							// we need at least one chunk per subregion in a line 
702							Log.log(Log.ERROR,this,"BUG: skew=" + skew + ",out.size()=" + out.size());
703							needFullRepaint = true;
704							lastScreenLine = lineInfo.length - 1;
705						}
706						else if(skew > 0)
707						{
708							info.firstSubregion = false;
709							for(int j = 0; j < skew; j++)
710								out.remove(0);
711						}
712					}
713					chunks = out.remove(0);
714					offset = chunks.offset;
715					if (!out.isEmpty())
716						length = out.get(0).offset - offset;
717					else
718						length = textArea.getLineLength(physicalLine) - offset + 1;
719				}
720			}
721			else
722			{
723				info.firstSubregion = false;
724
725				chunks = out.remove(0);
726				offset = chunks.offset;
727				if (!out.isEmpty())
728					length = out.get(0).offset - offset;
729				else
730					length = textArea.getLineLength(physicalLine) - offset + 1;
731			}
732
733			boolean lastSubregion = out.isEmpty();
734
735			if(i == lastScreenLine
736				&& lastScreenLine != lineInfo.length - 1)
737			{
738				/* if the user changes the syntax token at the
739				 * end of a line, need to do a full repaint. */
740				if(tokenHandler.getLineContext() !=
741					info.lineContext)
742				{
743					lastScreenLine++;
744					needFullRepaint = true;
745				}
746				/* If this line has become longer or shorter
747				 * (in which case the new physical line number
748				 * is different from the cached one) we need to:
749				 * - continue updating past the last line
750				 * - advise the text area to repaint
751				 * On the other hand, if the line wraps beyond
752				 * lastScreenLine, we need to keep updating the
753				 * chunk list to ensure proper alignment of
754				 * invalidation flags (see start of method) */
755				else if(info.physicalLine != physicalLine
756					|| info.lastSubregion != lastSubregion)
757				{
758					lastScreenLine++;
759					needFullRepaint = true;
760				}
761				/* We only cache entire physical lines at once;
762				 * don't want to split a physical line into
763				 * screen lines and only have some valid. */
764				else if (!out.isEmpty())
765					lastScreenLine++;
766			}
767
768			info.physicalLine = physicalLine;
769			info.lastSubregion = lastSubregion;
770			info.offset = offset;
771			info.length = length;
772			info.chunks = chunks;
773			info.lineContext = tokenHandler.getLineContext();
774		}
775
776		firstInvalidLine = Math.max(lastScreenLine + 1,firstInvalidLine);
777	} //}}}
778
779	//{{{ lineToChunkList() method
780	private void lineToChunkList(int physicalLine, List<Chunk> out)
781	{
782		TextAreaPainter painter = textArea.getPainter();
783		TabExpander expander= textArea.getTabExpander();
784		tokenHandler.init(painter.getStyles(),
785			painter.getFontRenderContext(),
786			expander,out,
787			textArea.softWrap
788			? textArea.wrapMargin : 0.0f, buffer.getLineStartOffset(physicalLine));
789		buffer.markTokens(physicalLine,tokenHandler);
790	} //}}}
791
792	//}}}
793
794	//{{{ LineInfo class
795	/**
796	 * The informations on a line. (for fast access)
797	 * When using softwrap, a line is divided in n
798	 * subregions.
799	 */
800	static class LineInfo
801	{
802		/**
803		 * The physical line.
804		 */
805		int physicalLine;
806		/**
807		 * The offset where begins the line.
808		 */
809		int offset;
810		/**
811		 * The line length.
812		 */
813		int length;
814		/**
815		 * true if it is the first subregion of a line.
816		 */
817		boolean firstSubregion;
818		/**
819		 * True if it is the last subregion of a line.
820		 */
821		boolean lastSubregion;
822		Chunk chunks;
823		/** The line width. */
824		int width;
825		TokenMarker.LineContext lineContext;
826
827		@Override
828		public String toString()
829		{
830			return "LineInfo[" + physicalLine + ',' + offset + ','
831			       + length + ',' + firstSubregion + ',' +
832			       lastSubregion + "]";
833		}
834	} //}}}
835}