1
/*******************************************************************************
2
* Copyright (c) 2000, 2010 IBM Corporation and others.
3
* All rights reserved. This program and the accompanying materials
4
* are made available under the terms of the Eclipse Public License v1.0
5
* which accompanies this distribution, and is available at
6
* http://www.eclipse.org/legal/epl-v10.html
9
* IBM Corporation - initial API and implementation
10
* Anton Leherbauer (Wind River Systems)
11
* Markus Schorn (Wind River Systems)
12
* Elazar Leibovich (IDF) - Code folding of compound statements (bug 174597)
13
* Andrew Ferguson (Symbian)
14
*******************************************************************************/
16
package org.eclipse.cdt.internal.ui.text.folding;
18
import java.util.ArrayList;
19
import java.util.Collection;
20
import java.util.Collections;
21
import java.util.Comparator;
22
import java.util.HashMap;
23
import java.util.Iterator;
24
import java.util.LinkedHashMap;
25
import java.util.List;
27
import java.util.Stack;
29
import org.eclipse.core.runtime.Assert;
30
import org.eclipse.core.runtime.IProgressMonitor;
31
import org.eclipse.core.runtime.IStatus;
32
import org.eclipse.core.runtime.Platform;
33
import org.eclipse.core.runtime.Status;
34
import org.eclipse.jface.preference.IPreferenceStore;
35
import org.eclipse.jface.text.BadLocationException;
36
import org.eclipse.jface.text.IDocument;
37
import org.eclipse.jface.text.IRegion;
38
import org.eclipse.jface.text.ITextSelection;
39
import org.eclipse.jface.text.ITypedRegion;
40
import org.eclipse.jface.text.Position;
41
import org.eclipse.jface.text.Region;
42
import org.eclipse.jface.text.TextUtilities;
43
import org.eclipse.jface.text.source.Annotation;
44
import org.eclipse.jface.text.source.projection.IProjectionListener;
45
import org.eclipse.jface.text.source.projection.IProjectionPosition;
46
import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
47
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
48
import org.eclipse.jface.text.source.projection.ProjectionViewer;
49
import org.eclipse.jface.viewers.ISelection;
50
import org.eclipse.jface.viewers.ISelectionChangedListener;
51
import org.eclipse.jface.viewers.SelectionChangedEvent;
52
import org.eclipse.ui.texteditor.IDocumentProvider;
53
import org.eclipse.ui.texteditor.ITextEditor;
55
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
56
import org.eclipse.cdt.core.dom.ast.IASTBreakStatement;
57
import org.eclipse.cdt.core.dom.ast.IASTCaseStatement;
58
import org.eclipse.cdt.core.dom.ast.IASTCompoundStatement;
59
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
60
import org.eclipse.cdt.core.dom.ast.IASTDefaultStatement;
61
import org.eclipse.cdt.core.dom.ast.IASTDoStatement;
62
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
63
import org.eclipse.cdt.core.dom.ast.IASTForStatement;
64
import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator;
65
import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition;
66
import org.eclipse.cdt.core.dom.ast.IASTIfStatement;
67
import org.eclipse.cdt.core.dom.ast.IASTNodeLocation;
68
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorElifStatement;
69
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorElseStatement;
70
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorEndifStatement;
71
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfStatement;
72
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfdefStatement;
73
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfndefStatement;
74
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement;
75
import org.eclipse.cdt.core.dom.ast.IASTProblem;
76
import org.eclipse.cdt.core.dom.ast.IASTStatement;
77
import org.eclipse.cdt.core.dom.ast.IASTSwitchStatement;
78
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
79
import org.eclipse.cdt.core.dom.ast.IASTWhileStatement;
80
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTRangeBasedForStatement;
81
import org.eclipse.cdt.core.model.CModelException;
82
import org.eclipse.cdt.core.model.ICElement;
83
import org.eclipse.cdt.core.model.ILanguage;
84
import org.eclipse.cdt.core.model.IParent;
85
import org.eclipse.cdt.core.model.ISourceRange;
86
import org.eclipse.cdt.core.model.ISourceReference;
87
import org.eclipse.cdt.core.model.ITranslationUnit;
88
import org.eclipse.cdt.core.parser.IProblem;
89
import org.eclipse.cdt.ui.CUIPlugin;
90
import org.eclipse.cdt.ui.PreferenceConstants;
91
import org.eclipse.cdt.ui.text.ICPartitions;
92
import org.eclipse.cdt.ui.text.folding.ICFoldingStructureProvider;
94
import org.eclipse.cdt.internal.core.dom.parser.ASTQueries;
95
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;
96
import org.eclipse.cdt.internal.core.model.ASTCache;
98
import org.eclipse.cdt.internal.ui.editor.ASTProvider;
99
import org.eclipse.cdt.internal.ui.editor.CEditor;
100
import org.eclipse.cdt.internal.ui.editor.ASTProvider.WAIT_FLAG;
101
import org.eclipse.cdt.internal.ui.text.DocumentCharacterIterator;
102
import org.eclipse.cdt.internal.ui.text.ICReconcilingListener;
105
* Default implementation of a {@link ICFoldingStructureProvider}.
107
* Derived from JDT counterpart.
110
public class DefaultCFoldingStructureProvider implements ICFoldingStructureProvider {
113
* A visitor to collect compound statement positions.
117
private final class StatementVisitor extends ASTVisitor {
119
shouldVisitStatements = true;
120
shouldVisitDeclarations = true;
122
private final Stack<StatementRegion> fStatements;
124
String fFunction= ""; //$NON-NLS-1$
126
private StatementVisitor(Stack<StatementRegion> statements) {
127
fStatements = statements;
131
public int visit(IASTStatement statement) {
133
// if it's not part of the displayed - file, we don't need it
134
if (!statement.isPartOfTranslationUnitFile())
135
return PROCESS_SKIP;// we neither need its descendants
139
if (statement instanceof IASTIfStatement) {
140
IASTIfStatement ifstmt = (IASTIfStatement) statement;
141
fl = ifstmt.getFileLocation();
142
if (fl==null) return PROCESS_CONTINUE;
143
int ifOffset= fl.getNodeOffset();
144
IASTStatement thenStmt;
146
thenStmt = ifstmt.getThenClause();
147
if (thenStmt==null) return PROCESS_CONTINUE;
148
fl = thenStmt.getFileLocation();
149
mr.setLength(fl.getNodeOffset() + fl.getNodeLength() - ifOffset);
150
mr.setOffset(ifOffset);
151
mr.inclusive = !(thenStmt instanceof IASTCompoundStatement);
152
IASTStatement elseStmt;
153
elseStmt = ifstmt.getElseClause();
154
if (elseStmt == null) {
156
fStatements.push(mr);
157
return PROCESS_CONTINUE;
159
IASTFileLocation elseStmtLocation = elseStmt.getFileLocation();
160
mr.inclusive = mr.inclusive || fl.getEndingLineNumber() < elseStmtLocation.getStartingLineNumber();
161
if (elseStmt instanceof IASTIfStatement) {
162
fStatements.push(mr);
163
return PROCESS_CONTINUE;
165
fStatements.push(mr);
167
mr.setLength(elseStmtLocation.getNodeLength());
168
mr.setOffset(elseStmtLocation.getNodeOffset());
170
fStatements.push(mr);
171
return PROCESS_CONTINUE;
175
if (statement instanceof IASTDoStatement)
176
mr.inclusive = false;
177
if (statement instanceof IASTSwitchStatement) {
178
IASTStatement switchstmt = ((IASTSwitchStatement)statement).getBody();
179
if (switchstmt instanceof IASTCompoundStatement) {
180
IASTStatement[] stmts = ((IASTCompoundStatement)switchstmt).getStatements();
181
boolean pushedMR = false;
182
for (IASTStatement tmpstmt : stmts) {
183
StatementRegion tmpmr;
184
if (!(tmpstmt instanceof IASTCaseStatement || tmpstmt instanceof IASTDefaultStatement)) {
185
if (!pushedMR) return PROCESS_SKIP;
186
IASTFileLocation tmpfl = tmpstmt.getFileLocation();
187
tmpmr = fStatements.peek();
188
tmpmr.setLength(tmpfl.getNodeLength()+tmpfl.getNodeOffset()-tmpmr.getOffset());
189
if (tmpstmt instanceof IASTBreakStatement) pushedMR = false;
192
IASTFileLocation tmpfl;
193
tmpmr = createRegion();
194
tmpmr.level= fLevel+1;
195
tmpmr.inclusive = true;
196
if (tmpstmt instanceof IASTCaseStatement) {
197
IASTCaseStatement casestmt = (IASTCaseStatement) tmpstmt;
198
tmpfl = casestmt.getExpression().getFileLocation();
199
tmpmr.setOffset(tmpfl.getNodeOffset());
200
tmpmr.setLength(tmpfl.getNodeLength());
201
} else if (tmpstmt instanceof IASTDefaultStatement) {
202
IASTDefaultStatement defstmt = (IASTDefaultStatement) tmpstmt;
203
tmpfl = defstmt.getFileLocation();
204
tmpmr.setOffset(tmpfl.getNodeOffset()+tmpfl.getNodeLength());
207
fStatements.push(tmpmr);
212
if (statement instanceof IASTForStatement
213
|| statement instanceof IASTWhileStatement
214
|| statement instanceof IASTDoStatement
215
|| statement instanceof IASTSwitchStatement
216
|| statement instanceof ICPPASTRangeBasedForStatement) {
217
fl = statement.getFileLocation();
218
mr.setLength(fl.getNodeLength());
219
mr.setOffset(fl.getNodeOffset());
220
fStatements.push(mr);
222
return PROCESS_CONTINUE;
223
} catch (Exception e) {
225
return PROCESS_ABORT;
230
public int leave(IASTStatement statement) {
232
return PROCESS_CONTINUE;
236
public int visit(IASTDeclaration declaration) {
237
if (!declaration.isPartOfTranslationUnitFile())
238
return PROCESS_SKIP;// we neither need its descendants
239
if (declaration instanceof IASTFunctionDefinition) {
240
final IASTFunctionDeclarator declarator = ((IASTFunctionDefinition)declaration).getDeclarator();
241
if (declarator != null) {
242
fFunction= new String(ASTQueries.findInnermostDeclarator(declarator).getName().toCharArray());
246
return PROCESS_CONTINUE;
250
public int leave(IASTDeclaration declaration) {
251
if (declaration instanceof IASTFunctionDefinition) {
252
fFunction= ""; //$NON-NLS-1$
254
return PROCESS_CONTINUE;
257
private StatementRegion createRegion() {
258
return new StatementRegion(fFunction, fLevel);
263
* Listen to cursor position changes.
265
private final class SelectionListener implements ISelectionChangedListener {
266
public void selectionChanged(SelectionChangedEvent event) {
267
ISelection s= event.getSelection();
268
if (s instanceof ITextSelection) {
269
ITextSelection selection= (ITextSelection)event.getSelection();
270
fCursorPosition= selection.getOffset();
276
* Update folding positions triggered by reconciler.
278
private class FoldingStructureReconciler implements ICReconcilingListener {
279
private volatile boolean fReconciling;
282
* @see org.eclipse.cdt.internal.ui.text.ICReconcilingListener#aboutToBeReconciled()
284
public void aboutToBeReconciled() {
289
* @see org.eclipse.cdt.internal.ui.text.ICReconcilingListener#reconciled(IASTTranslationUnit, boolean, IProgressMonitor)
291
public void reconciled(IASTTranslationUnit ast, boolean force, IProgressMonitor progressMonitor) {
292
if (fInput == null || progressMonitor.isCanceled()) {
295
synchronized (this) {
302
final boolean initialReconcile= fInitialReconcilePending;
303
fInitialReconcilePending= false;
304
FoldingStructureComputationContext ctx= createContext(initialReconcile);
306
if (initialReconcile || !hasSyntaxError(ast)) {
317
* Test whether the given ast contains one or more syntax errors.
320
* @return <code>true</code> if the ast contains a syntax error
322
private boolean hasSyntaxError(IASTTranslationUnit ast) {
326
IASTProblem[] problems= ast.getPreprocessorProblems();
327
for (IASTProblem problem : problems) {
328
if ((problem.getID() & (IProblem.SYNTAX_ERROR | IProblem.SCANNER_RELATED)) != 0) {
332
problems= CPPVisitor.getProblems(ast);
333
for (IASTProblem problem : problems) {
334
if ((problem.getID() & (IProblem.SYNTAX_ERROR | IProblem.SCANNER_RELATED)) != 0) {
345
* A context that contains the information needed to compute the folding structure of an
346
* {@link ITranslationUnit}. Computed folding regions are collected via
347
* {@linkplain #addProjectionRange(DefaultCFoldingStructureProvider.CProjectionAnnotation, Position) addProjectionRange}.
349
protected final class FoldingStructureComputationContext {
350
private final ProjectionAnnotationModel fModel;
351
private final IDocument fDocument;
352
private final boolean fAllowCollapsing;
354
private ISourceReference fFirstType;
355
private boolean fHasHeaderComment;
356
private LinkedHashMap<CProjectionAnnotation,Position> fMap= new LinkedHashMap<CProjectionAnnotation,Position>();
357
private IASTTranslationUnit fAST;
359
FoldingStructureComputationContext(IDocument document, ProjectionAnnotationModel model, boolean allowCollapsing) {
360
Assert.isNotNull(document);
361
Assert.isNotNull(model);
364
fAllowCollapsing= allowCollapsing;
367
void setFirstType(ISourceReference reference) {
369
throw new IllegalStateException();
370
fFirstType= reference;
373
boolean hasFirstType() {
374
return fFirstType != null;
377
ISourceReference getFirstType() {
381
boolean hasHeaderComment() {
382
return fHasHeaderComment;
385
void setHasHeaderComment() {
386
fHasHeaderComment= true;
390
* Returns <code>true</code> if newly created folding regions may be collapsed,
391
* <code>false</code> if not. This is usually <code>false</code> when updating the
392
* folding structure while typing; it may be <code>true</code> when computing or restoring
393
* the initial folding structure.
395
* @return <code>true</code> if newly created folding regions may be collapsed,
396
* <code>false</code> if not
398
public boolean allowCollapsing() {
399
return fAllowCollapsing;
403
* Returns the document which contains the code being folded.
405
* @return the document which contains the code being folded
407
IDocument getDocument() {
411
ProjectionAnnotationModel getModel() {
416
* Adds a projection (folding) region to this context. The created annotation / position
417
* pair will be added to the {@link ProjectionAnnotationModel} of the
418
* {@link ProjectionViewer} of the editor.
420
* @param annotation the annotation to add
421
* @param position the corresponding position
423
public void addProjectionRange(CProjectionAnnotation annotation, Position position) {
424
fMap.put(annotation, position);
428
* Returns <code>true</code> if header comments should be collapsed.
430
* @return <code>true</code> if header comments should be collapsed
432
public boolean collapseHeaderComments() {
433
return fAllowCollapsing && fCollapseHeaderComments;
437
* Returns <code>true</code> if comments should be collapsed.
439
* @return <code>true</code> if comments should be collapsed
441
public boolean collapseComments() {
442
return fAllowCollapsing && fCollapseComments;
446
* Returns <code>true</code> if functions should be collapsed.
448
* @return <code>true</code> if functions should be collapsed
450
public boolean collapseFunctions() {
451
return fAllowCollapsing && fCollapseFunctions;
455
* Returns <code>true</code> if macros should be collapsed.
457
* @return <code>true</code> if macros should be collapsed
459
public boolean collapseMacros() {
460
return fAllowCollapsing && fCollapseMacros;
464
* Returns <code>true</code> if methods should be collapsed.
466
* @return <code>true</code> if methods should be collapsed
468
public boolean collapseMethods() {
469
return fAllowCollapsing && fCollapseMethods;
473
* Returns <code>true</code> if structures should be collapsed.
475
* @return <code>true</code> if structures should be collapsed
477
public boolean collapseStructures() {
478
return fAllowCollapsing && fCollapseStructures;
482
* Returns <code>true</code> if inactive code should be collapsed.
484
* @return <code>true</code> if inactive code should be collapsed
486
public boolean collapseInactiveCode() {
487
return fAllowCollapsing && fCollapseInactiveCode;
491
* @return the current AST or <code>null</code>
493
public IASTTranslationUnit getAST() {
499
private static class CProjectionAnnotation extends ProjectionAnnotation {
501
public final static int CMODEL= 0;
502
public final static int COMMENT= 1;
503
public final static int BRANCH= 2;
504
public final static int STATEMENT= 3;
507
private int fCategory;
509
public CProjectionAnnotation(boolean isCollapsed, Object key, boolean isComment) {
510
this(isCollapsed, key, isComment ? COMMENT : 0);
513
public CProjectionAnnotation(boolean isCollapsed, Object key, int category) {
519
public Object getElement() {
523
public void setElement(Object element) {
527
public int getCategory() {
531
// public void setCategory(int category) {
532
// fCategory = category;
535
// public boolean isComment() {
536
// return fCategory == COMMENT;
539
// public void setIsComment(boolean isComment) {
540
// fCategory= isComment ? COMMENT : 0;
543
* @see java.lang.Object#toString()
546
public String toString() {
547
return "CProjectionAnnotation:\n" + //$NON-NLS-1$
548
"\tkey: \t"+ fKey + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
549
"\tcollapsed: \t" + isCollapsed() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
550
"\tcategory: \t" + getCategory() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
555
private static final class Tuple {
556
CProjectionAnnotation annotation;
558
Tuple(CProjectionAnnotation annotation, Position position) {
559
this.annotation= annotation;
560
this.position= position;
564
private static final class Counter {
569
* Projection position that will return two foldable regions: one folding away
570
* the region from after the '/*' to the beginning of the content, the other
571
* from after the first content line until after the comment.
573
private static final class CommentPosition extends Position implements IProjectionPosition {
574
CommentPosition(int offset, int length) {
575
super(offset, length);
579
* @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
581
public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
582
DocumentCharacterIterator sequence= new DocumentCharacterIterator(document, offset, offset + length);
584
int contentStart= findFirstContent(sequence, prefixEnd);
586
int firstLine= document.getLineOfOffset(offset + prefixEnd);
587
int captionLine= document.getLineOfOffset(offset + contentStart);
588
int lastLine= document.getLineOfOffset(offset + length);
590
Assert.isTrue(firstLine <= captionLine, "first folded line is greater than the caption line"); //$NON-NLS-1$
591
Assert.isTrue(captionLine <= lastLine, "caption line is greater than the last folded line"); //$NON-NLS-1$
594
if (firstLine < captionLine) {
595
int preOffset= document.getLineOffset(firstLine);
596
IRegion preEndLineInfo= document.getLineInformation(captionLine);
597
int preEnd= preEndLineInfo.getOffset();
598
preRegion= new Region(preOffset, preEnd - preOffset);
603
if (captionLine < lastLine) {
604
int postOffset= document.getLineOffset(captionLine + 1);
605
IRegion postRegion= new Region(postOffset, offset + length - postOffset);
607
if (preRegion == null)
608
return new IRegion[] { postRegion };
610
return new IRegion[] { preRegion, postRegion };
613
if (preRegion != null)
614
return new IRegion[] { preRegion };
620
* Finds the offset of the first identifier part within <code>content</code>.
621
* Returns 0 if none is found.
623
* @param content the content to search
624
* @return the first index of a unicode identifier part, or zero if none can
627
private int findFirstContent(final CharSequence content, int prefixEnd) {
628
int lenght= content.length();
629
for (int i= prefixEnd; i < lenght; i++) {
630
if (Character.isUnicodeIdentifierPart(content.charAt(i)))
637
* @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
639
public int computeCaptionOffset(IDocument document) {
640
DocumentCharacterIterator sequence= new DocumentCharacterIterator(document, offset, offset + length);
641
return findFirstContent(sequence, 0);
646
* Projection position that will return two foldable regions: one folding away
647
* the lines before the one containing the simple name of the C element, one
648
* folding away any lines after the caption.
650
private static final class CElementPosition extends Position implements IProjectionPosition {
652
private ICElement fElement;
654
public CElementPosition(int offset, int length, ICElement element) {
655
super(offset, length);
656
Assert.isNotNull(element);
660
public void setElement(ICElement member) {
661
Assert.isNotNull(member);
666
* @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
668
public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
669
int captionOffset= offset;
671
/* The member's name range may not be correct. However,
672
* reconciling would trigger another element delta which would
673
* lead to reentrant situations. Therefore, we optimistically
674
* assume that the name range is correct, but double check the
675
* received lines below. */
676
if (fElement instanceof ISourceReference) {
677
ISourceRange sourceRange= ((ISourceReference) fElement).getSourceRange();
678
if (sourceRange != null) {
679
// Use end of name range for the caption offset
680
// in case a qualified name is split on multiple lines (bug 248613).
681
captionOffset= sourceRange.getIdStartPos() + sourceRange.getIdLength() - 1;
684
} catch (CModelException e) {
685
// ignore and use default
688
int firstLine= document.getLineOfOffset(offset);
689
int captionLine= document.getLineOfOffset(captionOffset);
690
int lastLine= document.getLineOfOffset(offset + length);
692
/* see comment above - adjust the caption line to be inside the
693
* entire folded region, and rely on later element deltas to correct
695
if (captionLine < firstLine)
696
captionLine= firstLine;
697
if (captionLine > lastLine)
698
captionLine= lastLine;
701
if (firstLine < captionLine) {
702
int preOffset= document.getLineOffset(firstLine);
703
IRegion preEndLineInfo= document.getLineInformation(captionLine);
704
int preEnd= preEndLineInfo.getOffset();
705
preRegion= new Region(preOffset, preEnd - preOffset);
710
if (captionLine < lastLine) {
711
int postOffset= document.getLineOffset(captionLine + 1);
712
IRegion postRegion= new Region(postOffset, offset + length - postOffset);
714
if (preRegion == null)
715
return new IRegion[] { postRegion };
717
return new IRegion[] { preRegion, postRegion };
720
if (preRegion != null)
721
return new IRegion[] { preRegion };
727
* @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
729
public int computeCaptionOffset(IDocument document) throws BadLocationException {
730
int captionOffset= offset;
732
// need a reconcile here?
733
if (fElement instanceof ISourceReference) {
734
ISourceRange sourceRange= ((ISourceReference) fElement).getSourceRange();
735
if (sourceRange != null) {
736
captionOffset= sourceRange.getIdStartPos() + sourceRange.getIdLength() - 1;
737
if (captionOffset < offset) {
738
captionOffset= offset;
742
} catch (CModelException e) {
743
// ignore and use default
746
return captionOffset - offset;
752
* Internal projection listener.
754
private final class ProjectionListener implements IProjectionListener {
755
private ProjectionViewer fViewer;
758
* Registers the listener with the viewer.
760
* @param viewer the viewer to register a listener with
762
public ProjectionListener(ProjectionViewer viewer) {
763
Assert.isLegal(viewer != null);
765
fViewer.addProjectionListener(this);
769
* Disposes of this listener and removes the projection listener from the viewer.
771
public void dispose() {
772
if (fViewer != null) {
773
fViewer.removeProjectionListener(this);
779
* @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled()
781
public void projectionEnabled() {
782
handleProjectionEnabled();
786
* @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled()
788
public void projectionDisabled() {
789
handleProjectionDisabled();
794
* Implementation of <code>IRegion</code> that can be reused
795
* by setting the offset and the length.
797
private static class ModifiableRegion extends Position implements IRegion {
801
ModifiableRegion(int offset, int length) {
802
super(offset, length);
807
* Representation of a preprocessor code branch.
809
private static class Branch extends ModifiableRegion {
811
private final boolean fTaken;
812
public final String fCondition;
813
public boolean fInclusive;
815
Branch(int offset, boolean taken, String key) {
816
this(offset, 0, taken, key);
819
Branch(int offset, int length, boolean taken, String key) {
820
super(offset, length);
825
public void setEndOffset(int endOffset) {
826
setLength(endOffset - getOffset());
829
public boolean taken() {
833
public void setInclusive(boolean inclusive) {
834
fInclusive= inclusive;
838
private final static boolean DEBUG= "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.cdt.ui/debug/folding")); //$NON-NLS-1$//$NON-NLS-2$;
840
private ITextEditor fEditor;
841
private ProjectionListener fProjectionListener;
842
protected ICElement fInput;
844
private boolean fCollapseHeaderComments= true;
845
private boolean fCollapseComments= false;
846
private boolean fCollapseMacros= false;
847
private boolean fCollapseFunctions= true;
848
private boolean fCollapseStructures= true;
849
private boolean fCollapseMethods= false;
850
private boolean fCollapseInactiveCode= true;
852
private int fMinCommentLines= 1;
853
private boolean fPreprocessorBranchFoldingEnabled= true;
854
private boolean fStatementsFoldingEnabled= false;
855
private boolean fCommentFoldingEnabled= true;
857
private ICReconcilingListener fReconilingListener;
858
private volatile boolean fInitialReconcilePending= true;
860
private int fCursorPosition;
862
private SelectionListener fSelectionListener;
866
* Creates a new folding provider. It must be
867
* {@link #install(ITextEditor, ProjectionViewer) installed} on an editor/viewer pair before it
868
* can be used, and {@link #uninstall() uninstalled} when not used any longer.
870
* The projection state may be reset by calling {@link #initialize()}.
873
public DefaultCFoldingStructureProvider() {
877
* @see org.eclipse.cdt.ui.text.folding.ICFoldingStructureProvider#install(org.eclipse.ui.texteditor.ITextEditor, org.eclipse.jface.text.source.projection.ProjectionViewer)
879
public void install(ITextEditor editor, ProjectionViewer viewer) {
880
Assert.isLegal(editor != null);
881
Assert.isLegal(viewer != null);
885
if (editor instanceof CEditor) {
887
fProjectionListener= new ProjectionListener(viewer);
892
* @see org.eclipse.cdt.ui.text.folding.ICFoldingStructureProvider#uninstall()
894
public void uninstall() {
899
* Internal implementation of {@link #uninstall()}.
901
private void internalUninstall() {
903
handleProjectionDisabled();
904
fProjectionListener.dispose();
905
fProjectionListener= null;
911
* Returns <code>true</code> if the provider is installed, <code>false</code> otherwise.
913
* @return <code>true</code> if the provider is installed, <code>false</code> otherwise
915
protected final boolean isInstalled() {
916
return fEditor != null;
920
* Called whenever projection is enabled, for example when the viewer issues a
921
* {@link IProjectionListener#projectionEnabled() projectionEnabled} message. When the provider
922
* is already enabled when this method is called, it is first
923
* {@link #handleProjectionDisabled() disabled}.
925
* Subclasses may extend.
928
protected void handleProjectionEnabled() {
929
if (DEBUG) System.out.println("DefaultCFoldingStructureProvider.handleProjectionEnabled()"); //$NON-NLS-1$
930
// projectionEnabled messages are not always paired with projectionDisabled
931
// i.e. multiple enabled messages may be sent out.
932
// we have to make sure that we disable first when getting an enable
934
handleProjectionDisabled();
936
if (fEditor instanceof CEditor) {
938
fReconilingListener= new FoldingStructureReconciler();
939
((CEditor)fEditor).addReconcileListener(fReconilingListener);
940
fSelectionListener= new SelectionListener();
941
fEditor.getSelectionProvider().addSelectionChangedListener(fSelectionListener);
946
* Called whenever projection is disabled, for example when the provider is
947
* {@link #uninstall() uninstalled}, when the viewer issues a
948
* {@link IProjectionListener#projectionDisabled() projectionDisabled} message and before
949
* {@link #handleProjectionEnabled() enabling} the provider. Implementations must be prepared to
950
* handle multiple calls to this method even if the provider is already disabled.
952
* Subclasses may extend.
955
protected void handleProjectionDisabled() {
956
if (fReconilingListener != null) {
957
((CEditor)fEditor).removeReconcileListener(fReconilingListener);
958
fReconilingListener= null;
960
if (fSelectionListener != null) {
961
fEditor.getSelectionProvider().removeSelectionChangedListener(fSelectionListener);
962
fSelectionListener= null;
967
* @see org.eclipse.cdt.ui.text.folding.ICFoldingStructureProvider#initialize()
969
public final void initialize() {
970
if (DEBUG) System.out.println("DefaultCFoldingStructureProvider.initialize()"); //$NON-NLS-1$
971
fInitialReconcilePending= true;
973
update(createInitialContext());
976
private FoldingStructureComputationContext createInitialContext() {
977
initializePreferences();
978
fInput= getInputElement();
982
return createContext(true);
985
private FoldingStructureComputationContext createContext(boolean allowCollapse) {
988
ProjectionAnnotationModel model= getModel();
991
IDocument doc= getDocument();
995
return new FoldingStructureComputationContext(doc, model, allowCollapse);
998
private ICElement getInputElement() {
999
if (fEditor instanceof CEditor) {
1000
return ((CEditor)fEditor).getInputCElement();
1005
private void initializePreferences() {
1006
IPreferenceStore store= CUIPlugin.getDefault().getPreferenceStore();
1007
fCollapseFunctions= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_FUNCTIONS);
1008
fCollapseStructures= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_STRUCTURES);
1009
fCollapseMacros= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_MACROS);
1010
fCollapseMethods= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS);
1011
fCollapseHeaderComments= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_HEADERS);
1012
fCollapseComments= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_COMMENTS);
1013
fCollapseInactiveCode= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_INACTIVE_CODE);
1014
fPreprocessorBranchFoldingEnabled= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_PREPROCESSOR_BRANCHES_ENABLED);
1015
fStatementsFoldingEnabled= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_STATEMENTS);
1016
fCommentFoldingEnabled = true;
1019
private void update(FoldingStructureComputationContext ctx) {
1020
if (ctx == null || !isConsistent(fInput))
1023
if (!fInitialReconcilePending && fSelectionListener != null) {
1024
fEditor.getSelectionProvider().removeSelectionChangedListener(fSelectionListener);
1025
fSelectionListener= null;
1028
Map<CProjectionAnnotation,Position> additions= new HashMap<CProjectionAnnotation,Position>();
1029
List<CProjectionAnnotation> deletions= new ArrayList<CProjectionAnnotation>();
1030
List<CProjectionAnnotation> updates= new ArrayList<CProjectionAnnotation>();
1032
computeFoldingStructure(ctx);
1033
Map<CProjectionAnnotation,Position> updated= ctx.fMap;
1034
Map<Object, List<Tuple>> previous= computeCurrentStructure(ctx);
1036
Iterator<CProjectionAnnotation> e= updated.keySet().iterator();
1037
while (e.hasNext()) {
1038
CProjectionAnnotation newAnnotation= e.next();
1039
Object key= newAnnotation.getElement();
1040
Position newPosition= updated.get(newAnnotation);
1042
List<Tuple> annotations= previous.get(key);
1043
if (annotations == null) {
1044
if (DEBUG) System.out.println("DefaultCFoldingStructureProvider.update() new annotation " + newAnnotation); //$NON-NLS-1$
1046
additions.put(newAnnotation, newPosition);
1049
Iterator<Tuple> x= annotations.iterator();
1050
boolean matched= false;
1051
while (x.hasNext()) {
1052
Tuple tuple= x.next();
1053
CProjectionAnnotation existingAnnotation= tuple.annotation;
1054
Position existingPosition= tuple.position;
1055
if (newAnnotation.getCategory() == existingAnnotation.getCategory()) {
1056
final boolean collapseChanged = ctx.allowCollapsing() && existingAnnotation.isCollapsed() != newAnnotation.isCollapsed();
1057
if (existingPosition != null && (collapseChanged || !newPosition.equals(existingPosition))) {
1058
existingPosition.setOffset(newPosition.getOffset());
1059
existingPosition.setLength(newPosition.getLength());
1060
if (collapseChanged) {
1061
if (DEBUG) System.out.println("DefaultCFoldingStructureProvider.update() change annotation " + newAnnotation); //$NON-NLS-1$
1062
if (newAnnotation.isCollapsed())
1063
existingAnnotation.markCollapsed();
1065
existingAnnotation.markExpanded();
1067
updates.add(existingAnnotation);
1075
if (DEBUG) System.out.println("DefaultCFoldingStructureProvider.update() new annotation " + newAnnotation); //$NON-NLS-1$
1077
additions.put(newAnnotation, newPosition);
1079
if (annotations.isEmpty())
1080
previous.remove(key);
1084
Iterator<List<Tuple>> e2= previous.values().iterator();
1085
while (e2.hasNext()) {
1086
List<Tuple> list= e2.next();
1087
int size= list.size();
1088
for (int i= 0; i < size; i++) {
1089
CProjectionAnnotation annotation= list.get(i).annotation;
1090
if (DEBUG) System.out.println("DefaultCFoldingStructureProvider.update() deleted annotation " + annotation); //$NON-NLS-1$
1091
deletions.add(annotation);
1095
match(deletions, additions, updates, ctx);
1097
Annotation[] removals= new Annotation[deletions.size()];
1098
deletions.toArray(removals);
1099
Annotation[] changes= new Annotation[updates.size()];
1100
updates.toArray(changes);
1101
if (DEBUG) System.out.println("DefaultCFoldingStructureProvider.update() "+removals.length+" deleted, "+additions.size()+" added, "+changes.length+" changed"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
1102
ctx.getModel().modifyAnnotations(removals, additions, changes);
1106
* Matches deleted annotations to changed or added ones. A deleted
1107
* annotation/position tuple that has a matching addition / change
1108
* is updated and marked as changed. The matching tuple is not added
1109
* (for additions) or marked as deletion instead (for changes). The
1110
* result is that more annotations are changed and fewer get
1113
private void match(List<CProjectionAnnotation> deletions, Map<CProjectionAnnotation,Position> additions,
1114
List<CProjectionAnnotation> changes, FoldingStructureComputationContext ctx) {
1115
if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty()))
1118
List<CProjectionAnnotation> newDeletions= new ArrayList<CProjectionAnnotation>();
1119
List<CProjectionAnnotation> newChanges= new ArrayList<CProjectionAnnotation>();
1121
Iterator<CProjectionAnnotation> deletionIterator= deletions.iterator();
1122
while (deletionIterator.hasNext()) {
1123
CProjectionAnnotation deleted= deletionIterator.next();
1124
Position deletedPosition= ctx.getModel().getPosition(deleted);
1125
if (deletedPosition == null || deletedPosition.length < 5)
1128
Tuple deletedTuple= new Tuple(deleted, deletedPosition);
1130
Tuple match= findMatch(deletedTuple, changes, null, ctx);
1131
boolean addToDeletions= true;
1132
if (match == null) {
1133
match= findMatch(deletedTuple, additions.keySet(), additions, ctx);
1134
addToDeletions= false;
1137
if (match != null) {
1138
Object element= match.annotation.getElement();
1139
deleted.setElement(element);
1140
deletedPosition.setLength(match.position.getLength());
1141
if (deletedPosition instanceof CElementPosition && element instanceof ICElement) {
1142
CElementPosition cep= (CElementPosition) deletedPosition;
1143
cep.setElement((ICElement) element);
1146
deletionIterator.remove();
1147
if (DEBUG) System.out.println("DefaultCFoldingStructureProvider.update() changed annotation " + deleted); //$NON-NLS-1$
1148
newChanges.add(deleted);
1150
if (addToDeletions) {
1151
if (DEBUG) System.out.println("DefaultCFoldingStructureProvider.update() deleted annotation " + match.annotation); //$NON-NLS-1$
1152
newDeletions.add(match.annotation);
1157
deletions.addAll(newDeletions);
1158
changes.addAll(newChanges);
1162
* Finds a match for <code>tuple</code> in a collection of
1163
* annotations. The positions for the
1164
* <code>CProjectionAnnotation</code> instances in
1165
* <code>annotations</code> can be found in the passed
1166
* <code>positionMap</code> or in the model if
1167
* <code>positionMap</code> is <code>null</code>.
1169
* A tuple is said to match another if their annotations have the
1170
* same category and their position offsets are equal.
1173
* If a match is found, the annotation gets removed from
1174
* <code>annotations</code>.
1177
* @param tuple the tuple for which we want to find a match
1178
* @param annotations collection of
1179
* <code>CProjectionAnnotation</code>
1180
* @param positionMap a <code>Map<Annotation, Position></code>
1181
* or <code>null</code>
1182
* @return a matching tuple or <code>null</code> for no match
1184
private Tuple findMatch(Tuple tuple, Collection<CProjectionAnnotation> annotations, Map<CProjectionAnnotation,Position> positionMap, FoldingStructureComputationContext ctx) {
1185
Iterator<CProjectionAnnotation> it= annotations.iterator();
1186
while (it.hasNext()) {
1187
CProjectionAnnotation annotation= it.next();
1188
if (tuple.annotation.getCategory() == annotation.getCategory()) {
1189
Position position= positionMap == null ? ctx.getModel().getPosition(annotation) : positionMap.get(annotation);
1190
if (position == null)
1193
if (tuple.position.getOffset() == position.getOffset()) {
1195
return new Tuple(annotation, position);
1203
private Map<Object, List<Tuple>> computeCurrentStructure(FoldingStructureComputationContext ctx) {
1204
boolean includeBranches= fPreprocessorBranchFoldingEnabled && ctx.fAST != null;
1205
boolean includeStmts= fStatementsFoldingEnabled && ctx.fAST != null;
1206
boolean includeCModel= ctx.fAST != null || !(fPreprocessorBranchFoldingEnabled || fStatementsFoldingEnabled);
1207
Map<Object, List<Tuple>> map= new HashMap<Object, List<Tuple>>();
1208
ProjectionAnnotationModel model= ctx.getModel();
1209
Iterator<?> e= model.getAnnotationIterator();
1210
while (e.hasNext()) {
1211
Object annotation= e.next();
1212
if (annotation instanceof CProjectionAnnotation) {
1213
CProjectionAnnotation cAnnotation= (CProjectionAnnotation) annotation;
1214
final boolean include;
1215
switch (cAnnotation.getCategory()) {
1216
case CProjectionAnnotation.BRANCH:
1217
include= includeBranches;
1219
case CProjectionAnnotation.STATEMENT:
1220
include= includeStmts;
1222
case CProjectionAnnotation.CMODEL:
1223
include= includeCModel;
1229
Position position= model.getPosition(cAnnotation);
1230
assert position != null;
1231
if (include || position.length < 5) {
1232
List<Tuple> list= map.get(cAnnotation.getElement());
1234
list= new ArrayList<Tuple>(2);
1235
map.put(cAnnotation.getElement(), list);
1237
list.add(new Tuple(cAnnotation, position));
1242
Comparator<Tuple> comparator= new Comparator<Tuple>() {
1243
public int compare(Tuple t1, Tuple t2) {
1244
return t1.position.getOffset() - t2.position.getOffset();
1247
for(List<Tuple> list : map.values()) {
1248
Collections.sort(list, comparator);
1254
private void computeFoldingStructure(final FoldingStructureComputationContext ctx) {
1255
if (fCommentFoldingEnabled) {
1256
// compute comment positions from partitioning
1258
IDocument doc= ctx.getDocument();
1259
ITypedRegion[] partitions = TextUtilities.computePartitioning(doc, ICPartitions.C_PARTITIONING, 0, doc.getLength(), false);
1260
computeFoldingStructure(partitions, ctx);
1261
} catch (BadLocationException e) {
1265
final boolean needAST= fPreprocessorBranchFoldingEnabled || fStatementsFoldingEnabled;
1267
IASTTranslationUnit ast= ctx.getAST();
1269
computeFoldingStructure(ast, ctx);
1270
} else if (fInitialReconcilePending) {
1271
final WAIT_FLAG waitFlag= ASTProvider.WAIT_ACTIVE_ONLY;
1272
final ASTProvider astProvider= CUIPlugin.getDefault().getASTProvider();
1273
IStatus status= astProvider.runOnAST(getInputElement(), waitFlag, null, new ASTCache.ASTRunnable() {
1274
public IStatus runOnAST(ILanguage lang, IASTTranslationUnit ast) {
1277
computeFoldingStructure(ast, ctx);
1279
return Status.OK_STATUS;
1282
if (status.matches(IStatus.ERROR)) {
1283
CUIPlugin.log(status);
1287
if (!needAST || ctx.getAST() != null) {
1288
fInitialReconcilePending= false;
1289
IParent parent= (IParent) fInput;
1291
computeFoldingStructure(parent.getChildren(), ctx);
1292
} catch (CModelException x) {
1297
static boolean isConsistent(ICElement element) {
1298
if (element instanceof ITranslationUnit) {
1300
return ((ITranslationUnit)element).isConsistent();
1301
} catch (CModelException exc) {
1308
* A modifiable region with extra information about the region it holds.
1309
* It tells us whether or not to include the last line of the region
1311
private static class StatementRegion extends ModifiableRegion {
1312
public final String function;
1314
public boolean inclusive;
1315
public StatementRegion(String function, int level) {
1316
this.function= function;
1322
* Computes folding structure of statements for the given AST.
1327
private void computeStatementFoldingStructure(IASTTranslationUnit ast, FoldingStructureComputationContext ctx) {
1328
final Stack<StatementRegion> iral= new Stack<StatementRegion>();
1329
ast.accept(new StatementVisitor(iral));
1330
while (!iral.empty()) {
1331
StatementRegion mr = iral.pop();
1332
IRegion aligned = alignRegion(mr, ctx,mr.inclusive);
1333
if (aligned != null) {
1334
Position alignedPos= new Position(aligned.getOffset(), aligned.getLength());
1335
ctx.addProjectionRange(new CProjectionAnnotation(
1336
false, mr.function + mr.level + computeKey(mr, ctx), CProjectionAnnotation.STATEMENT), alignedPos);
1342
* Compute folding structure of things related to the AST tree. Currently it
1343
* computes the folding structure for: preprocessor branches for the given
1344
* AST. Also, it computes statements folding (if/else do/while for and
1350
private void computeFoldingStructure(IASTTranslationUnit ast, FoldingStructureComputationContext ctx) {
1354
String fileName = ast.getFilePath();
1355
if (fileName == null) {
1359
if (fStatementsFoldingEnabled)
1360
computeStatementFoldingStructure(ast, ctx);
1362
if (fPreprocessorBranchFoldingEnabled)
1363
computePreprocessorFoldingStructure(ast, ctx);
1367
* Computes folding structure for preprocessor branches for the given AST.
1372
private void computePreprocessorFoldingStructure(IASTTranslationUnit ast, FoldingStructureComputationContext ctx) {
1373
List<Branch> branches = new ArrayList<Branch>();
1374
Stack<Branch> branchStack = new Stack<Branch>();
1376
IASTPreprocessorStatement[] preprocStmts = ast.getAllPreprocessorStatements();
1378
for (IASTPreprocessorStatement statement : preprocStmts) {
1379
if (!statement.isPartOfTranslationUnitFile()) {
1380
// preprocessor directive is from a different file
1383
IASTNodeLocation stmtLocation= statement.getFileLocation();
1384
if (stmtLocation == null) {
1387
if (statement instanceof IASTPreprocessorIfStatement) {
1388
IASTPreprocessorIfStatement ifStmt = (IASTPreprocessorIfStatement)statement;
1389
branchStack.push(new Branch(stmtLocation.getNodeOffset(), ifStmt.taken(), "#if " + new String(ifStmt.getCondition()))); //$NON-NLS-1$
1390
} else if (statement instanceof IASTPreprocessorIfdefStatement) {
1391
IASTPreprocessorIfdefStatement ifdefStmt = (IASTPreprocessorIfdefStatement)statement;
1392
branchStack.push(new Branch(stmtLocation.getNodeOffset(), ifdefStmt.taken(), "#ifdef " + new String(ifdefStmt.getCondition()))); //$NON-NLS-1$
1393
} else if (statement instanceof IASTPreprocessorIfndefStatement) {
1394
IASTPreprocessorIfndefStatement ifndefStmt = (IASTPreprocessorIfndefStatement)statement;
1395
branchStack.push(new Branch(stmtLocation.getNodeOffset(), ifndefStmt.taken(), "#ifndef " + new String(ifndefStmt.getCondition()))); //$NON-NLS-1$
1396
} else if (statement instanceof IASTPreprocessorElseStatement) {
1397
if (branchStack.isEmpty()) {
1398
// #else without #if
1401
Branch branch= branchStack.pop();
1402
IASTPreprocessorElseStatement elseStmt = (IASTPreprocessorElseStatement)statement;
1403
branchStack.push(new Branch(stmtLocation.getNodeOffset(), elseStmt.taken(), branch.fCondition));
1404
branch.setEndOffset(stmtLocation.getNodeOffset());
1405
branches.add(branch);
1406
} else if (statement instanceof IASTPreprocessorElifStatement) {
1407
if (branchStack.isEmpty()) {
1408
// #elif without #if
1411
Branch branch= branchStack.pop();
1412
IASTPreprocessorElifStatement elifStmt = (IASTPreprocessorElifStatement) statement;
1413
branchStack.push(new Branch(stmtLocation.getNodeOffset(), elifStmt.taken(), branch.fCondition));
1414
branch.setEndOffset(stmtLocation.getNodeOffset());
1415
branches.add(branch);
1416
} else if (statement instanceof IASTPreprocessorEndifStatement) {
1417
if (branchStack.isEmpty()) {
1418
// #endif without #if
1421
Branch branch= branchStack.pop();
1422
branch.setEndOffset(stmtLocation.getNodeOffset() + stmtLocation.getNodeLength());
1423
branch.setInclusive(true);
1424
branches.add(branch);
1428
if (!branchStack.isEmpty()) {
1430
Branch branch= branchStack.pop();
1431
branch.setEndOffset(getDocument().getLength());
1432
branch.setInclusive(true);
1433
branches.add(branch);
1436
Map<String, Counter> keys= new HashMap<String, Counter>(branches.size());
1437
for (Branch branch : branches) {
1438
IRegion aligned = alignRegion(branch, ctx, branch.fInclusive);
1439
if (aligned != null) {
1440
Position alignedPos= new Position(aligned.getOffset(), aligned.getLength());
1441
final boolean collapse= !branch.taken() && ctx.collapseInactiveCode() && !alignedPos.includes(fCursorPosition);
1442
// compute a stable key
1443
String key = branch.fCondition;
1444
Counter counter= keys.get(key);
1445
if (counter == null) {
1446
keys.put(key, new Counter());
1448
key= Integer.toString(counter.fCount++) + key;
1450
ctx.addProjectionRange(new CProjectionAnnotation(collapse, key, CProjectionAnnotation.BRANCH), alignedPos);
1456
* Compute a key for recognizing an annotation based on the given position.
1460
* @return a key to recognize an annotation position
1462
private Object computeKey(Position pos, FoldingStructureComputationContext ctx) {
1464
final IDocument document= ctx.getDocument();
1465
IRegion line= document.getLineInformationOfOffset(pos.offset);
1466
return document.get(pos.offset, Math.min(32, line.getOffset() + line.getLength() - pos.offset));
1467
} catch (BadLocationException exc) {
1473
* Compute folding structure based on partioning information.
1475
* @param partitions array of document partitions
1476
* @param ctx the folding structure context
1477
* @throws BadLocationException
1479
private void computeFoldingStructure(ITypedRegion[] partitions, FoldingStructureComputationContext ctx) throws BadLocationException {
1480
boolean collapse = ctx.collapseComments();
1481
IDocument doc= ctx.getDocument();
1484
List<Tuple> comments= new ArrayList<Tuple>();
1485
ModifiableRegion commentRange = new ModifiableRegion();
1486
for (ITypedRegion partition : partitions) {
1487
boolean singleLine= false;
1488
if (ICPartitions.C_MULTI_LINE_COMMENT.equals(partition.getType())
1489
|| ICPartitions.C_MULTI_LINE_DOC_COMMENT.equals(partition.getType())) {
1490
Position position= createCommentPosition(alignRegion(partition, ctx, true));
1491
if (position != null) {
1492
if (startLine >= 0 && endLine - startLine >= fMinCommentLines) {
1493
Position projection = createCommentPosition(alignRegion(commentRange, ctx, true));
1494
if (projection != null) {
1495
comments.add(new Tuple(new CProjectionAnnotation(collapse, doc.get(projection.offset, Math.min(16, projection.length)), true), projection));
1499
comments.add(new Tuple(new CProjectionAnnotation(collapse, doc.get(position.offset, Math.min(16, position.length)), true), position));
1504
singleLine= ICPartitions.C_SINGLE_LINE_COMMENT.equals(partition.getType());
1507
// if comment starts at column 0 and spans only one line
1508
// and is adjacent to a previous line comment, add it
1509
// to the commentRange
1510
int lineNr = doc.getLineOfOffset(partition.getOffset());
1511
IRegion lineRegion = doc.getLineInformation(lineNr);
1512
boolean isLineStart = partition.getOffset() == lineRegion.getOffset();
1517
singleLine = lineRegion.getOffset() + lineRegion.getLength() >= partition.getOffset() + partition.getLength();
1522
if (startLine < 0 || lineNr - endLine > 1) {
1523
if (startLine >= 0 && endLine - startLine >= fMinCommentLines) {
1524
Position projection = createCommentPosition(alignRegion(commentRange, ctx, true));
1525
if (projection != null) {
1526
comments.add(new Tuple(new CProjectionAnnotation(collapse, doc.get(projection.offset, Math.min(16, projection.length)), true), projection));
1531
commentRange.offset = lineRegion.getOffset();
1532
commentRange.length = lineRegion.getLength();
1535
int delta = lineRegion.getOffset() + lineRegion.getLength() - commentRange.offset - commentRange.length;
1536
commentRange.length += delta;
1540
if (startLine >= 0 && endLine - startLine >= fMinCommentLines) {
1541
Position projection = createCommentPosition(alignRegion(commentRange, ctx, true));
1542
if (projection != null) {
1543
comments.add(new Tuple(new CProjectionAnnotation(collapse, doc.get(projection.offset, Math.min(16, projection.length)), true), projection));
1546
if (!comments.isEmpty()) {
1547
// first comment starting before line 10 is considered the header comment
1548
Iterator<Tuple> iter = comments.iterator();
1549
Tuple tuple = iter.next();
1550
int lineNr = doc.getLineOfOffset(tuple.position.getOffset());
1552
if (ctx.collapseHeaderComments()) {
1553
tuple.annotation.markCollapsed();
1555
tuple.annotation.markExpanded();
1558
ctx.addProjectionRange(tuple.annotation, tuple.position);
1559
while (iter.hasNext()) {
1560
tuple = iter.next();
1561
ctx.addProjectionRange(tuple.annotation, tuple.position);
1566
private void computeFoldingStructure(ICElement[] elements, FoldingStructureComputationContext ctx) throws CModelException {
1567
for (ICElement element : elements) {
1568
computeFoldingStructure(element, ctx);
1570
if (element instanceof IParent) {
1571
IParent parent= (IParent) element;
1572
computeFoldingStructure(parent.getChildren(), ctx);
1578
* Computes the folding structure for a given {@link ICElement C element}. Computed
1579
* projection annotations are
1580
* {@link DefaultCFoldingStructureProvider.FoldingStructureComputationContext#addProjectionRange(DefaultCFoldingStructureProvider.CProjectionAnnotation, Position) added}
1581
* to the computation context.
1583
* Subclasses may extend or replace. The default implementation creates projection annotations
1584
* for the following elements:
1586
* <li>structs, unions, classes</li>
1587
* <li>functions</li>
1589
* <li>multiline macro definitions</li>
1593
* @param element the C element to compute the folding structure for
1594
* @param ctx the computation context
1596
protected void computeFoldingStructure(ICElement element, FoldingStructureComputationContext ctx) {
1598
boolean collapse= false;
1599
switch (element.getElementType()) {
1601
case ICElement.C_STRUCT:
1602
case ICElement.C_CLASS:
1603
case ICElement.C_UNION:
1604
case ICElement.C_ENUMERATION:
1605
case ICElement.C_TEMPLATE_STRUCT:
1606
case ICElement.C_TEMPLATE_CLASS:
1607
case ICElement.C_TEMPLATE_UNION:
1608
collapse= ctx.collapseStructures();
1610
case ICElement.C_MACRO:
1611
collapse= ctx.collapseMacros();
1613
case ICElement.C_FUNCTION:
1614
case ICElement.C_TEMPLATE_FUNCTION:
1615
collapse= ctx.collapseFunctions();
1617
case ICElement.C_METHOD:
1618
case ICElement.C_TEMPLATE_METHOD:
1619
collapse= ctx.collapseMethods();
1621
case ICElement.C_NAMESPACE:
1627
IRegion[] regions= computeProjectionRanges((ISourceReference) element, ctx);
1628
if (regions.length > 0) {
1629
IRegion normalized= alignRegion(regions[regions.length - 1], ctx, true);
1630
if (normalized != null) {
1631
Position position= createElementPosition(normalized, element);
1632
if (position != null) {
1633
collapse= collapse && !position.includes(fCursorPosition);
1634
ctx.addProjectionRange(new CProjectionAnnotation(collapse, element, false), position);
1641
* Computes the projection ranges for a given <code>ISourceReference</code>. More than one
1642
* range or none at all may be returned. If there are no foldable regions, an empty array is
1645
* The last region in the returned array (if not empty) describes the region for the C
1646
* element that implements the source reference. Any preceding regions describe comments
1650
* @param reference a C element that is a source reference
1651
* @param ctx the folding context
1652
* @return the regions to be folded
1654
protected final IRegion[] computeProjectionRanges(ISourceReference reference, FoldingStructureComputationContext ctx) {
1656
ISourceRange range= reference.getSourceRange();
1657
return new IRegion[] {
1658
new Region(range.getStartPos(), range.getLength())
1660
} catch (CModelException e) {
1663
return new IRegion[0];
1667
* Creates a comment folding position from an
1668
* {@link #alignRegion(IRegion, DefaultCFoldingStructureProvider.FoldingStructureComputationContext, boolean) aligned}
1671
* @param aligned an aligned region
1672
* @return a folding position corresponding to <code>aligned</code>
1674
protected final Position createCommentPosition(IRegion aligned) {
1675
if (aligned == null) {
1678
return new CommentPosition(aligned.getOffset(), aligned.getLength());
1682
* Creates a folding position that remembers its element from an
1683
* {@link #alignRegion(IRegion, DefaultCFoldingStructureProvider.FoldingStructureComputationContext, boolean) aligned}
1686
* @param aligned an aligned region
1687
* @param element the element to remember
1688
* @return a folding position corresponding to <code>aligned</code>
1690
protected final Position createElementPosition(IRegion aligned, ICElement element) {
1691
return new CElementPosition(aligned.getOffset(), aligned.getLength(), element);
1695
* Aligns <code>region</code> to start and end at a line offset. The region's start is
1696
* decreased to the next line offset, and the end offset increased to the next line start or the
1697
* end of the document. <code>null</code> is returned if <code>region</code> is
1698
* <code>null</code> itself or does not comprise at least one line delimiter, as a single line
1701
* @param region the region to align, may be <code>null</code>
1702
* @param ctx the folding context
1703
* @return a region equal or greater than <code>region</code> that is aligned with line
1704
* offsets, <code>null</code> if the region is too small to be foldable (e.g. covers
1707
protected final IRegion alignRegion(IRegion region, FoldingStructureComputationContext ctx) {
1708
return alignRegion(region, ctx, true);
1712
* Aligns <code>region</code> to start and end at a line offset. The region's start is
1713
* decreased to the next line offset, and the end offset increased to the next line start or the
1714
* end of the document. <code>null</code> is returned if <code>region</code> is
1715
* <code>null</code> itself or does not comprise at least one line delimiter, as a single line
1718
* @param region the region to align, may be <code>null</code>
1719
* @param ctx the folding context
1720
* @param inclusive include line of end offset
1721
* @return a region equal or greater than <code>region</code> that is aligned with line
1722
* offsets, <code>null</code> if the region is too small to be foldable (e.g. covers
1725
protected final IRegion alignRegion(IRegion region, FoldingStructureComputationContext ctx, boolean inclusive) {
1729
IDocument document= ctx.getDocument();
1733
int start= document.getLineOfOffset(region.getOffset());
1734
int end= document.getLineOfOffset(region.getOffset() + region.getLength());
1738
int offset= document.getLineOffset(start);
1741
if (document.getNumberOfLines() > end + 1)
1742
endOffset= document.getLineOffset(end + 1);
1744
endOffset= document.getLineOffset(end) + document.getLineLength(end);
1746
endOffset= document.getLineOffset(end);
1748
return new Region(offset, endOffset - offset);
1750
} catch (BadLocationException x) {
1751
// concurrent modification
1756
private ProjectionAnnotationModel getModel() {
1757
return (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class);
1760
private IDocument getDocument() {
1761
IDocumentProvider provider= fEditor.getDocumentProvider();
1762
return provider.getDocument(fEditor.getEditorInput());