PageRenderTime 219ms CodeModel.GetById 196ms app.highlight 19ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre4/org/gjt/sp/jedit/textarea/ChunkCache.java

#
Java | 749 lines | 498 code | 94 blank | 157 comment | 107 complexity | ec03ae686f2653232f9ceecb50d56181 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, 2003 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.*;
 28import org.gjt.sp.jedit.Buffer;
 29import org.gjt.sp.jedit.Debug;
 30import org.gjt.sp.jedit.syntax.*;
 31import org.gjt.sp.util.Log;
 32//}}}
 33
 34/**
 35 * Manages low-level text display tasks.
 36 *
 37 * @author Slava Pestov
 38 * @version $Id: ChunkCache.java 4765 2003-06-05 20:37:02Z spestov $
 39 */
 40class ChunkCache
 41{
 42	//{{{ ChunkCache constructor
 43	ChunkCache(JEditTextArea textArea)
 44	{
 45		this.textArea = textArea;
 46		out = new ArrayList();
 47		tokenHandler = new DisplayTokenHandler();
 48	} //}}}
 49
 50	//{{{ getMaxHorizontalScrollWidth() method
 51	int getMaxHorizontalScrollWidth()
 52	{
 53		int max = 0;
 54		for(int i = 0; i < firstInvalidLine; i++)
 55		{
 56			LineInfo info = lineInfo[i];
 57			if(info.width > max)
 58				max = info.width;
 59		}
 60		return max;
 61	} //}}}
 62
 63	//{{{ getScreenLineOfOffset() method
 64	int getScreenLineOfOffset(int line, int offset)
 65	{
 66		if(line < textArea.getFirstPhysicalLine())
 67		{
 68			return -1;
 69		}
 70		else if(line > textArea.getLastPhysicalLine())
 71		{
 72			return -1;
 73		}
 74		else
 75		{
 76			int screenLine;
 77
 78			if(line == lastScreenLineP)
 79			{
 80				LineInfo last = getLineInfo(lastScreenLine);
 81
 82				if(offset >= last.offset
 83					&& offset < last.offset + last.length)
 84				{
 85					return lastScreenLine;
 86				}
 87			}
 88
 89			screenLine = -1;
 90
 91			// Find the screen line containing this offset
 92			for(int i = 0; i < textArea.getVisibleLines(); i++)
 93			{
 94				LineInfo info = getLineInfo(i);
 95				if(info.physicalLine > line)
 96				{
 97					// line is invisible?
 98					return -1;
 99				}
100				else if(info.physicalLine == line)
101				{
102					if(offset >= info.offset
103						&& offset < info.offset + info.length)
104					{
105						screenLine = i;
106						break;
107					}
108				}
109			}
110
111			if(screenLine == -1)
112				return -1;
113			else
114			{
115				lastScreenLineP = line;
116				lastScreenLine = screenLine;
117
118				return screenLine;
119			}
120		}
121	} //}}}
122
123	//{{{ recalculateVisibleLines() method
124	void recalculateVisibleLines()
125	{
126		LineInfo[] newLineInfo = new LineInfo[textArea.getVisibleLines()];
127
128		int start;
129		if(lineInfo == null)
130			start = 0;
131		else
132		{
133			start = Math.min(lineInfo.length,newLineInfo.length);
134			System.arraycopy(lineInfo,0,newLineInfo,0,start);
135		}
136
137		for(int i = start; i < newLineInfo.length; i++)
138			newLineInfo[i] = new LineInfo();
139
140		lineInfo = newLineInfo;
141
142		lastScreenLine = lastScreenLineP = -1;
143	} //}}}
144
145	//{{{ setBuffer() method
146	void setBuffer(Buffer buffer)
147	{
148		this.buffer = buffer;
149		lastScreenLine = lastScreenLineP = -1;
150	} //}}}
151
152	//{{{ scrollDown() method
153	void scrollDown(int amount)
154	{
155		int visibleLines = textArea.getVisibleLines();
156
157		System.arraycopy(lineInfo,amount,lineInfo,0,visibleLines - amount);
158
159		for(int i = visibleLines - amount; i < visibleLines; i++)
160		{
161			lineInfo[i] = new LineInfo();
162		}
163
164		firstInvalidLine -= amount;
165		if(firstInvalidLine < 0)
166			firstInvalidLine = 0;
167
168		if(Debug.CHUNK_CACHE_DEBUG)
169		{
170			System.err.println("f > t.f: only " + amount
171				+ " need updates");
172		}
173
174		lastScreenLine = lastScreenLineP = -1;
175	} //}}}
176
177	//{{{ scrollUp() method
178	void scrollUp(int amount)
179	{
180		System.arraycopy(lineInfo,0,lineInfo,amount,
181			textArea.getVisibleLines() - amount);
182
183		for(int i = 0; i < amount; i++)
184		{
185			lineInfo[i] = new LineInfo();
186		}
187
188		// don't try this at home
189		int oldFirstInvalidLine = firstInvalidLine;
190		firstInvalidLine = 0;
191		updateChunksUpTo(amount);
192		firstInvalidLine = oldFirstInvalidLine + amount;
193		if(firstInvalidLine > textArea.getVisibleLines())
194			firstInvalidLine = textArea.getVisibleLines();
195
196		if(Debug.CHUNK_CACHE_DEBUG)
197		{
198			System.err.println("f > t.f: only " + amount
199				+ " need updates");
200		}
201
202		lastScreenLine = lastScreenLineP = -1;
203	} //}}}
204
205	//{{{ invalidateAll() method
206	void invalidateAll()
207	{
208		firstInvalidLine = 0;
209		lastScreenLine = lastScreenLineP = -1;
210	} //}}}
211
212	//{{{ invalidateChunksFrom() method
213	void invalidateChunksFrom(int screenLine)
214	{
215		firstInvalidLine = Math.min(screenLine,firstInvalidLine);
216
217		if(screenLine <= lastScreenLine)
218			lastScreenLine = lastScreenLineP = -1;
219	} //}}}
220
221	//{{{ invalidateChunksFromPhys() method
222	void invalidateChunksFromPhys(int physicalLine)
223	{
224		for(int i = 0; i < firstInvalidLine; i++)
225		{
226			LineInfo info = lineInfo[i];
227			if(info.physicalLine == -1 || info.physicalLine >= physicalLine)
228			{
229				firstInvalidLine = i;
230				break;
231			}
232		}
233	} //}}}
234
235	//{{{ getLineInfo() method
236	LineInfo getLineInfo(int screenLine)
237	{
238		updateChunksUpTo(screenLine);
239		return lineInfo[screenLine];
240	} //}}}
241
242	//{{{ getLineSubregionCount() method
243	int getLineSubregionCount(int physicalLine)
244	{
245		if(!textArea.displayManager.softWrap)
246			return 1;
247
248		out.clear();
249		lineToChunkList(physicalLine,out);
250
251		int size = out.size();
252		if(size == 0)
253			return 1;
254		else
255			return size;
256	} //}}}
257
258	//{{{ getSubregionOfOffset() method
259	/**
260	 * Returns the subregion containing the specified offset. A subregion
261	 * is a subset of a physical line. Each screen line corresponds to one
262	 * subregion. Unlike the {@link #getScreenLineOfOffset()} method,
263	 * this method works with non-visible lines too.
264	 */
265	int getSubregionOfOffset(int offset, LineInfo[] lineInfos)
266	{
267		for(int i = 0; i < lineInfos.length; i++)
268		{
269			LineInfo info = lineInfos[i];
270			if(offset >= info.offset && offset < info.offset + info.length)
271				return i;
272		}
273
274		return -1;
275	} //}}}
276
277	//{{{ xToSubregionOffset() method
278	/**
279	 * Converts an x co-ordinate within a subregion into an offset from the
280	 * start of that subregion.
281	 * @param physicalLine The physical line number
282	 * @param subregion The subregion; if -1, then this is the last
283	 * subregion.
284	 * @param x The x co-ordinate
285	 * @param round Round up to next character if x is past the middle of a
286	 * character?
287	 */
288	int xToSubregionOffset(int physicalLine, int subregion, int x,
289		boolean round)
290	{
291		LineInfo[] infos = getLineInfosForPhysicalLine(physicalLine);
292		if(subregion == -1)
293			subregion += infos.length;
294		return xToSubregionOffset(infos[subregion],x,round);
295	} //}}}
296
297	//{{{ xToSubregionOffset() method
298	/**
299	 * Converts an x co-ordinate within a subregion into an offset from the
300	 * start of that subregion.
301	 * @param info The line info object
302	 * @param x The x co-ordinate
303	 * @param round Round up to next character if x is past the middle of a
304	 * character?
305	 */
306	int xToSubregionOffset(LineInfo info, int x,
307		boolean round)
308	{
309		int offset = Chunk.xToOffset(info.chunks,
310			x - textArea.getHorizontalOffset(),round);
311		if(offset == -1 || offset == info.offset + info.length)
312			offset = info.offset + info.length - 1;
313
314		return offset;
315	} //}}}
316
317	//{{{ subregionOffsetToX() method
318	/**
319	 * Converts an offset within a subregion into an x co-ordinate.
320	 * @param physicalLine The physical line
321	 * @param offset The offset
322	 */
323	int subregionOffsetToX(int physicalLine, int offset)
324	{
325		LineInfo[] infos = getLineInfosForPhysicalLine(physicalLine);
326		LineInfo info = infos[getSubregionOfOffset(offset,infos)];
327		return subregionOffsetToX(info,offset);
328	} //}}}
329
330	//{{{ subregionOffsetToX() method
331	/**
332	 * Converts an offset within a subregion into an x co-ordinate.
333	 * @param info The line info object
334	 * @param offset The offset
335	 */
336	int subregionOffsetToX(LineInfo info, int offset)
337	{
338		return (int)(textArea.getHorizontalOffset() + Chunk.offsetToX(
339			info.chunks,offset));
340	} //}}}
341
342	//{{{ getSubregionStartOffset() method
343	/**
344	 * Returns the start offset of the specified subregion of the specified
345	 * physical line.
346	 * @param line The physical line number
347	 * @param offset An offset
348	 */
349	int getSubregionStartOffset(int line, int offset)
350	{
351		LineInfo[] lineInfos = getLineInfosForPhysicalLine(line);
352		LineInfo info = lineInfos[getSubregionOfOffset(offset,lineInfos)];
353		return textArea.getLineStartOffset(info.physicalLine)
354			+ info.offset;
355	} //}}}
356
357	//{{{ getSubregionEndOffset() method
358	/**
359	 * Returns the end offset of the specified subregion of the specified
360	 * physical line.
361	 * @param line The physical line number
362	 * @param offset An offset
363	 */
364	int getSubregionEndOffset(int line, int offset)
365	{
366		LineInfo[] lineInfos = getLineInfosForPhysicalLine(line);
367		LineInfo info = lineInfos[getSubregionOfOffset(offset,lineInfos)];
368		return textArea.getLineStartOffset(info.physicalLine)
369			+ info.offset + info.length;
370	} //}}}
371
372	//{{{ getBelowPosition() method
373	/**
374	 * @param physicalLine The physical line number
375	 * @param offset The offset
376	 * @param x The location
377	 * @param ignoreWrap If true, behave as if soft wrap is off even if it
378	 * is on
379	 */
380	int getBelowPosition(int physicalLine, int offset, int x,
381		boolean ignoreWrap)
382	{
383		LineInfo[] lineInfos = getLineInfosForPhysicalLine(physicalLine);
384
385		int subregion = getSubregionOfOffset(offset,lineInfos);
386
387		if(subregion != lineInfos.length - 1 && !ignoreWrap)
388		{
389			return textArea.getLineStartOffset(physicalLine)
390				+ xToSubregionOffset(lineInfos[subregion + 1],
391				x,true);
392		}
393		else
394		{
395			int nextLine = textArea.displayManager
396				.getNextVisibleLine(physicalLine);
397
398			if(nextLine == -1)
399				return -1;
400			else
401			{
402				return textArea.getLineStartOffset(nextLine)
403					+ xToSubregionOffset(nextLine,0,
404					x,true);
405			}
406		}
407	} //}}}
408
409	//{{{ getAbovePosition() method
410	/**
411	 * @param physicalLine The physical line number
412	 * @param offset The offset
413	 * @param x The location
414	 * @param ignoreWrap If true, behave as if soft wrap is off even if it
415	 * is on
416	 */
417	int getAbovePosition(int physicalLine, int offset, int x,
418		boolean ignoreWrap)
419	{
420		LineInfo[] lineInfos = getLineInfosForPhysicalLine(physicalLine);
421
422		int subregion = getSubregionOfOffset(offset,lineInfos);
423
424		if(subregion != 0 && !ignoreWrap)
425		{
426			return textArea.getLineStartOffset(physicalLine)
427				+ xToSubregionOffset(lineInfos[subregion - 1],
428				x,true);
429		}
430		else
431		{
432			int prevLine = textArea.displayManager
433				.getPrevVisibleLine(physicalLine);
434
435			if(prevLine == -1)
436				return -1;
437			else
438			{
439				return textArea.getLineStartOffset(prevLine)
440					+ xToSubregionOffset(prevLine,-1,
441					x,true);
442			}
443		}
444	} //}}}
445
446	//{{{ needFullRepaint() method
447	/**
448	 * The needFullRepaint variable becomes true when the number of screen
449	 * lines in a physical line changes.
450	 */
451	boolean needFullRepaint()
452	{
453		boolean retVal = needFullRepaint;
454		needFullRepaint = false;
455		return retVal;
456	} //}}}
457
458	//{{{ getLineInfosForPhysicalLine() method
459	LineInfo[] getLineInfosForPhysicalLine(int physicalLine)
460	{
461		out.clear();
462
463		if(buffer.isLoaded())
464			lineToChunkList(physicalLine,out);
465
466		if(out.size() == 0)
467			out.add(null);
468
469		ArrayList returnValue = new ArrayList(out.size());
470		getLineInfosForPhysicalLine(physicalLine,returnValue);
471		return (LineInfo[])returnValue.toArray(new LineInfo[out.size()]);
472	} //}}}
473
474	//{{{ Private members
475
476	//{{{ Instance variables
477	private JEditTextArea textArea;
478	private Buffer buffer;
479	private LineInfo[] lineInfo;
480	private ArrayList out;
481
482	private int firstInvalidLine;
483	private int lastScreenLineP;
484	private int lastScreenLine;
485
486	private boolean needFullRepaint;
487
488	private DisplayTokenHandler tokenHandler;
489	//}}}
490
491	//{{{ getLineInfosForPhysicalLine() method
492	private void getLineInfosForPhysicalLine(int physicalLine, List list)
493	{
494		for(int i = 0; i < out.size(); i++)
495		{
496			Chunk chunks = (Chunk)out.get(i);
497			LineInfo info = new LineInfo();
498			info.physicalLine = physicalLine;
499			if(i == 0)
500			{
501				info.firstSubregion = true;
502				info.offset = 0;
503			}
504			else
505				info.offset = chunks.offset;
506
507			if(i == out.size() - 1)
508			{
509				info.lastSubregion = true;
510				info.length = textArea.getLineLength(physicalLine)
511					- info.offset + 1;
512			}
513			else
514			{
515				info.length = ((Chunk)out.get(i + 1)).offset
516					- info.offset;
517			}
518
519			info.chunks = chunks;
520
521			list.add(info);
522		}
523	} //}}}
524
525	//{{{ updateChunksUpTo() method
526	private void updateChunksUpTo(int lastScreenLine)
527	{
528		// this method is a nightmare
529
530		if(lastScreenLine >= lineInfo.length)
531		{
532			throw new ArrayIndexOutOfBoundsException(lastScreenLine);
533		}
534
535		// if one line's chunks are invalid, remaining lines are also
536		// invalid
537		if(lastScreenLine < firstInvalidLine)
538			return;
539
540		// find a valid line closest to the last screen line
541		int firstScreenLine = 0;
542
543		for(int i = firstInvalidLine - 1; i >= 0; i--)
544		{
545			if(lineInfo[i].lastSubregion)
546			{
547				firstScreenLine = i + 1;
548				break;
549			}
550		}
551
552		int physicalLine;
553
554		// for the first line displayed, take its physical line to be
555		// the text area's first physical line
556		if(firstScreenLine == 0)
557		{
558			physicalLine = textArea.getFirstPhysicalLine();
559		}
560		// otherwise, determine the next visible line
561		else
562		{
563			int prevPhysLine = lineInfo[
564				firstScreenLine - 1]
565				.physicalLine;
566			// if -1, the empty space at the end of the text area
567			// when the buffer has less lines than are visible
568			if(prevPhysLine == -1)
569				physicalLine = -1;
570			else
571			{
572				physicalLine = textArea
573					.displayManager
574					.getNextVisibleLine(prevPhysLine);
575			}
576		}
577
578		if(Debug.CHUNK_CACHE_DEBUG)
579		{
580			Log.log(Log.DEBUG,this,"Updating chunks from " + firstScreenLine
581				+ " to " + lastScreenLine);
582		}
583
584		// Note that we rely on the fact that when a physical line is
585		// invalidated, all screen lines/subregions of that line are
586		// invalidated as well. See below comment for code that tries
587		// to uphold this assumption.
588
589		out.clear();
590
591		int offset = 0;
592		int length = 0;
593
594		for(int i = firstScreenLine; i <= lastScreenLine; i++)
595		{
596			LineInfo info = lineInfo[i];
597
598			Chunk chunks;
599
600			// get another line of chunks
601			if(out.size() == 0)
602			{
603				// unless this is the first time, increment
604				// the line number
605				if(physicalLine != -1 && i != firstScreenLine)
606				{
607					physicalLine = textArea.displayManager
608						.getNextVisibleLine(physicalLine);
609				}
610
611				// empty space
612				if(physicalLine == -1)
613				{
614					info.chunks = null;
615					info.physicalLine = -1;
616					continue;
617				}
618
619				// chunk the line.
620				lineToChunkList(physicalLine,out);
621
622				info.firstSubregion = true;
623
624				// if the line has no text, out.size() == 0
625				if(out.size() == 0)
626				{
627					textArea.displayManager
628						.setScreenLineCount(
629						physicalLine,1);
630					if(i == 0)
631					{
632						if(textArea.displayManager.firstLine.skew > 0)
633						{
634							Log.log(Log.ERROR,this,"BUG: skew=" + textArea.displayManager.firstLine.skew + ",out.size()=" + out.size());
635							textArea.displayManager.firstLine.skew = 0;
636							needFullRepaint = true;
637							lastScreenLine = lineInfo.length - 1;
638						}
639					}
640					chunks = null;
641					offset = 0;
642					length = 1;
643				}
644				// otherwise, the number of subregions
645				else
646				{
647					textArea.displayManager
648						.setScreenLineCount(
649						physicalLine,out.size());
650					if(i == 0)
651					{
652						int skew = textArea.displayManager.firstLine.skew;
653						if(skew >= out.size())
654						{
655							Log.log(Log.ERROR,this,"BUG: skew=" + skew + ",out.size()=" + out.size());
656							skew = 0;
657							needFullRepaint = true;
658							lastScreenLine = lineInfo.length - 1;
659						}
660						else if(skew > 0)
661						{
662							info.firstSubregion = false;
663							for(int j = 0; j < skew; j++)
664								out.remove(0);
665						}
666					}
667					chunks = (Chunk)out.get(0);
668					out.remove(0);
669					offset = chunks.offset;
670					if(out.size() != 0)
671						length = ((Chunk)out.get(0)).offset - offset;
672					else
673						length = textArea.getLineLength(physicalLine) - offset + 1;
674				}
675			}
676			else
677			{
678				info.firstSubregion = false;
679
680				chunks = (Chunk)out.get(0);
681				out.remove(0);
682				offset = chunks.offset;
683				if(out.size() != 0)
684					length = ((Chunk)out.get(0)).offset - offset;
685				else
686					length = textArea.getLineLength(physicalLine) - offset + 1;
687			}
688
689			boolean lastSubregion = (out.size() == 0);
690
691			if(i == lastScreenLine
692				&& lastScreenLine != lineInfo.length - 1)
693			{
694				/* If this line has become longer or shorter
695				 * (in which case the new physical line number
696				 * is different from the cached one) we need to:
697				 * - continue updating past the last line
698				 * - advise the text area to repaint
699				 * On the other hand, if the line wraps beyond
700				 * lastScreenLine, we need to keep updating the
701				 * chunk list to ensure proper alignment of
702				 * invalidation flags (see start of method) */
703				if(info.physicalLine != physicalLine
704					|| info.lastSubregion != lastSubregion)
705				{
706					lastScreenLine++;
707					needFullRepaint = true;
708				}
709				else if(out.size() != 0)
710					lastScreenLine++;
711			}
712
713			info.physicalLine = physicalLine;
714			info.lastSubregion = lastSubregion;
715			info.offset = offset;
716			info.length = length;
717			info.chunks = chunks;
718		}
719
720		firstInvalidLine = Math.max(lastScreenLine + 1,firstInvalidLine);
721	} //}}}
722
723	//{{{ lineToChunkList() method
724	private void lineToChunkList(int physicalLine, List out)
725	{
726		TextAreaPainter painter = textArea.getPainter();
727
728		tokenHandler.init(painter.getStyles(),
729			painter.getFontRenderContext(),
730			painter,out,
731			(textArea.displayManager.softWrap
732			? textArea.displayManager.wrapMargin : 0.0f));
733		buffer.markTokens(physicalLine,tokenHandler);
734	} //}}}
735
736	//}}}
737
738	//{{{ LineInfo class
739	static class LineInfo
740	{
741		int physicalLine;
742		int offset;
743		int length;
744		boolean firstSubregion;
745		boolean lastSubregion;
746		Chunk chunks;
747		int width;
748	} //}}}
749}