PageRenderTime 143ms CodeModel.GetById 101ms app.highlight 37ms RepoModel.GetById 0ms app.codeStats 1ms

/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
  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}