source: other-projects/rsyntax-textarea/src/java/org/fife/ui/rsyntaxtextarea/folding/Fold.java@ 25584

Last change on this file since 25584 was 25584, checked in by davidb, 12 years ago

Initial cut an a text edit area for GLI that supports color syntax highlighting

File size: 15.2 KB
Line 
1/*
2 * 10/08/2011
3 *
4 * Fold.java - A foldable region of text in an RSyntaxTextArea instance.
5 *
6 * This library is distributed under a modified BSD license. See the included
7 * RSyntaxTextArea.License.txt file for details.
8 */
9package org.fife.ui.rsyntaxtextarea.folding;
10
11import java.util.ArrayList;
12import java.util.List;
13import javax.swing.text.BadLocationException;
14import javax.swing.text.Element;
15import javax.swing.text.Position;
16
17import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
18
19
20/**
21 * Information about a foldable region.<p>
22 *
23 * A <code>Fold</code> has zero or more children, and <code>Folds</code> thus
24 * form a hierarchical structure, with "parent" folds containing the info about
25 * any "child" folds they contain.<p>
26 *
27 * Fold regions are denoted by a starting and ending offset, but the actual
28 * folding is done on a per-line basis, so <code>Fold</code> instances provide
29 * methods for retrieving both starting and ending offsets and lines. The
30 * starting and ending offsets/lines are "sticky" and correctly track their
31 * positions even as the document is modified.
32 *
33 * @author Robert Futrell
34 * @version 1.0
35 */
36public class Fold implements Comparable {
37
38 private int type;
39 private RSyntaxTextArea textArea;
40 private Position startOffs;
41 private Position endOffs;
42 private Fold parent;
43 private List children;
44 private boolean collapsed;
45 private int childCollapsedLineCount;
46
47 private int lastStartOffs = -1;
48 private int cachedStartLine;
49
50 private int lastEndOffs = -1;
51 private int cachedEndLine;
52
53
54 public Fold(int type, RSyntaxTextArea textArea, int startOffs)
55 throws BadLocationException {
56 this.type = type;
57 this.textArea = textArea;
58 this.startOffs = textArea.getDocument().createPosition(startOffs);
59 }
60
61
62 /**
63 * Creates a fold that is a child of this one.
64 *
65 * @param type The type of fold.
66 * @param startOffs The starting offset of the fold.
67 * @return The child fold.
68 * @throws BadLocationException If <code>startOffs</code> is invalid.
69 */
70 public Fold createChild(int type, int startOffs) throws BadLocationException {
71 Fold child = new Fold(type, textArea, startOffs);
72 child.parent = this;
73 if (children==null) {
74 children = new ArrayList();
75 }
76 children.add(child);
77 return child;
78 }
79
80
81 /**
82 * Two folds are considered equal if they start at the same offset.
83 *
84 * @param otherFold Another fold to compare this one to.
85 * @return How this fold compares to the other.
86 */
87 public int compareTo(Object otherFold) {
88 int result = -1;
89 if (otherFold instanceof Fold) {
90 result = startOffs.getOffset() - ((Fold)otherFold).startOffs.getOffset();
91 //result = getStartLine() - ((Fold)otherFold).getStartLine();
92 }
93 return result;
94 }
95
96
97 /**
98 * Returns whether the specified line would be hidden in this fold. Since
99 * RSTA displays the "first" line in a fold, this means that the line must
100 * must be between <code>(getStartLine()+1)</code> and
101 * <code>getEndLine()</code>, inclusive.
102 *
103 * @param line The line to check.
104 * @return Whether the line would be hidden if this fold is collapsed.
105 * @see #containsOffset(int)
106 * @see #containsOrStartsOnLine(int)
107 */
108 public boolean containsLine(int line) {
109 return line>getStartLine() && line<=getEndLine();
110 }
111
112
113 /**
114 * Returns whether the given line is in the range
115 * <code>[getStartLine(), getEndLine()]</code>, inclusive.
116 *
117 * @param line The line to check.
118 * @return Whether this fold contains, or starts on, the line.
119 * @see #containsLine(int)
120 */
121 public boolean containsOrStartsOnLine(int line) {
122 return line>=getStartLine() && line<=getEndLine();
123 }
124
125
126 /**
127 * Returns whether the specified offset is "inside" the fold. This method
128 * returns <code>true</code> if the offset is greater than the fold start
129 * offset, and no further than the last offset of the last folded line.
130 *
131 * @param offs The offset to check.
132 * @return Whether the offset is "inside" the fold.
133 * @see #containsLine(int)
134 */
135 public boolean containsOffset(int offs) {
136 boolean contained = false;
137 if (offs>getStartOffset()) {
138 // Use Elements to avoid BadLocationExceptions
139 Element root = textArea.getDocument().getDefaultRootElement();
140 int line = root.getElementIndex(offs);
141 contained = line<=getEndLine();
142 }
143 return contained;
144 }
145
146
147 /**
148 * Two folds are considered equal if they have the same starting offset.
149 *
150 * @param otherFold Another fold to compare this one to.
151 * @return Whether the two folds are equal.
152 * @see #compareTo(Object)
153 */
154 public boolean equals(Object otherFold) {
155 return compareTo(otherFold)==0;
156 }
157
158
159 /**
160 * Returns a specific child fold.
161 *
162 * @param index The index of the child fold.
163 * @return The child fold.
164 * @see #getChildCount()
165 */
166 public Fold getChild(int index) {
167 return (Fold)children.get(index);
168 }
169
170
171 /**
172 * Returns the number of child folds.
173 *
174 * @return The number of child folds.
175 * @see #getChild(int)
176 */
177 public int getChildCount() {
178 return children==null ? 0 : children.size();
179 }
180
181
182 /**
183 * Returns the array of child folds.
184 *
185 * @return The array of child folds.
186 */
187 List getChildren() {
188 return children;
189 }
190
191
192 /**
193 * Returns the number of collapsed lines under this fold. If this fold
194 * is collapsed, this method returns {@link #getLineCount()}, otherwise
195 * it returns the sum of all collapsed lines of all child folds of this
196 * one.<p>
197 *
198 * The value returned is cached, so this method returns quickly and
199 * shouldn't affect performance.
200 *
201 * @return The number of collapsed lines under this fold.
202 */
203 public int getCollapsedLineCount() {
204 return collapsed ? getLineCount() : childCollapsedLineCount;
205 }
206
207
208 /**
209 * Returns the "deepest" fold containing the specified offset. It is
210 * assumed that it's already been verified that <code>offs</code> is indeed
211 * contained in this fold.
212 *
213 * @param offs The offset.
214 * @return The fold, or <code>null</code> if no child fold also contains
215 * the offset.
216 * @see FoldManager#getDeepestFoldContaining(int)
217 */
218 Fold getDeepestFoldContaining(int offs) {
219 Fold deepestFold = this;
220 for (int i=0; i<getChildCount(); i++) {
221 Fold fold = getChild(i);
222 if (fold.containsOffset(offs)) {
223 deepestFold = fold.getDeepestFoldContaining(offs);
224 break;
225 }
226 }
227 return deepestFold;
228 }
229
230
231 /**
232 * Returns the "deepest" open fold containing the specified offset. It
233 * is assumed that it's already been verified that <code>offs</code> is
234 * indeed contained in this fold.
235 *
236 * @param offs The offset.
237 * @return The fold, or <code>null</code> if no open fold contains the
238 * offset.
239 * @see FoldManager#getDeepestOpenFoldContaining(int)
240 */
241 Fold getDeepestOpenFoldContaining(int offs) {
242
243 Fold deepestFold = this;
244
245 for (int i=0; i<getChildCount(); i++) {
246 Fold fold = getChild(i);
247 if (fold.containsOffset(offs)) {
248 if (fold.isCollapsed()) {
249 break;
250 }
251 deepestFold = fold.getDeepestOpenFoldContaining(offs);
252 break;
253 }
254 }
255
256 return deepestFold;
257
258 }
259
260
261 /**
262 * Returns the end line of this fold. For example, in languages such as
263 * C and Java, this might be the line containing the closing curly brace of
264 * a code block.<p>
265 *
266 * The value returned by this method will automatically update as the
267 * text area's contents are modified, to track the ending line of the
268 * code block.
269 *
270 * @return The end line of this code block.
271 * @see #getEndOffset()
272 * @see #getStartLine()
273 */
274 public int getEndLine() {
275 int endOffs = getEndOffset();
276 if (lastEndOffs==endOffs) {
277 return cachedEndLine;
278 }
279 lastEndOffs = endOffs;
280 Element root = textArea.getDocument().getDefaultRootElement();
281 return cachedEndLine = root.getElementIndex(endOffs);
282 }
283
284
285 /**
286 * Returns the end offset of this fold. For example, in languages such as
287 * C and Java, this might be the offset of the closing curly brace of a
288 * code block.<p>
289 *
290 * The value returned by this method will automatically update as the
291 * text area's contents are modified, to track the ending offset of the
292 * code block.
293 *
294 * @return The end offset of this code block, or {@link Integer#MAX_VALUE}
295 * if this fold region isn't closed properly. The latter causes
296 * this fold to collapsed all lines through the end of the file.
297 * @see #getEndLine()
298 * @see #getStartOffset()
299 */
300 public int getEndOffset() {
301 return endOffs!=null ? endOffs.getOffset() : Integer.MAX_VALUE;
302 }
303
304
305 /**
306 * Returns the type of fold this is. This will be one of the values in
307 * {@link FoldType}, or a user-defined value.
308 *
309 * @return The type of fold this is.
310 */
311 public int getFoldType() {
312 return type;
313 }
314
315
316 /**
317 * Returns whether this fold has any child folds.
318 *
319 * @return Whether this fold has any children.
320 * @see #getChildCount()
321 */
322 public boolean getHasChildFolds() {
323 return getChildCount()>0;
324 }
325
326
327 /**
328 * Returns the last child fold.
329 *
330 * @return The last child fold, or <code>null</code> if this fold does not
331 * have any children.
332 * @see #getChild(int)
333 * @see #getHasChildFolds()
334 */
335 public Fold getLastChild() {
336 int childCount = getChildCount();
337 return childCount==0 ? null : getChild(childCount-1);
338 }
339
340
341 /**
342 * Returns the number of lines that are hidden when this fold is
343 * collapsed.
344 *
345 * @return The number of lines hidden.
346 * @see #getStartLine()
347 * @see #getEndLine()
348 */
349 public int getLineCount() {
350 return getEndLine() - getStartLine();
351 }
352
353
354 /**
355 * Returns the parent fold of this one.
356 *
357 * @return The parent fold, or <code>null</code> if this is a top-level
358 * fold.
359 */
360 public Fold getParent() {
361 return parent;
362 }
363
364
365 /**
366 * Returns the starting line of this fold region. This is the only line
367 * in the fold region that is not hidden when a fold is collapsed.<p>
368 *
369 * The value returned by this method will automatically update as the
370 * text area's contents are modified, to track the starting line of the
371 * code block.
372 *
373 * @return The starting line of the code block.
374 * @see #getEndLine()
375 * @see #getStartOffset()
376 */
377 public int getStartLine() {
378 int startOffs = getStartOffset();
379 if (lastStartOffs==startOffs) {
380 return cachedStartLine;
381 }
382 lastStartOffs = startOffs;
383 Element root = textArea.getDocument().getDefaultRootElement();
384 return cachedStartLine = root.getElementIndex(startOffs);
385 }
386
387
388 /**
389 * Returns the starting offset of this fold region. For example, for
390 * languages such as C and Java, this would be the offset of the opening
391 * curly brace of a code block.<p>
392 *
393 * The value returned by this method will automatically update as the
394 * text area's contents are modified, to track the starting offset of the
395 * code block.
396 *
397 * @return The start offset of this fold.
398 * @see #getStartLine()
399 * @see #getEndOffset()
400 */
401 public int getStartOffset() {
402 return startOffs.getOffset();
403 }
404
405
406 public int hashCode() {
407 return getStartLine();
408 }
409
410
411 /**
412 * Returns whether this fold is collapsed.
413 *
414 * @return Whether this fold is collapsed.
415 * @see #setCollapsed(boolean)
416 * @see #toggleCollapsedState()
417 */
418 public boolean isCollapsed() {
419 return collapsed;
420 }
421
422
423 /**
424 * Returns whether this fold is entirely on a single line. In general,
425 * a {@link FoldParser} should not remember fold regions all on a single
426 * line, since there's really nothing to fold.
427 *
428 * @return Whether this fold is on a single line.
429 * @see #removeFromParent()
430 */
431 public boolean isOnSingleLine() {
432 return getStartLine()==getEndLine();
433 }
434
435
436 /**
437 * Removes this fold from its parent. This should only be called by
438 * {@link FoldParser} implementations if they determine that a fold is all
439 * on a single line (and thus shouldn't be remembered) after creating it.
440 *
441 * @see #isOnSingleLine()
442 */
443 public void removeFromParent() {
444 if (parent!=null) {
445 parent.removeMostRecentChild();
446 this.parent = null;
447 }
448 }
449
450
451 private void removeMostRecentChild() {
452 children.remove(children.size()-1);
453 }
454
455
456 /**
457 * Sets whether this <code>Fold</code> is collapsed. Calling this method
458 * will update both the text area and all <code>Gutter</code> components.
459 *
460 * @param collapsed Whether this fold should be collapsed.
461 * @see #isCollapsed()
462 * @see #toggleCollapsedState()
463 */
464 public void setCollapsed(boolean collapsed) {
465
466 if (collapsed!=this.collapsed) {
467
468 // Change our fold state and cached info about folded line count.
469 int lineCount = getLineCount();
470 int linesToCollapse = lineCount - childCollapsedLineCount;
471 if (!collapsed) { // If we're expanding
472 linesToCollapse = -linesToCollapse;
473 }
474 //System.out.println("Hiding lines: " + linesToCollapse +
475 // " (" + lineCount + ", " + linesToCollapse + ")");
476 this.collapsed = collapsed;
477 if (parent!=null) {
478 parent.updateChildCollapsedLineCount(linesToCollapse);
479 }
480
481 // If an end point of the selection is being hidden, move the caret
482 // "out" of the fold.
483 if (collapsed) {
484 int dot = textArea.getSelectionStart(); // Forgive variable name
485 Element root = textArea.getDocument().getDefaultRootElement();
486 int dotLine = root.getElementIndex(dot);
487 boolean updateCaret = containsLine(dotLine);
488 if (!updateCaret) {
489 int mark = textArea.getSelectionEnd();
490 if (mark!=dot) {
491 int markLine = root.getElementIndex(mark);
492 updateCaret = containsLine(markLine);
493 }
494 }
495 if (updateCaret) {
496 dot = root.getElement(getStartLine()).getEndOffset() - 1;
497 textArea.setCaretPosition(dot);
498 }
499 }
500
501 textArea.foldToggled(this);
502
503 }
504
505 }
506
507
508 /**
509 * Sets the ending offset of this fold, such as the closing curly brace
510 * of a code block in C or Java. {@link FoldParser} implementations should
511 * call this on an existing <code>Fold</code> upon finding its end. If
512 * this method isn't called, then this <code>Fold</code> is considered to
513 * have no end, i.e., it will collapse everything to the end of the file.
514 *
515 * @param endOffs The end offset of this fold.
516 * @throws BadLocationException If <code>endOffs</code> is not a valid
517 * location in the text area.
518 */
519 public void setEndOffset(int endOffs) throws BadLocationException {
520 this.endOffs = textArea.getDocument().createPosition(endOffs);
521 }
522
523
524 /**
525 * Toggles the collapsed state of this fold.
526 *
527 * @see #setCollapsed(boolean)
528 */
529 public void toggleCollapsedState() {
530 setCollapsed(!collapsed);
531 }
532
533
534 private void updateChildCollapsedLineCount(int count) {
535 childCollapsedLineCount += count;
536 //if (childCollapsedLineCount>getLineCount()) {
537 // Thread.dumpStack();
538 //}
539 if (!collapsed && parent!=null) {
540 parent.updateChildCollapsedLineCount(count);
541 }
542 }
543
544
545 /**
546 * Overridden for debugging purposes.
547 *
548 * @return A string representation of this <code>Fold</code>.
549 */
550 public String toString() {
551 return "[Fold: " +
552 "startOffs=" + getStartOffset() +
553 ", endOffs=" + getEndOffset() +
554 ", collapsed=" + collapsed +
555 "]";
556 }
557
558
559}
Note: See TracBrowser for help on using the repository browser.