/jEdit/tags/jedit-4-0-pre3/org/gjt/sp/jedit/textarea/FoldVisibilityManager.java
Java | 867 lines | 582 code | 106 blank | 179 comment | 154 complexity | c8e2dad057fa7249ee71150ed4e8c383 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
1/*
2 * FoldVisibilityManager.java - Controls fold visiblity
3 * :tabSize=8:indentSize=8:noTabs=false:
4 * :folding=explicit:collapseFolds=1:
5 *
6 * Copyright (C) 2001 Slava Pestov
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */
22
23package org.gjt.sp.jedit.textarea;
24
25import java.awt.Toolkit;
26import org.gjt.sp.jedit.*;
27
28/**
29 * Controls fold visibility.
30 * Note that a "physical" line number is a line index, numbered from the
31 * start of the buffer. A "virtual" line number is a visible line index;
32 * lines after a collapsed fold have a virtual line number that is less
33 * than their physical line number, for example.<p>
34 *
35 * You can use the <code>physicalToVirtual()</code> and
36 * <code>virtualToPhysical()</code> methods to convert one type of line
37 * number to another.
38 *
39 * @author Slava Pestov
40 * @version $Id: FoldVisibilityManager.java 3933 2001-12-03 10:52:27Z spestov $
41 * @since jEdit 4.0pre1
42 */
43public class FoldVisibilityManager
44{
45 //{{{ FoldVisibilityManager constructor
46 public FoldVisibilityManager(Buffer buffer, JEditTextArea textArea)
47 {
48 this.buffer = buffer;
49 this.textArea = textArea;
50 } //}}}
51
52 //{{{ isNarrowed() method
53 /**
54 * Returns if the buffer has been narrowed.
55 * @since jEdit 4.0pre2
56 */
57 public boolean isNarrowed()
58 {
59 return narrowed;
60 } //}}}
61
62 //{{{ getVirtualLineCount() method
63 /**
64 * Returns the number of virtual lines in the buffer.
65 * @since jEdit 4.0pre1
66 */
67 public int getVirtualLineCount()
68 {
69 return buffer._getVirtualLineCount(index);
70 } //}}}
71
72 //{{{ isLineVisible() method
73 /**
74 * Returns if the specified line is visible.
75 * @param line A physical line index
76 * @since jEdit 4.0pre1
77 */
78 public final boolean isLineVisible(int line)
79 {
80 try
81 {
82 buffer.readLock();
83 return buffer._isLineVisible(line,index);
84 }
85 finally
86 {
87 buffer.readUnlock();
88 }
89 } //}}}
90
91 //{{{ getFirstVisibleLine() method
92 /**
93 * Returns the physical line number of the first visible line.
94 * @since jEdit 4.0pre3
95 */
96 public int getFirstVisibleLine()
97 {
98 try
99 {
100 buffer.readLock();
101
102 for(int i = 0; i < buffer.getLineCount(); i++)
103 {
104 if(isLineVisible(i))
105 return i;
106 }
107 }
108 finally
109 {
110 buffer.readUnlock();
111 }
112
113 // can't happen?
114 return -1;
115 } //}}}
116
117 //{{{ getLastVisibleLine() method
118 /**
119 * Returns the physical line number of the last visible line.
120 * @since jEdit 4.0pre3
121 */
122 public int getLastVisibleLine()
123 {
124 try
125 {
126 buffer.readLock();
127
128 for(int i = buffer.getLineCount() - 1; i >= 0; i--)
129 {
130 if(isLineVisible(i))
131 return i;
132 }
133 }
134 finally
135 {
136 buffer.readUnlock();
137 }
138
139 // can't happen?
140 return -1;
141 } //}}}
142
143 //{{{ getNextVisibleLine() method
144 /**
145 * Returns the next visible line after the specified line index.
146 * @param line A physical line index
147 * @since jEdit 4.0pre1
148 */
149 public int getNextVisibleLine(int line)
150 {
151 try
152 {
153 buffer.readLock();
154
155 if(line == buffer.getLineCount() - 1)
156 return -1;
157
158 for(int i = line + 1; i < buffer.getLineCount(); i++)
159 {
160 if(buffer._isLineVisible(i,index))
161 return i;
162 }
163 return -1;
164 }
165 finally
166 {
167 buffer.readUnlock();
168 }
169 } //}}}
170
171 //{{{ getPrevVisibleLine() method
172 /**
173 * Returns the previous visible line before the specified line index.
174 * @param line A physical line index
175 * @since jEdit 4.0pre1
176 */
177 public int getPrevVisibleLine(int line)
178 {
179 try
180 {
181 buffer.readLock();
182
183 if(line == 0)
184 return -1;
185
186 for(int i = line - 1; i >= 0; i--)
187 {
188 if(buffer._isLineVisible(i,index))
189 return i;
190 }
191 return -1;
192 }
193 finally
194 {
195 buffer.readUnlock();
196 }
197 } //}}}
198
199 //{{{ physicalToVirtual() method
200 /**
201 * Converts a physical line number to a virtual line number.
202 * @param line A physical line index
203 * @since jEdit 4.0pre1
204 */
205 public int physicalToVirtual(int line)
206 {
207 try
208 {
209 buffer.readLock();
210
211 if(line < 0)
212 throw new ArrayIndexOutOfBoundsException(line + " < 0");
213 else if(line >= buffer.getLineCount())
214 {
215 throw new ArrayIndexOutOfBoundsException(line + " > "
216 + buffer.getLineCount());
217 }
218
219 while(!buffer._isLineVisible(line,index) && line > 0)
220 line--;
221
222 if(line == 0 && !buffer._isLineVisible(line,index))
223 {
224 // inside the top narrow.
225 return 0;
226 }
227
228 if(lastPhysical == line)
229 {
230 if(lastVirtual < 0 || lastVirtual >= buffer._getVirtualLineCount(index))
231 {
232 throw new ArrayIndexOutOfBoundsException(
233 "cached: " + lastVirtual);
234 }
235 }
236 else if(line > lastPhysical && lastPhysical != -1)
237 {
238 for(;;)
239 {
240 if(lastPhysical == line)
241 break;
242
243 if(buffer._isLineVisible(lastPhysical,index))
244 lastVirtual++;
245
246 if(lastPhysical == buffer.getLineCount() - 1)
247 break;
248 else
249 lastPhysical++;
250 }
251
252 if(lastVirtual < 0 || lastVirtual >= buffer._getVirtualLineCount(index))
253 {
254 throw new ArrayIndexOutOfBoundsException(
255 "fwd scan: " + lastVirtual);
256 }
257 }
258 else if(line < lastPhysical && lastPhysical - line > line)
259 {
260 for(;;)
261 {
262 if(lastPhysical == line)
263 break;
264
265 if(buffer._isLineVisible(lastPhysical,index))
266 lastVirtual--;
267
268 if(lastPhysical == 0)
269 break;
270 else
271 lastPhysical--;
272 }
273
274 if(lastVirtual < 0 || lastVirtual >= buffer._getVirtualLineCount(index))
275 {
276 throw new ArrayIndexOutOfBoundsException(
277 "back scan: " + lastVirtual);
278 }
279 }
280 else
281 {
282 lastPhysical = 0;
283 // find first visible line
284 while(!buffer._isLineVisible(lastPhysical,index))
285 lastPhysical++;
286
287 lastVirtual = 0;
288 for(;;)
289 {
290 if(lastPhysical == line)
291 break;
292
293 if(buffer._isLineVisible(lastPhysical,index))
294 lastVirtual++;
295
296 if(lastPhysical == buffer.getLineCount() - 1)
297 break;
298 else
299 lastPhysical++;
300 }
301
302 if(lastVirtual < 0 || lastVirtual >= buffer._getVirtualLineCount(index))
303 {
304 throw new ArrayIndexOutOfBoundsException(
305 "zero scan: " + lastVirtual);
306 }
307 }
308
309 return lastVirtual;
310 }
311 finally
312 {
313 buffer.readUnlock();
314 }
315 } //}}}
316
317 //{{{ virtualToPhysical() method
318 /**
319 * Converts a virtual line number to a physical line number.
320 * @param line A virtual line index
321 * @since jEdit 4.0pre1
322 */
323 public int virtualToPhysical(int line)
324 {
325 try
326 {
327 buffer.readLock();
328
329 if(line < 0)
330 throw new ArrayIndexOutOfBoundsException(line + " < 0");
331 else if(line >= buffer._getVirtualLineCount(index))
332 {
333 throw new ArrayIndexOutOfBoundsException(line + " > "
334 + buffer._getVirtualLineCount(index));
335 }
336
337 if(lastVirtual == line)
338 {
339 if(lastPhysical < 0 || lastPhysical >= buffer.getLineCount())
340 {
341 throw new ArrayIndexOutOfBoundsException(
342 "cached: " + lastPhysical);
343 }
344 }
345 else if(line > lastVirtual && lastVirtual != -1)
346 {
347 for(;;)
348 {
349 if(buffer._isLineVisible(lastPhysical,index))
350 {
351 if(lastVirtual == line)
352 break;
353 else
354 lastVirtual++;
355 }
356
357 if(lastPhysical == buffer.getLineCount() - 1)
358 break;
359 else
360 lastPhysical++;
361 }
362
363 if(lastPhysical < 0 || lastPhysical >= buffer.getLineCount())
364 {
365 throw new ArrayIndexOutOfBoundsException(
366 "fwd scan: " + lastPhysical);
367 }
368 }
369 else if(line < lastVirtual && lastVirtual - line > line)
370 {
371 for(;;)
372 {
373 if(buffer._isLineVisible(lastPhysical,index))
374 {
375 if(lastVirtual == line)
376 break;
377 else
378 lastVirtual--;
379 }
380
381 if(lastPhysical == 0)
382 break;
383 else
384 lastPhysical--;
385 }
386
387 if(lastPhysical < 0 || lastPhysical >= buffer.getLineCount())
388 {
389 throw new ArrayIndexOutOfBoundsException(
390 "back scan: " + lastPhysical);
391 }
392 }
393 else
394 {
395 lastPhysical = 0;
396 // find first visible line
397 while(!buffer._isLineVisible(lastPhysical,index))
398 lastPhysical++;
399
400 lastVirtual = 0;
401 for(;;)
402 {
403 if(buffer._isLineVisible(lastPhysical,index))
404 {
405 if(lastVirtual == line)
406 break;
407 else
408 lastVirtual++;
409 }
410
411 if(lastPhysical == buffer.getLineCount() - 1)
412 break;
413 else
414 lastPhysical++;
415 }
416
417 if(lastPhysical < 0 || lastPhysical >= buffer.getLineCount())
418 {
419 throw new ArrayIndexOutOfBoundsException(
420 "zero scan: " + lastPhysical);
421 }
422 }
423
424 return lastPhysical;
425 }
426 finally
427 {
428 buffer.readUnlock();
429 }
430 } //}}}
431
432 //{{{ collapseFold() method
433 /**
434 * Collapses the fold at the specified physical line index.
435 * @param line A physical line index
436 * @since jEdit 4.0pre1
437 */
438 public void collapseFold(int line)
439 {
440 int lineCount = buffer.getLineCount();
441 int start = 0;
442 int end = lineCount - 1;
443
444 try
445 {
446 buffer.writeLock();
447
448 // if the caret is on a collapsed fold, collapse the
449 // parent fold
450 if(line != 0
451 && line != buffer.getLineCount() - 1
452 && buffer.isFoldStart(line)
453 && !buffer._isLineVisible(line + 1,index))
454 {
455 line--;
456 }
457
458 int initialFoldLevel = buffer.getFoldLevel(line);
459
460 //{{{ Find fold start and end...
461 if(line != lineCount - 1
462 && buffer.getFoldLevel(line + 1) > initialFoldLevel)
463 {
464 // this line is the start of a fold
465 start = line + 1;
466
467 for(int i = line + 1; i < lineCount; i++)
468 {
469 if(buffer.getFoldLevel(i) <= initialFoldLevel)
470 {
471 end = i - 1;
472 break;
473 }
474 }
475 }
476 else
477 {
478 boolean ok = false;
479
480 // scan backwards looking for the start
481 for(int i = line - 1; i >= 0; i--)
482 {
483 if(buffer.getFoldLevel(i) < initialFoldLevel)
484 {
485 start = i + 1;
486 ok = true;
487 break;
488 }
489 }
490
491 if(!ok)
492 {
493 // no folds in buffer
494 return;
495 }
496
497 for(int i = line + 1; i < lineCount; i++)
498 {
499 if(buffer.getFoldLevel(i) < initialFoldLevel)
500 {
501 end = i - 1;
502 break;
503 }
504 }
505 } //}}}
506
507 //{{{ Collapse the fold...
508 int delta = (end - start + 1);
509
510 for(int i = start; i <= end; i++)
511 {
512 if(buffer._isLineVisible(i,index))
513 buffer._setLineVisible(i,index,false);
514 else
515 delta--;
516 }
517
518 if(delta == 0)
519 {
520 // user probably pressed A+BACK_SPACE twice
521 return;
522 }
523
524 buffer._setVirtualLineCount(index,
525 buffer._getVirtualLineCount(index)
526 - delta);
527 //}}}
528 }
529 finally
530 {
531 buffer.writeUnlock();
532 }
533
534 foldStructureChanged();
535
536 int virtualLine = physicalToVirtual(start);
537 if(textArea.getFirstLine() > virtualLine)
538 textArea.setFirstLine(virtualLine - textArea.getElectricScroll());
539 } //}}}
540
541 //{{{ expandFold() method
542 /**
543 * Expands the fold at the specified physical line index.
544 * @param line A physical line index
545 * @param fully If true, all subfolds will also be expanded
546 * @since jEdit 4.0pre3
547 */
548 public int expandFold(int line, boolean fully)
549 {
550 // the first sub-fold. used by JEditTextArea.expandFold().
551 int returnValue = -1;
552
553 int lineCount = buffer.getLineCount();
554 int start = 0;
555 int end = lineCount - 1;
556 int delta = 0;
557
558 try
559 {
560 buffer.writeLock();
561
562 int initialFoldLevel = buffer.getFoldLevel(line);
563
564 //{{{ Find fold start and fold end...
565 if(line != lineCount - 1
566 && buffer._isLineVisible(line,index)
567 && !buffer._isLineVisible(line + 1,index)
568 && buffer.getFoldLevel(line + 1) > initialFoldLevel)
569 {
570 // this line is the start of a fold
571 start = line + 1;
572
573 for(int i = line + 1; i < lineCount; i++)
574 {
575 if(buffer._isLineVisible(i,index) && buffer.getFoldLevel(i) <= initialFoldLevel)
576 {
577 end = i - 1;
578 break;
579 }
580 }
581 }
582 else
583 {
584 boolean ok = false;
585
586 // scan backwards looking for the start
587 for(int i = line - 1; i >= 0; i--)
588 {
589 if(buffer._isLineVisible(i,index) && buffer.getFoldLevel(i) < initialFoldLevel)
590 {
591 start = i + 1;
592 ok = true;
593 break;
594 }
595 }
596
597 if(!ok)
598 {
599 // no folds in buffer
600 return -1;
601 }
602
603 for(int i = line + 1; i < lineCount; i++)
604 {
605 if(buffer._isLineVisible(i,index) && buffer.getFoldLevel(i) < initialFoldLevel)
606 {
607 end = i - 1;
608 break;
609 }
610 }
611 } //}}}
612
613 //{{{ Expand the fold...
614
615 // we need a different value of initialFoldLevel here!
616 initialFoldLevel = buffer.getFoldLevel(start);
617
618 for(int i = start; i <= end; i++)
619 {
620 if(buffer.getFoldLevel(i) > initialFoldLevel)
621 {
622 if(returnValue == -1
623 && i != 0
624 && buffer.isFoldStart(i - 1))
625 {
626 returnValue = i - 1;
627 }
628
629 if(!buffer._isLineVisible(i,index) && fully)
630 {
631 delta++;
632 buffer._setLineVisible(i,index,true);
633 }
634 }
635 else if(!buffer._isLineVisible(i,index))
636 {
637 delta++;
638 buffer._setLineVisible(i,index,true);
639 }
640 }
641
642 buffer._setVirtualLineCount(index,
643 buffer._getVirtualLineCount(index)
644 + delta);
645 //}}}
646
647 if(!fully && !buffer._isLineVisible(line,index))
648 {
649 // this is a hack, and really needs to be done better.
650 expandFold(line,false);
651 return returnValue;
652 }
653 }
654 finally
655 {
656 buffer.writeUnlock();
657 }
658
659 foldStructureChanged();
660
661 int virtualLine = physicalToVirtual(start);
662 int firstLine = textArea.getFirstLine();
663 int visibleLines = textArea.getVisibleLines();
664 if(virtualLine + delta >= firstLine + visibleLines
665 && delta < visibleLines - 1)
666 {
667 textArea.setFirstLine(virtualLine + delta - visibleLines + 1);
668 }
669
670 return returnValue;
671 } //}}}
672
673 //{{{ expandAllFolds() method
674 /**
675 * Expands all folds.
676 * @since jEdit 4.0pre1
677 */
678 public void expandAllFolds()
679 {
680 try
681 {
682 buffer.writeLock();
683
684 narrowed = false;
685
686 buffer._setVirtualLineCount(index,buffer.getLineCount());
687 for(int i = 0; i < buffer.getLineCount(); i++)
688 {
689 buffer._setLineVisible(i,index,true);
690 }
691 foldStructureChanged();
692 }
693 finally
694 {
695 buffer.writeUnlock();
696 }
697 } //}}}
698
699 //{{{ expandFolds() method
700 /**
701 * This method should only be called from <code>actions.xml</code>.
702 * @since jEdit 4.0pre1
703 */
704 public void expandFolds(char digit)
705 {
706 if(digit < '1' || digit > '9')
707 {
708 Toolkit.getDefaultToolkit().beep();
709 return;
710 }
711 else
712 expandFolds((int)(digit - '1') + 1);
713 } //}}}
714
715 //{{{ expandFolds() method
716 /**
717 * Expands all folds with the specified fold level.
718 * @param foldLevel The fold level
719 * @since jEdit 4.0pre1
720 */
721 public void expandFolds(int foldLevel)
722 {
723 try
724 {
725 buffer.writeLock();
726
727 narrowed = false;
728
729 // so that getFoldLevel() calling fireFoldLevelsChanged()
730 // won't break
731 buffer._setVirtualLineCount(index,buffer.getLineCount());
732
733 int newVirtualLineCount = 0;
734 foldLevel = (foldLevel - 1) * buffer.getIndentSize() + 1;
735
736 /* this ensures that the first line is always visible */
737 boolean seenVisibleLine = false;
738
739 for(int i = 0; i < buffer.getLineCount(); i++)
740 {
741 if(!seenVisibleLine || buffer.getFoldLevel(i) < foldLevel)
742 {
743 seenVisibleLine = true;
744 buffer._setLineVisible(i,index,true);
745 newVirtualLineCount++;
746 }
747 else
748 buffer._setLineVisible(i,index,false);
749 }
750
751 buffer._setVirtualLineCount(index,newVirtualLineCount);
752 }
753 finally
754 {
755 buffer.writeUnlock();
756 }
757
758 foldStructureChanged();
759 } //}}}
760
761 //{{{ narrow() method
762 /**
763 * Narrows the visible portion of the buffer to the specified
764 * line range.
765 * @param start The first line
766 * @param end The last line
767 * @since jEdit 4.0pre1
768 */
769 public void narrow(int start, int end)
770 {
771 int virtualLineCount = buffer._getVirtualLineCount(index);
772 for(int i = 0; i < start; i++)
773 {
774 if(buffer._isLineVisible(i,index))
775 {
776 virtualLineCount--;
777 buffer._setLineVisible(i,index,false);
778 }
779 }
780
781 for(int i = end + 1; i < buffer.getLineCount(); i++)
782 {
783 if(buffer._isLineVisible(i,index))
784 {
785 virtualLineCount--;
786 buffer._setLineVisible(i,index,false);
787 }
788 }
789
790 buffer._setVirtualLineCount(index,virtualLineCount);
791
792 narrowed = true;
793
794 foldStructureChanged();
795
796 // Hack... need a more direct way of obtaining a view?
797 // JEditTextArea.getView() method?
798 GUIUtilities.getView(textArea).getStatus().setMessageAndClear(
799 jEdit.getProperty("view.status.narrow"));
800 } //}}}
801
802 //{{{ Methods for Buffer class to call
803
804 //{{{ _grab() method
805 /**
806 * Do not call this method. The only reason it is public is so
807 * that the <code>Buffer</code> class can call it.
808 */
809 public final void _grab(int index)
810 {
811 this.index = index;
812 lastPhysical = lastVirtual = -1;
813 } //}}}
814
815 //{{{ _release() method
816 /**
817 * Do not call this method. The only reason it is public is so
818 * that the <code>Buffer</code> class can call it.
819 */
820 public final void _release()
821 {
822 index = -1;
823 } //}}}
824
825 //{{{ _getIndex() method
826 /**
827 * Do not call this method. The only reason it is public is so
828 * that the <code>Buffer</code> class can call it.
829 */
830 public final int _getIndex()
831 {
832 return index;
833 } //}}}
834
835 //{{{ _invalidate() method
836 /**
837 * Do not call this method. The only reason it is public is so
838 * that the <code>Buffer</code> class can call it.
839 */
840 public void _invalidate(int startLine)
841 {
842 if(lastPhysical >= startLine)
843 lastPhysical = lastVirtual = 0;
844 } //}}}
845
846 //}}}
847
848 //{{{ Private members
849
850 //{{{ Instance variables
851 private Buffer buffer;
852 private JEditTextArea textArea;
853 private int index;
854 private int lastPhysical;
855 private int lastVirtual;
856 private boolean narrowed;
857 //}}}
858
859 //{{{ foldStructureChanged() method
860 private void foldStructureChanged()
861 {
862 lastPhysical = lastVirtual = -1;
863 textArea.foldStructureChanged();
864 } //}}}
865
866 //}}}
867}