KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > gjt > sp > jedit > textarea > ChunkCache


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
24 package org.gjt.sp.jedit.textarea;
25
26 //{{{ Imports
27
import java.util.*;
28 import org.gjt.sp.jedit.buffer.JEditBuffer;
29 import org.gjt.sp.jedit.Debug;
30 import org.gjt.sp.jedit.syntax.*;
31 import org.gjt.sp.util.Log;
32 //}}}
33

34 /**
35  * Manages low-level text display tasks - the visible lines in the TextArea.
36  *
37  *
38  *
39  * @author Slava Pestov
40  * @version $Id: ChunkCache.java 8271 2006-12-28 08:05:51Z kpouer $
41  */

42 class ChunkCache
43 {
44     //{{{ ChunkCache constructor
45
ChunkCache(TextArea textArea)
46     {
47         this.textArea = textArea;
48         out = new ArrayList<Chunk>();
49         tokenHandler = new DisplayTokenHandler();
50     } //}}}
51

52     //{{{ getMaxHorizontalScrollWidth() method
53
/**
54      * Returns the max line width of the textarea.
55      * It will check all lines the first invalid line.
56      *
57      * @return the max line width
58      */

59     int getMaxHorizontalScrollWidth()
60     {
61         int max = 0;
62         for(int i = 0; i < firstInvalidLine; i++)
63         {
64             LineInfo info = lineInfo[i];
65             if(info.width > max)
66                 max = info.width;
67         }
68         return max;
69     } //}}}
70

71     //{{{ getScreenLineOfOffset() method
72
/**
73      * @param line physical line number of document
74      * @param offset number of characters from the left of the line.
75      * @return number of pixels from the left of the textArea where the
76      * cursor should be
77      */

78     int getScreenLineOfOffset(int line, int offset)
79     {
80         if(lineInfo.length == 0)
81             return -1;
82         if(line < textArea.getFirstPhysicalLine())
83             return -1;
84         if(line == textArea.getFirstPhysicalLine()
85             && offset < getLineInfo(0).offset)
86             return -1;
87         if(line > textArea.getLastPhysicalLine())
88             return -1;
89         
90         if(line == lastScreenLineP)
91         {
92             LineInfo last = getLineInfo(lastScreenLine);
93
94             if(offset >= last.offset
95                 && offset < last.offset + last.length) {
96                 return lastScreenLine;
97             }
98         }
99
100         int screenLine = -1;
101
102         // Find the screen line containing this offset
103
for(int i = 0; i < textArea.getVisibleLines(); i++)
104         {
105             LineInfo info = getLineInfo(i);
106             if(info.physicalLine > line)
107             {
108                 // line is invisible?
109
return i - 1;
110                 //return -1;
111
}
112             if(info.physicalLine == line)
113             {
114                 if(offset >= info.offset
115                     && offset < info.offset + info.length)
116                 {
117                     screenLine = i;
118                     break;
119                 }
120             }
121         }
122
123         if(screenLine == -1)
124             return -1;
125
126
127         lastScreenLineP = line;
128         lastScreenLine = screenLine;
129
130         return screenLine;
131     } //}}}
132

133     //{{{ recalculateVisibleLines() method
134
/**
135      * Recalculate visible lines.
136      * This is called when the TextArea geometry is changed or when the font is changed.
137      */

138     void recalculateVisibleLines()
139     {
140         LineInfo[] newLineInfo = new LineInfo[textArea.getVisibleLines()];
141
142         int start;
143         if(lineInfo == null)
144             start = 0;
145         else
146         {
147             start = Math.min(lineInfo.length,newLineInfo.length);
148             System.arraycopy(lineInfo,0,newLineInfo,0,start);
149         }
150
151         for(int i = start; i < newLineInfo.length; i++)
152             newLineInfo[i] = new LineInfo();
153
154         lineInfo = newLineInfo;
155
156         lastScreenLine = lastScreenLineP = -1;
157     } //}}}
158

159     //{{{ setBuffer() method
160
void setBuffer(JEditBuffer buffer)
161     {
162         this.buffer = buffer;
163         lastScreenLine = lastScreenLineP = -1;
164     } //}}}
165

166     //{{{ scrollDown() method
167
void scrollDown(int amount)
168     {
169         int visibleLines = textArea.getVisibleLines();
170
171         System.arraycopy(lineInfo,amount,lineInfo,0,visibleLines - amount);
172
173         for(int i = visibleLines - amount; i < visibleLines; i++)
174         {
175             lineInfo[i] = new LineInfo();
176         }
177
178         firstInvalidLine -= amount;
179         if(firstInvalidLine < 0)
180             firstInvalidLine = 0;
181
182         if(Debug.CHUNK_CACHE_DEBUG)
183         {
184             System.err.println("f > t.f: only " + amount
185                 + " need updates");
186         }
187
188         lastScreenLine = lastScreenLineP = -1;
189     } //}}}
190

191     //{{{ scrollUp() method
192
void scrollUp(int amount)
193     {
194         System.arraycopy(lineInfo,0,lineInfo,amount,
195             textArea.getVisibleLines() - amount);
196
197         for(int i = 0; i < amount; i++)
198         {
199             lineInfo[i] = new LineInfo();
200         }
201
202         // don't try this at home
203
int oldFirstInvalidLine = firstInvalidLine;
204         firstInvalidLine = 0;
205         updateChunksUpTo(amount);
206         firstInvalidLine = oldFirstInvalidLine + amount;
207         if(firstInvalidLine > textArea.getVisibleLines())
208             firstInvalidLine = textArea.getVisibleLines();
209
210         if(Debug.CHUNK_CACHE_DEBUG)
211         {
212             Log.log(Log.DEBUG,this,"f > t.f: only " + amount
213                 + " need updates");
214         }
215
216         lastScreenLine = lastScreenLineP = -1;
217     } //}}}
218

219     //{{{ invalidateAll() method
220
void invalidateAll()
221     {
222         firstInvalidLine = 0;
223         lastScreenLine = lastScreenLineP = -1;
224     } //}}}
225

226     //{{{ invalidateChunksFrom() method
227
void invalidateChunksFrom(int screenLine)
228     {
229         if(Debug.CHUNK_CACHE_DEBUG)
230             Log.log(Log.DEBUG,this,"Invalidate from " + screenLine);
231         firstInvalidLine = Math.min(screenLine,firstInvalidLine);
232
233         if(screenLine <= lastScreenLine)
234             lastScreenLine = lastScreenLineP = -1;
235     } //}}}
236

237     //{{{ invalidateChunksFromPhys() method
238
void invalidateChunksFromPhys(int physicalLine)
239     {
240         for(int i = 0; i < firstInvalidLine; i++)
241         {
242             LineInfo info = lineInfo[i];
243             if(info.physicalLine == -1 || info.physicalLine >= physicalLine)
244             {
245                 firstInvalidLine = i;
246                 if(i <= lastScreenLine)
247                     lastScreenLine = lastScreenLineP = -1;
248                 break;
249             }
250         }
251     } //}}}
252

253     //{{{ getLineInfo() method
254
LineInfo getLineInfo(int screenLine)
255     {
256         updateChunksUpTo(screenLine);
257         return lineInfo[screenLine];
258     } //}}}
259

260     //{{{ getLineSubregionCount() method
261
int getLineSubregionCount(int physicalLine)
262     {
263         if(!textArea.softWrap)
264             return 1;
265
266         out.clear();
267         lineToChunkList(physicalLine,out);
268
269         int size = out.size();
270         if(size == 0)
271             return 1;
272         else
273             return size;
274     } //}}}
275

276     //{{{ getSubregionOfOffset() method
277
/**
278      * Returns the subregion containing the specified offset. A subregion
279      * is a subset of a physical line. Each screen line corresponds to one
280      * subregion. Unlike the {@link #getScreenLineOfOffset(int, int)} method,
281      * this method works with non-visible lines too.
282      *
283      * @param offset the offset
284      * @param lineInfos a lineInfos array. Usualy the array is the result of
285      * {@link #getLineInfosForPhysicalLine(int)} call
286      *
287      * @return the subregion of the offset, or -1 if the offset was not in one of the given lineInfos
288      */

289     static int getSubregionOfOffset(int offset, LineInfo[] lineInfos)
290     {
291         for(int i = 0; i < lineInfos.length; i++)
292         {
293             LineInfo info = lineInfos[i];
294             if(offset >= info.offset && offset < info.offset + info.length)
295                 return i;
296         }
297
298         return -1;
299     } //}}}
300

301     //{{{ xToSubregionOffset() method
302
/**
303      * Converts an x co-ordinate within a subregion into an offset from the
304      * start of that subregion.
305      * @param physicalLine The physical line number
306      * @param subregion The subregion; if -1, then this is the last
307      * subregion.
308      * @param x The x co-ordinate
309      * @param round Round up to next character if x is past the middle of a
310      * character?
311      */

312     int xToSubregionOffset(int physicalLine, int subregion, int x,
313         boolean round)
314     {
315         LineInfo[] infos = getLineInfosForPhysicalLine(physicalLine);
316         if(subregion == -1)
317             subregion += infos.length;
318         return xToSubregionOffset(infos[subregion],x,round);
319     } //}}}
320

321     //{{{ xToSubregionOffset() method
322
/**
323      * Converts an x co-ordinate within a subregion into an offset from the
324      * start of that subregion.
325      * @param info The line info object
326      * @param x The x co-ordinate
327      * @param round Round up to next character if x is past the middle of a
328      * character?
329      */

330     static int xToSubregionOffset(LineInfo info, int x,
331         boolean round)
332     {
333         int offset = Chunk.xToOffset(info.chunks,x,round);
334         if(offset == -1 || offset == info.offset + info.length)
335             offset = info.offset + info.length - 1;
336
337         return offset;
338     } //}}}
339

340     //{{{ subregionOffsetToX() method
341
/**
342      * Converts an offset within a subregion into an x co-ordinate.
343      * @param physicalLine The physical line
344      * @param offset The offset
345      */

346     int subregionOffsetToX(int physicalLine, int offset)
347     {
348         LineInfo[] infos = getLineInfosForPhysicalLine(physicalLine);
349         LineInfo info = infos[getSubregionOfOffset(offset,infos)];
350         return subregionOffsetToX(info,offset);
351     } //}}}
352

353     //{{{ subregionOffsetToX() method
354
/**
355      * Converts an offset within a subregion into an x co-ordinate.
356      * @param info The line info object
357      * @param offset The offset
358      */

359     static int subregionOffsetToX(LineInfo info, int offset)
360     {
361         return (int)Chunk.offsetToX(info.chunks,offset);
362     } //}}}
363

364     //{{{ getSubregionStartOffset() method
365
/**
366      * Returns the start offset of the specified subregion of the specified
367      * physical line.
368      * @param line The physical line number
369      * @param offset An offset
370      */

371     int getSubregionStartOffset(int line, int offset)
372     {
373         LineInfo[] lineInfos = getLineInfosForPhysicalLine(line);
374         LineInfo info = lineInfos[getSubregionOfOffset(offset,lineInfos)];
375         return textArea.getLineStartOffset(info.physicalLine)
376             + info.offset;
377     } //}}}
378

379     //{{{ getSubregionEndOffset() method
380
/**
381      * Returns the end offset of the specified subregion of the specified
382      * physical line.
383      * @param line The physical line number
384      * @param offset An offset
385      */

386     int getSubregionEndOffset(int line, int offset)
387     {
388         LineInfo[] lineInfos = getLineInfosForPhysicalLine(line);
389         LineInfo info = lineInfos[getSubregionOfOffset(offset,lineInfos)];
390         return textArea.getLineStartOffset(info.physicalLine)
391             + info.offset + info.length;
392     } //}}}
393

394     //{{{ getBelowPosition() method
395
/**
396      * @param physicalLine The physical line number
397      * @param offset The offset
398      * @param x The location
399      * @param ignoreWrap If true, behave as if soft wrap is off even if it
400      * is on
401      */

402     int getBelowPosition(int physicalLine, int offset, int x,
403         boolean ignoreWrap)
404     {
405         LineInfo[] lineInfos = getLineInfosForPhysicalLine(physicalLine);
406
407         int subregion = getSubregionOfOffset(offset,lineInfos);
408
409         if(subregion != lineInfos.length - 1 && !ignoreWrap)
410         {
411             return textArea.getLineStartOffset(physicalLine)
412                 + xToSubregionOffset(lineInfos[subregion + 1],
413                 x,true);
414         }
415         else
416         {
417             int nextLine = textArea.displayManager
418                 .getNextVisibleLine(physicalLine);
419
420             if(nextLine == -1)
421                 return -1;
422             else
423             {
424                 return textArea.getLineStartOffset(nextLine)
425                     + xToSubregionOffset(nextLine,0,
426                     x,true);
427             }
428         }
429     } //}}}
430

431     //{{{ getAbovePosition() method
432
/**
433      * @param physicalLine The physical line number
434      * @param offset The offset
435      * @param x The location
436      * @param ignoreWrap If true, behave as if soft wrap is off even if it
437      * is on
438      */

439     int getAbovePosition(int physicalLine, int offset, int x,
440         boolean ignoreWrap)
441     {
442         LineInfo[] lineInfos = getLineInfosForPhysicalLine(physicalLine);
443
444         int subregion = getSubregionOfOffset(offset,lineInfos);
445
446         if(subregion != 0 && !ignoreWrap)
447         {
448             return textArea.getLineStartOffset(physicalLine)
449                 + xToSubregionOffset(lineInfos[subregion - 1],
450                 x,true);
451         }
452         else
453         {
454             int prevLine = textArea.displayManager
455                 .getPrevVisibleLine(physicalLine);
456
457             if(prevLine == -1)
458                 return -1;
459             else
460             {
461                 return textArea.getLineStartOffset(prevLine)
462                     + xToSubregionOffset(prevLine,-1,
463                     x,true);
464             }
465         }
466     } //}}}
467

468     //{{{ needFullRepaint() method
469
/**
470      * The needFullRepaint variable becomes true when the number of screen
471      * lines in a physical line changes.
472      */

473     boolean needFullRepaint()
474     {
475         boolean retVal = needFullRepaint;
476         needFullRepaint = false;
477         return retVal;
478     } //}}}
479

480     //{{{ getLineInfosForPhysicalLine() method
481
LineInfo[] getLineInfosForPhysicalLine(int physicalLine)
482     {
483         out.clear();
484
485         if(!buffer.isLoading())
486             lineToChunkList(physicalLine,out);
487
488         if(out.isEmpty())
489             out.add(null);
490
491         List<LineInfo> returnValue = new ArrayList<LineInfo>(out.size());
492         getLineInfosForPhysicalLine(physicalLine,returnValue);
493         return returnValue.toArray(new LineInfo[out.size()]);
494     } //}}}
495

496     //{{{ Private members
497

498     //{{{ Instance variables
499
private final TextArea textArea;
500     private JEditBuffer buffer;
501     /**
502      * The lineInfo array. There is LineInfo for each line that is visible in the textArea.
503      * it can be resized by {@link #recalculateVisibleLines()}.
504      * The content is valid from 0 to {@link #firstInvalidLine}
505      */

506     private LineInfo[] lineInfo;
507     private final List<Chunk> out;
508
509     /** The first invalid line. All lines before this one are valid. */
510     private int firstInvalidLine;
511     private int lastScreenLineP;
512     private int lastScreenLine;
513
514     private boolean needFullRepaint;
515
516     private final DisplayTokenHandler tokenHandler;
517     //}}}
518

519     //{{{ getLineInfosForPhysicalLine() method
520
private void getLineInfosForPhysicalLine(int physicalLine, List<LineInfo> list)
521     {
522         for(int i = 0; i < out.size(); i++)
523         {
524             Chunk chunks = out.get(i);
525             LineInfo info = new LineInfo();
526             info.physicalLine = physicalLine;
527             if(i == 0)
528             {
529                 info.firstSubregion = true;
530                 info.offset = 0;
531             }
532             else
533                 info.offset = chunks.offset;
534
535             if(i == out.size() - 1)
536             {
537                 info.lastSubregion = true;
538                 info.length = textArea.getLineLength(physicalLine)
539                     - info.offset + 1;
540             }
541             else
542             {
543                 info.length = out.get(i + 1).offset
544                     - info.offset;
545             }
546
547             info.chunks = chunks;
548
549             list.add(info);
550         }
551     } //}}}
552

553     //{{{ getFirstScreenLine() method
554
/**
555      * Find a valid line closest to the last screen line.
556      */

557     private int getFirstScreenLine()
558     {
559         for(int i = firstInvalidLine - 1; i >= 0; i--)
560         {
561             if(lineInfo[i].lastSubregion)
562                 return i + 1;
563         }
564
565         return 0;
566     } //}}}
567

568     //{{{ getUpdateStartLine() method
569
/**
570      * Return a physical line number.
571      */

572     private int getUpdateStartLine(int firstScreenLine)
573     {
574         // for the first line displayed, take its physical line to be
575
// the text area's first physical line
576
if(firstScreenLine == 0)
577         {
578             return textArea.getFirstPhysicalLine();
579         }
580         // otherwise, determine the next visible line
581
else
582         {
583             int prevPhysLine = lineInfo[
584                 firstScreenLine - 1]
585                 .physicalLine;
586             // if -1, the empty space at the end of the text area
587
// when the buffer has less lines than are visible
588
if(prevPhysLine == -1)
589                 return -1;
590             else
591             {
592                 return textArea.displayManager
593                     .getNextVisibleLine(prevPhysLine);
594             }
595         }
596     } //}}}
597

598     //{{{ updateChunksUpTo() method
599
private void updateChunksUpTo(int lastScreenLine)
600     {
601         // this method is a nightmare
602
if(lastScreenLine >= lineInfo.length)
603             throw new ArrayIndexOutOfBoundsException JavaDoc(lastScreenLine);
604
605         // if one line's chunks are invalid, remaining lines are also
606
// invalid
607
if(lastScreenLine < firstInvalidLine)
608             return;
609
610         int firstScreenLine = getFirstScreenLine();
611         int physicalLine = getUpdateStartLine(firstScreenLine);
612
613         if(Debug.CHUNK_CACHE_DEBUG)
614         {
615             Log.log(Log.DEBUG,this,"Updating chunks from " + firstScreenLine
616                 + " to " + lastScreenLine);
617         }
618
619         // Note that we rely on the fact that when a physical line is
620
// invalidated, all screen lines/subregions of that line are
621
// invalidated as well. See below comment for code that tries
622
// to uphold this assumption.
623

624         out.clear();
625
626         int offset = 0;
627         int length = 0;
628
629         for(int i = firstScreenLine; i <= lastScreenLine; i++)
630         {
631             LineInfo info = lineInfo[i];
632
633             Chunk chunks;
634
635             // get another line of chunks
636
if(out.isEmpty())
637             {
638                 // unless this is the first time, increment
639
// the line number
640
if(physicalLine != -1 && i != firstScreenLine)
641                 {
642                     physicalLine = textArea.displayManager
643                         .getNextVisibleLine(physicalLine);
644                 }
645
646                 // empty space
647
if(physicalLine == -1)
648                 {
649                     info.chunks = null;
650                     info.physicalLine = -1;
651                     // fix the bug where the horiz.
652
// scroll bar was not updated
653
// after creating a new file.
654
info.width = 0;
655                     continue;
656                 }
657
658                 // chunk the line.
659
lineToChunkList(physicalLine,out);
660
661                 info.firstSubregion = true;
662
663                 int screenLines;
664
665                 // if the line has no text, out.size() == 0
666
if(out.isEmpty())
667                 {
668                     screenLines = 1;
669
670                     if(i == 0)
671                     {
672                         if(textArea.displayManager.firstLine.skew > 0)
673                         {
674                             Log.log(Log.ERROR,this,"BUG: skew=" + textArea.displayManager.firstLine.skew + ",out.size()=" + out.size());
675                             textArea.displayManager.firstLine.skew = 0;
676                             needFullRepaint = true;
677                             lastScreenLine = lineInfo.length - 1;
678                         }
679                     }
680                     chunks = null;
681                     offset = 0;
682                     length = 1;
683                 }
684                 // otherwise, the number of subregions
685
else
686                 {
687                     screenLines = out.size();
688
689                     if(i == 0)
690                     {
691                         int skew = textArea.displayManager.firstLine.skew;
692                         if(skew >= out.size())
693                         {
694                             Log.log(Log.ERROR,this,"BUG: skew=" + skew + ",out.size()=" + out.size());
695                             skew = 0;
696                             needFullRepaint = true;
697                             lastScreenLine = lineInfo.length - 1;
698                         }
699                         else if(skew > 0)
700                         {
701                             info.firstSubregion = false;
702                             for(int j = 0; j < skew; j++)
703                                 out.remove(0);
704                         }
705                     }
706                     chunks = out.remove(0);
707                     offset = chunks.offset;
708                     if (!out.isEmpty())
709                         length = out.get(0).offset - offset;
710                     else
711                         length = textArea.getLineLength(physicalLine) - offset + 1;
712                 }
713             }
714             else
715             {
716                 info.firstSubregion = false;
717
718                 chunks = out.remove(0);
719                 offset = chunks.offset;
720                 if (!out.isEmpty())
721                     length = out.get(0).offset - offset;
722                 else
723                     length = textArea.getLineLength(physicalLine) - offset + 1;
724             }
725
726             boolean lastSubregion = out.isEmpty();
727
728             if(i == lastScreenLine
729                 && lastScreenLine != lineInfo.length - 1)
730             {
731                 /* if the user changes the syntax token at the
732                  * end of a line, need to do a full repaint. */

733                 if(tokenHandler.getLineContext() !=
734                     info.lineContext)
735                 {
736                     lastScreenLine++;
737                     needFullRepaint = true;
738                 }
739                 /* If this line has become longer or shorter
740                  * (in which case the new physical line number
741                  * is different from the cached one) we need to:
742                  * - continue updating past the last line
743                  * - advise the text area to repaint
744                  * On the other hand, if the line wraps beyond
745                  * lastScreenLine, we need to keep updating the
746                  * chunk list to ensure proper alignment of
747                  * invalidation flags (see start of method) */

748                 else if(info.physicalLine != physicalLine
749                     || info.lastSubregion != lastSubregion)
750                 {
751                     lastScreenLine++;
752                     needFullRepaint = true;
753                 }
754                 /* We only cache entire physical lines at once;
755                  * don't want to split a physical line into
756                  * screen lines and only have some valid. */

757                 else if (!out.isEmpty())
758                     lastScreenLine++;
759             }
760
761             info.physicalLine = physicalLine;
762             info.lastSubregion = lastSubregion;
763             info.offset = offset;
764             info.length = length;
765             info.chunks = chunks;
766             info.lineContext = tokenHandler.getLineContext();
767         }
768
769         firstInvalidLine = Math.max(lastScreenLine + 1,firstInvalidLine);
770     } //}}}
771

772     //{{{ lineToChunkList() method
773
private void lineToChunkList(int physicalLine, List<Chunk> out)
774     {
775         TextAreaPainter painter = textArea.getPainter();
776
777         tokenHandler.init(painter.getStyles(),
778             painter.getFontRenderContext(),
779             painter,out,
780             textArea.softWrap
781             ? textArea.wrapMargin : 0.0f);
782         buffer.markTokens(physicalLine,tokenHandler);
783     } //}}}
784

785     //}}}
786

787     //{{{ LineInfo class
788
/** The informations on a line. (for fast access) */
789     static class LineInfo
790     {
791         int physicalLine;
792         int offset;
793         int length;
794         boolean firstSubregion;
795         boolean lastSubregion;
796         Chunk chunks;
797         /** The line width. */
798         int width;
799         TokenMarker.LineContext lineContext;
800     } //}}}
801
}
802
Popular Tags