blob: c1d416f8706c8e1da58bd43da7cba9ad64a397f1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Anton Leherbauer (Wind River Systems)
* Markus Schorn (Wind River Systems)
* Elazar Leibovich (IDF) - Code folding of compound statements (bug 174597)
* Andrew Ferguson (Symbian)
*******************************************************************************/
package org.eclipse.cdt.internal.ui.text.folding;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTBreakStatement;
import org.eclipse.cdt.core.dom.ast.IASTCaseStatement;
import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTCompoundStatement;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTDefaultStatement;
import org.eclipse.cdt.core.dom.ast.IASTDoStatement;
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
import org.eclipse.cdt.core.dom.ast.IASTForStatement;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.IASTIfStatement;
import org.eclipse.cdt.core.dom.ast.IASTNodeLocation;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorElifStatement;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorElseStatement;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorEndifStatement;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfStatement;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfdefStatement;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfndefStatement;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement;
import org.eclipse.cdt.core.dom.ast.IASTProblem;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTStatement;
import org.eclipse.cdt.core.dom.ast.IASTSwitchStatement;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IASTWhileStatement;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTRangeBasedForStatement;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ILanguage;
import org.eclipse.cdt.core.model.IParent;
import org.eclipse.cdt.core.model.ISourceRange;
import org.eclipse.cdt.core.model.ISourceReference;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.core.parser.IProblem;
import org.eclipse.cdt.internal.core.dom.parser.ASTQueries;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;
import org.eclipse.cdt.internal.core.model.ASTCache;
import org.eclipse.cdt.internal.ui.editor.ASTProvider;
import org.eclipse.cdt.internal.ui.editor.ASTProvider.WAIT_FLAG;
import org.eclipse.cdt.internal.ui.editor.CEditor;
import org.eclipse.cdt.internal.ui.text.DocumentCharacterIterator;
import org.eclipse.cdt.internal.ui.text.ICReconcilingListener;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.cdt.ui.PreferenceConstants;
import org.eclipse.cdt.ui.text.ICPartitions;
import org.eclipse.cdt.ui.text.folding.ICFoldingStructureProvider;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.projection.IProjectionListener;
import org.eclipse.jface.text.source.projection.IProjectionPosition;
import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
/**
* Default implementation of a {@link ICFoldingStructureProvider}.
* <p>
* Derived from JDT counterpart.
* </p>
*/
public class DefaultCFoldingStructureProvider implements ICFoldingStructureProvider {
/**
* A visitor to collect compound statement positions.
*
* @since 5.0
*/
private final class StatementVisitor extends ASTVisitor {
{
shouldVisitStatements = true;
shouldVisitDeclarations = true;
}
private final Stack<StatementRegion> fStatements;
int fLevel = 0;
Stack<String> fScope = new Stack<>();
private StatementVisitor(Stack<StatementRegion> statements) {
fStatements = statements;
}
@Override
public int visit(IASTStatement statement) {
++fLevel;
// if it's not part of the displayed - file, we don't need it
if (!statement.isPartOfTranslationUnitFile())
return PROCESS_SKIP;// we neither need its descendants
try {
StatementRegion mr;
IASTFileLocation fl;
if (statement instanceof IASTIfStatement) {
IASTIfStatement ifstmt = (IASTIfStatement) statement;
fl = ifstmt.getFileLocation();
if (fl == null)
return PROCESS_CONTINUE;
int ifOffset = fl.getNodeOffset();
IASTStatement thenStmt;
mr = createRegion();
thenStmt = ifstmt.getThenClause();
if (thenStmt == null)
return PROCESS_CONTINUE;
fl = thenStmt.getFileLocation();
mr.setLength(fl.getNodeOffset() + fl.getNodeLength() - ifOffset);
mr.setOffset(ifOffset);
mr.inclusive = !(thenStmt instanceof IASTCompoundStatement);
IASTStatement elseStmt;
elseStmt = ifstmt.getElseClause();
if (elseStmt == null) {
mr.inclusive = true;
fStatements.push(mr);
return PROCESS_CONTINUE;
}
IASTFileLocation elseStmtLocation = elseStmt.getFileLocation();
mr.inclusive = mr.inclusive || fl.getEndingLineNumber() < elseStmtLocation.getStartingLineNumber();
if (elseStmt instanceof IASTIfStatement) {
fStatements.push(mr);
return PROCESS_CONTINUE;
}
fStatements.push(mr);
mr = createRegion();
mr.setLength(elseStmtLocation.getNodeLength());
mr.setOffset(elseStmtLocation.getNodeOffset());
mr.inclusive = true;
fStatements.push(mr);
return PROCESS_CONTINUE;
}
mr = createRegion();
mr.inclusive = true;
if (statement instanceof IASTDoStatement)
mr.inclusive = false;
if (statement instanceof IASTSwitchStatement) {
IASTStatement switchstmt = ((IASTSwitchStatement) statement).getBody();
if (switchstmt instanceof IASTCompoundStatement) {
IASTStatement[] stmts = ((IASTCompoundStatement) switchstmt).getStatements();
boolean pushedMR = false;
for (IASTStatement tmpstmt : stmts) {
StatementRegion tmpmr;
if (!(tmpstmt instanceof IASTCaseStatement || tmpstmt instanceof IASTDefaultStatement)) {
if (!pushedMR)
return PROCESS_SKIP;
IASTFileLocation tmpfl = tmpstmt.getFileLocation();
tmpmr = fStatements.peek();
tmpmr.setLength(tmpfl.getNodeLength() + tmpfl.getNodeOffset() - tmpmr.getOffset());
if (tmpstmt instanceof IASTBreakStatement)
pushedMR = false;
continue;
}
IASTFileLocation tmpfl;
tmpmr = createRegion();
tmpmr.level = fLevel + 1;
tmpmr.inclusive = true;
if (tmpstmt instanceof IASTCaseStatement) {
IASTCaseStatement casestmt = (IASTCaseStatement) tmpstmt;
tmpfl = casestmt.getExpression().getFileLocation();
tmpmr.setOffset(tmpfl.getNodeOffset());
tmpmr.setLength(tmpfl.getNodeLength());
} else if (tmpstmt instanceof IASTDefaultStatement) {
IASTDefaultStatement defstmt = (IASTDefaultStatement) tmpstmt;
tmpfl = defstmt.getFileLocation();
tmpmr.setOffset(tmpfl.getNodeOffset() + tmpfl.getNodeLength());
tmpmr.setLength(0);
}
fStatements.push(tmpmr);
pushedMR = true;
}
}
}
if (statement instanceof IASTForStatement || statement instanceof IASTWhileStatement
|| statement instanceof IASTDoStatement || statement instanceof IASTSwitchStatement
|| statement instanceof ICPPASTRangeBasedForStatement) {
fl = statement.getFileLocation();
mr.setLength(fl.getNodeLength());
mr.setOffset(fl.getNodeOffset());
fStatements.push(mr);
}
return PROCESS_CONTINUE;
} catch (Exception e) {
CUIPlugin.log(e);
return PROCESS_ABORT;
}
}
@Override
public int leave(IASTStatement statement) {
--fLevel;
return PROCESS_CONTINUE;
}
@Override
public int visit(IASTDeclaration declaration) {
if (!declaration.isPartOfTranslationUnitFile())
return PROCESS_SKIP;// we neither need its descendants
if (declaration instanceof IASTFunctionDefinition) {
final IASTFunctionDeclarator declarator = ((IASTFunctionDefinition) declaration).getDeclarator();
if (declarator != null) {
fScope.push(new String(ASTQueries.findInnermostDeclarator(declarator).getName().toCharArray()));
fLevel = 0;
}
} else if (declaration instanceof IASTSimpleDeclaration) {
IASTDeclSpecifier declSpecifier = ((IASTSimpleDeclaration) declaration).getDeclSpecifier();
if (declSpecifier instanceof IASTCompositeTypeSpecifier) {
fScope.push(new String(((IASTCompositeTypeSpecifier) declSpecifier).getName().toCharArray()));
}
}
return PROCESS_CONTINUE;
}
@Override
public int leave(IASTDeclaration declaration) {
if (declaration instanceof IASTFunctionDefinition) {
if (!fScope.isEmpty())
fScope.pop();
} else if (declaration instanceof IASTSimpleDeclaration) {
IASTDeclSpecifier declSpecifier = ((IASTSimpleDeclaration) declaration).getDeclSpecifier();
if (declSpecifier instanceof IASTCompositeTypeSpecifier) {
if (!fScope.isEmpty())
fScope.pop();
}
}
return PROCESS_CONTINUE;
}
private StatementRegion createRegion() {
return new StatementRegion(fScope.toString(), fLevel);
}
}
/**
* Listen to cursor position changes.
*/
private final class SelectionListener implements ISelectionChangedListener {
@Override
public void selectionChanged(SelectionChangedEvent event) {
ISelection s = event.getSelection();
if (s instanceof ITextSelection) {
ITextSelection selection = (ITextSelection) event.getSelection();
fCursorPosition = selection.getOffset();
}
}
}
/**
* Update folding positions triggered by reconciler.
*/
private class FoldingStructureReconciler implements ICReconcilingListener {
private volatile boolean fReconciling;
/*
* @see org.eclipse.cdt.internal.ui.text.ICReconcilingListener#aboutToBeReconciled()
*/
@Override
public void aboutToBeReconciled() {
}
/*
* @see org.eclipse.cdt.internal.ui.text.ICReconcilingListener#reconciled(IASTTranslationUnit, boolean, IProgressMonitor)
*/
@Override
public void reconciled(IASTTranslationUnit ast, boolean force, IProgressMonitor progressMonitor) {
if (fInput == null || progressMonitor.isCanceled()) {
return;
}
synchronized (this) {
if (fReconciling) {
return;
}
fReconciling = true;
}
try {
final boolean initialReconcile = fInitialReconcilePending;
fInitialReconcilePending = false;
FoldingStructureComputationContext ctx = createContext(initialReconcile);
if (ctx != null) {
if (initialReconcile || !hasSyntaxError(ast)) {
ctx.fAST = ast;
}
update(ctx);
}
} finally {
fReconciling = false;
}
}
/**
* Test whether the given ast contains one or more syntax errors.
*
* @param ast
* @return <code>true</code> if the ast contains a syntax error
*/
private boolean hasSyntaxError(IASTTranslationUnit ast) {
if (ast == null) {
return false;
}
IASTProblem[] problems = ast.getPreprocessorProblems();
for (IASTProblem problem : problems) {
if ((problem.getID() & (IProblem.SYNTAX_ERROR | IProblem.SCANNER_RELATED)) != 0) {
return true;
}
}
problems = CPPVisitor.getProblems(ast);
for (IASTProblem problem : problems) {
if ((problem.getID() & (IProblem.SYNTAX_ERROR | IProblem.SCANNER_RELATED)) != 0) {
return true;
}
}
return false;
}
}
/**
* A context that contains the information needed to compute the folding structure of an
* {@link ITranslationUnit}. Computed folding regions are collected via
* {@linkplain #addProjectionRange(DefaultCFoldingStructureProvider.CProjectionAnnotation, Position) addProjectionRange}.
*/
protected final class FoldingStructureComputationContext {
private final ProjectionAnnotationModel fModel;
private final IDocument fDocument;
private final boolean fAllowCollapsing;
private ISourceReference fFirstType;
private boolean fHasHeaderComment;
private LinkedHashMap<CProjectionAnnotation, Position> fMap = new LinkedHashMap<>();
private IASTTranslationUnit fAST;
FoldingStructureComputationContext(IDocument document, ProjectionAnnotationModel model,
boolean allowCollapsing) {
Assert.isNotNull(document);
Assert.isNotNull(model);
fDocument = document;
fModel = model;
fAllowCollapsing = allowCollapsing;
}
void setFirstType(ISourceReference reference) {
if (hasFirstType())
throw new IllegalStateException();
fFirstType = reference;
}
boolean hasFirstType() {
return fFirstType != null;
}
ISourceReference getFirstType() {
return fFirstType;
}
boolean hasHeaderComment() {
return fHasHeaderComment;
}
void setHasHeaderComment() {
fHasHeaderComment = true;
}
/**
* Returns <code>true</code> if newly created folding regions may be collapsed,
* <code>false</code> if not. This is usually <code>false</code> when updating the
* folding structure while typing; it may be <code>true</code> when computing or restoring
* the initial folding structure.
*
* @return <code>true</code> if newly created folding regions may be collapsed,
* <code>false</code> if not
*/
public boolean allowCollapsing() {
return fAllowCollapsing;
}
/**
* Returns the document which contains the code being folded.
*
* @return the document which contains the code being folded
*/
IDocument getDocument() {
return fDocument;
}
ProjectionAnnotationModel getModel() {
return fModel;
}
/**
* Adds a projection (folding) region to this context. The created annotation / position
* pair will be added to the {@link ProjectionAnnotationModel} of the
* {@link ProjectionViewer} of the editor.
*
* @param annotation the annotation to add
* @param position the corresponding position
*/
public void addProjectionRange(CProjectionAnnotation annotation, Position position) {
fMap.put(annotation, position);
}
/**
* Returns <code>true</code> if documentation comments should be collapsed.
*
* @return <code>true</code> if documentation comments should be collapsed
*/
public boolean collapseDocComments() {
return collapseComments() && fCollapseDocComments;
}
/**
* Returns <code>true</code> if non-documentation comments should be collapsed.
*
* @return <code>true</code> if non-documentation comments should be collapsed
*/
public boolean collapseNonDocComments() {
return collapseComments() && fCollapseNonDocComments;
}
/**
* Returns <code>true</code> if header comments should be collapsed.
*
* @return <code>true</code> if header comments should be collapsed
*/
public boolean collapseHeaderComments() {
return collapseComments() && fCollapseHeaderComments;
}
/**
* Returns <code>true</code> if comments should be collapsed.
*
* @return <code>true</code> if comments should be collapsed
*/
public boolean collapseComments() {
return fAllowCollapsing && fCollapseComments;
}
/**
* Returns <code>true</code> if functions should be collapsed.
*
* @return <code>true</code> if functions should be collapsed
*/
public boolean collapseFunctions() {
return fAllowCollapsing && fCollapseFunctions;
}
/**
* Returns <code>true</code> if macros should be collapsed.
*
* @return <code>true</code> if macros should be collapsed
*/
public boolean collapseMacros() {
return fAllowCollapsing && fCollapseMacros;
}
/**
* Returns <code>true</code> if methods should be collapsed.
*
* @return <code>true</code> if methods should be collapsed
*/
public boolean collapseMethods() {
return fAllowCollapsing && fCollapseMethods;
}
/**
* Returns <code>true</code> if structures should be collapsed.
*
* @return <code>true</code> if structures should be collapsed
*/
public boolean collapseStructures() {
return fAllowCollapsing && fCollapseStructures;
}
/**
* Returns <code>true</code> if inactive code should be collapsed.
*
* @return <code>true</code> if inactive code should be collapsed
*/
public boolean collapseInactiveCode() {
return fAllowCollapsing && fCollapseInactiveCode;
}
/**
* @return the current AST or <code>null</code>
*/
public IASTTranslationUnit getAST() {
return fAST;
}
}
public static class CProjectionAnnotation extends ProjectionAnnotation {
public final static int CMODEL = 0;
public final static int COMMENT = 1;
public final static int BRANCH = 2;
public final static int STATEMENT = 3;
private Object fKey;
private int fCategory;
public CProjectionAnnotation(boolean isCollapsed, Object key, boolean isComment) {
this(isCollapsed, key, isComment ? COMMENT : 0);
}
public CProjectionAnnotation(boolean isCollapsed, Object key, int category) {
super(isCollapsed);
fKey = key;
fCategory = category;
}
public Object getElement() {
return fKey;
}
public void setElement(Object element) {
fKey = element;
}
public int getCategory() {
return fCategory;
}
// public void setCategory(int category) {
// fCategory = category;
// }
//
// public boolean isComment() {
// return fCategory == COMMENT;
// }
//
// public void setIsComment(boolean isComment) {
// fCategory= isComment ? COMMENT : 0;
// }
/*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "CProjectionAnnotation:\n" + //$NON-NLS-1$
"\tkey: \t" + fKey + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
"\tcollapsed: \t" + isCollapsed() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
"\tcategory: \t" + getCategory() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
}
}
private static final class Tuple {
CProjectionAnnotation annotation;
Position position;
Tuple(CProjectionAnnotation annotation, Position position) {
this.annotation = annotation;
this.position = position;
}
}
private static final class Counter {
int fCount;
}
/**
* Projection position that will return two foldable regions: one folding away
* the region from after the '/*' to the beginning of the content, the other
* from after the first content line until after the comment.
*/
private static final class CommentPosition extends Position implements IProjectionPosition {
CommentPosition(int offset, int length) {
super(offset, length);
}
/*
* @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
*/
@Override
public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
DocumentCharacterIterator sequence = new DocumentCharacterIterator(document, offset, offset + length);
int prefixEnd = 0;
int contentStart = findFirstContent(sequence, prefixEnd);
int firstLine = document.getLineOfOffset(offset + prefixEnd);
int captionLine = document.getLineOfOffset(offset + contentStart);
int lastLine = document.getLineOfOffset(offset + length);
Assert.isTrue(firstLine <= captionLine, "first folded line is greater than the caption line"); //$NON-NLS-1$
Assert.isTrue(captionLine <= lastLine, "caption line is greater than the last folded line"); //$NON-NLS-1$
IRegion preRegion;
if (firstLine < captionLine) {
int preOffset = document.getLineOffset(firstLine);
IRegion preEndLineInfo = document.getLineInformation(captionLine);
int preEnd = preEndLineInfo.getOffset();
preRegion = new Region(preOffset, preEnd - preOffset);
} else {
preRegion = null;
}
if (captionLine < lastLine) {
int postOffset = document.getLineOffset(captionLine + 1);
IRegion postRegion = new Region(postOffset, offset + length - postOffset);
if (preRegion == null)
return new IRegion[] { postRegion };
return new IRegion[] { preRegion, postRegion };
}
if (preRegion != null)
return new IRegion[] { preRegion };
return null;
}
/**
* Finds the offset of the first identifier part within <code>content</code>.
* Returns 0 if none is found.
*
* @param content the content to search
* @return the first index of a unicode identifier part, or zero if none can
* be found
*/
private int findFirstContent(final CharSequence content, int prefixEnd) {
int lenght = content.length();
for (int i = prefixEnd; i < lenght; i++) {
if (Character.isUnicodeIdentifierPart(content.charAt(i)))
return i;
}
return 0;
}
/*
* @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
*/
@Override
public int computeCaptionOffset(IDocument document) {
DocumentCharacterIterator sequence = new DocumentCharacterIterator(document, offset, offset + length);
return findFirstContent(sequence, 0);
}
}
/**
* Projection position that will return two foldable regions: one folding away
* the lines before the one containing the simple name of the C element, one
* folding away any lines after the caption.
*/
private static final class CElementPosition extends Position implements IProjectionPosition {
private ICElement fElement;
public CElementPosition(int offset, int length, ICElement element) {
super(offset, length);
Assert.isNotNull(element);
fElement = element;
}
public void setElement(ICElement member) {
Assert.isNotNull(member);
fElement = member;
}
/*
* @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
*/
@Override
public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
int captionOffset = offset;
try {
/* The member's name range may not be correct. However,
* reconciling would trigger another element delta which would
* lead to reentrant situations. Therefore, we optimistically
* assume that the name range is correct, but double check the
* received lines below. */
if (fElement instanceof ISourceReference) {
ISourceRange sourceRange = ((ISourceReference) fElement).getSourceRange();
if (sourceRange != null) {
// Use end of name range for the caption offset
// in case a qualified name is split on multiple lines (bug 248613).
captionOffset = sourceRange.getIdStartPos() + sourceRange.getIdLength() - 1;
}
}
} catch (CModelException e) {
// ignore and use default
}
int firstLine = document.getLineOfOffset(offset);
int captionLine = document.getLineOfOffset(captionOffset);
int lastLine = document.getLineOfOffset(offset + length);
/* see comment above - adjust the caption line to be inside the
* entire folded region, and rely on later element deltas to correct
* the name range. */
if (captionLine < firstLine)
captionLine = firstLine;
if (captionLine > lastLine)
captionLine = lastLine;
IRegion preRegion;
if (firstLine < captionLine) {
int preOffset = document.getLineOffset(firstLine);
IRegion preEndLineInfo = document.getLineInformation(captionLine);
int preEnd = preEndLineInfo.getOffset();
preRegion = new Region(preOffset, preEnd - preOffset);
} else {
preRegion = null;
}
if (captionLine < lastLine) {
int postOffset = document.getLineOffset(captionLine + 1);
IRegion postRegion = new Region(postOffset, offset + length - postOffset);
if (preRegion == null)
return new IRegion[] { postRegion };
return new IRegion[] { preRegion, postRegion };
}
if (preRegion != null)
return new IRegion[] { preRegion };
return null;
}
/*
* @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
*/
@Override
public int computeCaptionOffset(IDocument document) throws BadLocationException {
int captionOffset = offset;
try {
// need a reconcile here?
if (fElement instanceof ISourceReference) {
ISourceRange sourceRange = ((ISourceReference) fElement).getSourceRange();
if (sourceRange != null) {
captionOffset = sourceRange.getIdStartPos() + sourceRange.getIdLength() - 1;
if (captionOffset < offset) {
captionOffset = offset;
}
}
}
} catch (CModelException e) {
// ignore and use default
}
return captionOffset - offset;
}
}
/**
* Internal projection listener.
*/
private final class ProjectionListener implements IProjectionListener {
private ProjectionViewer fViewer;
/**
* Registers the listener with the viewer.
*
* @param viewer the viewer to register a listener with
*/
public ProjectionListener(ProjectionViewer viewer) {
Assert.isLegal(viewer != null);
fViewer = viewer;
fViewer.addProjectionListener(this);
}
/**
* Disposes of this listener and removes the projection listener from the viewer.
*/
public void dispose() {
if (fViewer != null) {
fViewer.removeProjectionListener(this);
fViewer = null;
}
}
/*
* @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled()
*/
@Override
public void projectionEnabled() {
handleProjectionEnabled();
}
/*
* @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled()
*/
@Override
public void projectionDisabled() {
handleProjectionDisabled();
}
}
/**
* Implementation of <code>IRegion</code> that can be reused
* by setting the offset and the length.
*/
private static class ModifiableRegion extends Position implements IRegion {
ModifiableRegion() {
super();
}
ModifiableRegion(int offset, int length) {
super(offset, length);
}
}
/**
* Representation of a preprocessor code branch.
*/
private static class Branch extends ModifiableRegion {
private final boolean fTaken;
public final String fCondition;
public boolean fInclusive;
Branch(int offset, boolean taken, String key) {
this(offset, 0, taken, key);
}
Branch(int offset, int length, boolean taken, String key) {
super(offset, length);
fTaken = taken;
fCondition = key;
}
public void setEndOffset(int endOffset) {
setLength(endOffset - getOffset());
}
public boolean taken() {
return fTaken;
}
public void setInclusive(boolean inclusive) {
fInclusive = inclusive;
}
}
private final static boolean DEBUG = Boolean
.parseBoolean(Platform.getDebugOption("org.eclipse.cdt.ui/debug/folding")); //$NON-NLS-1$
private ITextEditor fEditor;
private ProjectionListener fProjectionListener;
protected ICElement fInput;
private boolean fCollapseHeaderComments = true;
private boolean fCollapseComments = false;
private boolean fCollapseDocComments = false;
private boolean fCollapseNonDocComments = false;
private boolean fCollapseMacros = false;
private boolean fCollapseFunctions = true;
private boolean fCollapseStructures = true;
private boolean fCollapseMethods = false;
private boolean fCollapseInactiveCode = true;
private int fMinCommentLines = 1;
private boolean fPreprocessorBranchFoldingEnabled = true;
private boolean fStatementsFoldingEnabled = false;
private boolean fCommentFoldingEnabled = true;
private ICReconcilingListener fReconilingListener;
private volatile boolean fInitialReconcilePending = true;
private int fCursorPosition;
private SelectionListener fSelectionListener;
/**
* Creates a new folding provider. It must be
* {@link #install(ITextEditor, ProjectionViewer) installed} on an editor/viewer pair before it
* can be used, and {@link #uninstall() uninstalled} when not used any longer.
* <p>
* The projection state may be reset by calling {@link #initialize()}.
* </p>
*/
public DefaultCFoldingStructureProvider() {
}
/*
* @see org.eclipse.cdt.ui.text.folding.ICFoldingStructureProvider#install(org.eclipse.ui.texteditor.ITextEditor, org.eclipse.jface.text.source.projection.ProjectionViewer)
*/
@Override
public void install(ITextEditor editor, ProjectionViewer viewer) {
Assert.isLegal(editor != null);
Assert.isLegal(viewer != null);
internalUninstall();
if (editor instanceof CEditor) {
fEditor = editor;
fProjectionListener = new ProjectionListener(viewer);
}
}
/*
* @see org.eclipse.cdt.ui.text.folding.ICFoldingStructureProvider#uninstall()
*/
@Override
public void uninstall() {
internalUninstall();
}
/**
* Internal implementation of {@link #uninstall()}.
*/
private void internalUninstall() {
if (isInstalled()) {
handleProjectionDisabled();
fProjectionListener.dispose();
fProjectionListener = null;
fEditor = null;
}
}
/**
* Returns <code>true</code> if the provider is installed, <code>false</code> otherwise.
*
* @return <code>true</code> if the provider is installed, <code>false</code> otherwise
*/
protected final boolean isInstalled() {
return fEditor != null;
}
/**
* Called whenever projection is enabled, for example when the viewer issues a
* {@link IProjectionListener#projectionEnabled() projectionEnabled} message. When the provider
* is already enabled when this method is called, it is first
* {@link #handleProjectionDisabled() disabled}.
* <p>
* Subclasses may extend.
* </p>
*/
protected void handleProjectionEnabled() {
if (DEBUG)
System.out.println("DefaultCFoldingStructureProvider.handleProjectionEnabled()"); //$NON-NLS-1$
// projectionEnabled messages are not always paired with projectionDisabled
// i.e. multiple enabled messages may be sent out.
// we have to make sure that we disable first when getting an enable
// message.
handleProjectionDisabled();
if (fEditor instanceof CEditor) {
initialize();
fReconilingListener = new FoldingStructureReconciler();
((CEditor) fEditor).addReconcileListener(fReconilingListener);
fSelectionListener = new SelectionListener();
fEditor.getSelectionProvider().addSelectionChangedListener(fSelectionListener);
}
}
/**
* Called whenever projection is disabled, for example when the provider is
* {@link #uninstall() uninstalled}, when the viewer issues a
* {@link IProjectionListener#projectionDisabled() projectionDisabled} message and before
* {@link #handleProjectionEnabled() enabling} the provider. Implementations must be prepared to
* handle multiple calls to this method even if the provider is already disabled.
* <p>
* Subclasses may extend.
* </p>
*/
protected void handleProjectionDisabled() {
if (fReconilingListener != null) {
((CEditor) fEditor).removeReconcileListener(fReconilingListener);
fReconilingListener = null;
}
if (fSelectionListener != null) {
fEditor.getSelectionProvider().removeSelectionChangedListener(fSelectionListener);
fSelectionListener = null;
}
}
/*
* @see org.eclipse.cdt.ui.text.folding.ICFoldingStructureProvider#initialize()
*/
@Override
public final void initialize() {
if (DEBUG)
System.out.println("DefaultCFoldingStructureProvider.initialize()"); //$NON-NLS-1$
fInitialReconcilePending = true;
fCursorPosition = -1;
update(createInitialContext());
}
private FoldingStructureComputationContext createInitialContext() {
initializePreferences();
fInput = getInputElement();
if (fInput == null)
return null;
return createContext(true);
}
private FoldingStructureComputationContext createContext(boolean allowCollapse) {
if (!isInstalled())
return null;
ProjectionAnnotationModel model = getModel();
if (model == null)
return null;
IDocument doc = getDocument();
if (doc == null)
return null;
return new FoldingStructureComputationContext(doc, model, allowCollapse);
}
private ICElement getInputElement() {
if (fEditor instanceof CEditor) {
return ((CEditor) fEditor).getInputCElement();
}
return null;
}
private void initializePreferences() {
IPreferenceStore store = CUIPlugin.getDefault().getPreferenceStore();
fCollapseFunctions = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_FUNCTIONS);
fCollapseStructures = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_STRUCTURES);
fCollapseMacros = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_MACROS);
fCollapseMethods = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS);
fCollapseComments = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_COMMENTS);
fCollapseDocComments = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_DOC_COMMENTS);
fCollapseNonDocComments = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_NON_DOC_COMMENTS);
fCollapseHeaderComments = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_HEADERS);
fCollapseInactiveCode = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_INACTIVE_CODE);
fPreprocessorBranchFoldingEnabled = store
.getBoolean(PreferenceConstants.EDITOR_FOLDING_PREPROCESSOR_BRANCHES_ENABLED);
fStatementsFoldingEnabled = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_STATEMENTS);
fCommentFoldingEnabled = true;
}
private void update(FoldingStructureComputationContext ctx) {
if (ctx == null || !isConsistent(fInput))
return;
if (!fInitialReconcilePending && fSelectionListener != null) {
fEditor.getSelectionProvider().removeSelectionChangedListener(fSelectionListener);
fSelectionListener = null;
}
Map<CProjectionAnnotation, Position> additions = new HashMap<>();
List<CProjectionAnnotation> deletions = new ArrayList<>();
List<CProjectionAnnotation> updates = new ArrayList<>();
computeFoldingStructure(ctx);
Map<CProjectionAnnotation, Position> updated = ctx.fMap;
Map<Object, List<Tuple>> previous = computeCurrentStructure(ctx);
Iterator<CProjectionAnnotation> e = updated.keySet().iterator();
while (e.hasNext()) {
CProjectionAnnotation newAnnotation = e.next();
Object key = newAnnotation.getElement();
Position newPosition = updated.get(newAnnotation);
List<Tuple> annotations = previous.get(key);
if (annotations == null) {
if (DEBUG)
System.out.println("DefaultCFoldingStructureProvider.update() new annotation " + newAnnotation); //$NON-NLS-1$
additions.put(newAnnotation, newPosition);
} else {
Iterator<Tuple> x = annotations.iterator();
boolean matched = false;
while (x.hasNext()) {
Tuple tuple = x.next();
CProjectionAnnotation existingAnnotation = tuple.annotation;
Position existingPosition = tuple.position;
if (newAnnotation.getCategory() == existingAnnotation.getCategory()) {
final boolean collapseChanged = ctx.allowCollapsing()
&& existingAnnotation.isCollapsed() != newAnnotation.isCollapsed();
if (existingPosition != null && (collapseChanged || !newPosition.equals(existingPosition))) {
existingPosition.setOffset(newPosition.getOffset());
existingPosition.setLength(newPosition.getLength());
if (collapseChanged) {
if (DEBUG)
System.out.println(
"DefaultCFoldingStructureProvider.update() change annotation due to collapse state change " //$NON-NLS-1$
+ newAnnotation);
if (newAnnotation.isCollapsed())
existingAnnotation.markCollapsed();
else
existingAnnotation.markExpanded();
} else {
if (DEBUG)
System.out.println(
"DefaultCFoldingStructureProvider.update() change annotation due to position change " //$NON-NLS-1$
+ newAnnotation);
}
updates.add(existingAnnotation);
}
matched = true;
x.remove();
break;
}
}
if (!matched) {
if (DEBUG)
System.out.println("DefaultCFoldingStructureProvider.update() new annotation " + newAnnotation); //$NON-NLS-1$
additions.put(newAnnotation, newPosition);
}
if (annotations.isEmpty())
previous.remove(key);
}
}
Iterator<List<Tuple>> e2 = previous.values().iterator();
while (e2.hasNext()) {
List<Tuple> list = e2.next();
int size = list.size();
for (int i = 0; i < size; i++) {
CProjectionAnnotation annotation = list.get(i).annotation;
if (DEBUG)
System.out.println("DefaultCFoldingStructureProvider.update() deleted annotation " + annotation); //$NON-NLS-1$
deletions.add(annotation);
}
}
match(deletions, additions, updates, ctx);
Annotation[] removals = new Annotation[deletions.size()];
deletions.toArray(removals);
Annotation[] changes = new Annotation[updates.size()];
updates.toArray(changes);
if (DEBUG)
System.out.println("DefaultCFoldingStructureProvider.update() " + removals.length + " deleted, " //$NON-NLS-1$//$NON-NLS-2$
+ additions.size() + " added, " + changes.length + " changed"); //$NON-NLS-1$ //$NON-NLS-2$
ctx.getModel().modifyAnnotations(removals, additions, changes);
}
/**
* Matches deleted annotations to changed or added ones. A deleted
* annotation/position tuple that has a matching addition / change
* is updated and marked as changed. The matching tuple is not added
* (for additions) or marked as deletion instead (for changes). The
* result is that more annotations are changed and fewer get
* deleted/re-added.
*/
private void match(List<CProjectionAnnotation> deletions, Map<CProjectionAnnotation, Position> additions,
List<CProjectionAnnotation> changes, FoldingStructureComputationContext ctx) {
if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty()))
return;
List<CProjectionAnnotation> newDeletions = new ArrayList<>();
List<CProjectionAnnotation> newChanges = new ArrayList<>();
Iterator<CProjectionAnnotation> deletionIterator = deletions.iterator();
while (deletionIterator.hasNext()) {
CProjectionAnnotation deleted = deletionIterator.next();
Position deletedPosition = ctx.getModel().getPosition(deleted);
if (deletedPosition == null || deletedPosition.length < 5)
continue;
Tuple deletedTuple = new Tuple(deleted, deletedPosition);
Tuple match = findMatch(deletedTuple, changes, null, ctx);
boolean addToDeletions = true;
if (match == null) {
match = findMatch(deletedTuple, additions.keySet(), additions, ctx);
addToDeletions = false;
}
if (match != null) {
Object element = match.annotation.getElement();
deleted.setElement(element);
deletedPosition.setLength(match.position.getLength());
if (deletedPosition instanceof CElementPosition && element instanceof ICElement) {
CElementPosition cep = (CElementPosition) deletedPosition;
cep.setElement((ICElement) element);
}
deletionIterator.remove();
if (DEBUG)
System.out.println("DefaultCFoldingStructureProvider.update() changed annotation " + deleted); //$NON-NLS-1$
newChanges.add(deleted);
if (addToDeletions) {
if (DEBUG)
System.out.println(
"DefaultCFoldingStructureProvider.update() deleted annotation " + match.annotation); //$NON-NLS-1$
newDeletions.add(match.annotation);
}
}
}
deletions.addAll(newDeletions);
changes.addAll(newChanges);
}
/**
* Finds a match for <code>tuple</code> in a collection of
* annotations. The positions for the
* <code>CProjectionAnnotation</code> instances in
* <code>annotations</code> can be found in the passed
* <code>positionMap</code> or in the model if
* <code>positionMap</code> is <code>null</code>.
* <p>
* A tuple is said to match another if their annotations have the
* same category and their position offsets are equal.
* </p>
* <p>
* If a match is found, the annotation gets removed from
* <code>annotations</code>.
* </p>
*
* @param tuple the tuple for which we want to find a match
* @param annotations collection of
* <code>CProjectionAnnotation</code>
* @param positionMap a <code>Map&lt;Annotation, Position&gt;</code>
* or <code>null</code>
* @return a matching tuple or <code>null</code> for no match
*/
private Tuple findMatch(Tuple tuple, Collection<CProjectionAnnotation> annotations,
Map<CProjectionAnnotation, Position> positionMap, FoldingStructureComputationContext ctx) {
Iterator<CProjectionAnnotation> it = annotations.iterator();
while (it.hasNext()) {
CProjectionAnnotation annotation = it.next();
if (tuple.annotation.getCategory() == annotation.getCategory()) {
Position position = positionMap == null ? ctx.getModel().getPosition(annotation)
: positionMap.get(annotation);
if (position == null)
continue;
if (tuple.position.getOffset() == position.getOffset()) {
it.remove();
return new Tuple(annotation, position);
}
}
}
return null;
}
private Map<Object, List<Tuple>> computeCurrentStructure(FoldingStructureComputationContext ctx) {
boolean includeBranches = fPreprocessorBranchFoldingEnabled && ctx.fAST != null;
boolean includeStmts = fStatementsFoldingEnabled && ctx.fAST != null;
boolean includeCModel = ctx.fAST != null || !(fPreprocessorBranchFoldingEnabled || fStatementsFoldingEnabled);
Map<Object, List<Tuple>> map = new HashMap<>();
ProjectionAnnotationModel model = ctx.getModel();
Iterator<?> e = model.getAnnotationIterator();
while (e.hasNext()) {
Object annotation = e.next();
if (annotation instanceof CProjectionAnnotation) {
CProjectionAnnotation cAnnotation = (CProjectionAnnotation) annotation;
final boolean include;
switch (cAnnotation.getCategory()) {
case CProjectionAnnotation.BRANCH:
include = includeBranches;
break;
case CProjectionAnnotation.STATEMENT:
include = includeStmts;
break;
case CProjectionAnnotation.CMODEL:
include = includeCModel;
break;
default:
include = true;
break;
}
Position position = model.getPosition(cAnnotation);
assert position != null;
if (include || position.length < 5) {
List<Tuple> list = map.get(cAnnotation.getElement());
if (list == null) {
list = new ArrayList<>(2);
map.put(cAnnotation.getElement(), list);
}
list.add(new Tuple(cAnnotation, position));
}
}
}
Comparator<Tuple> comparator = new Comparator<>() {
@Override
public int compare(Tuple t1, Tuple t2) {
return t1.position.getOffset() - t2.position.getOffset();
}
};
for (List<Tuple> list : map.values()) {
Collections.sort(list, comparator);
}
return map;
}
private void computeFoldingStructure(final FoldingStructureComputationContext ctx) {
if (fCommentFoldingEnabled) {
// compute comment positions from partitioning
try {
IDocument doc = ctx.getDocument();
ITypedRegion[] partitions = TextUtilities.computePartitioning(doc, ICPartitions.C_PARTITIONING, 0,
doc.getLength(), false);
computeFoldingStructure(partitions, ctx);
} catch (BadLocationException e) {
// ignore
}
}
final boolean needAST = fPreprocessorBranchFoldingEnabled || fStatementsFoldingEnabled;
if (needAST) {
IASTTranslationUnit ast = ctx.getAST();
if (ast != null) {
computeFoldingStructure(ast, ctx);
} else if (fInitialReconcilePending) {
final WAIT_FLAG waitFlag = ASTProvider.WAIT_ACTIVE_ONLY;
final ASTProvider astProvider = CUIPlugin.getDefault().getASTProvider();
IStatus status = astProvider.runOnAST(getInputElement(), waitFlag, null, new ASTCache.ASTRunnable() {
@Override
public IStatus runOnAST(ILanguage lang, IASTTranslationUnit ast) {
if (ast != null) {
ctx.fAST = ast;
computeFoldingStructure(ast, ctx);
}
return Status.OK_STATUS;
}
});
if (status.matches(IStatus.ERROR)) {
CUIPlugin.log(status);
}
}
}
if (!needAST || ctx.getAST() != null) {
fInitialReconcilePending = false;
IParent parent = (IParent) fInput;
try {
computeFoldingStructure(parent.getChildren(), ctx);
} catch (CModelException x) {
}
}
}
static boolean isConsistent(ICElement element) {
if (element instanceof ITranslationUnit) {
try {
return ((ITranslationUnit) element).isConsistent();
} catch (CModelException exc) {
}
}
return false;
}
/**
* A modifiable region with extra information about the region it holds.
* It tells us whether or not to include the last line of the region
*/
private static class StatementRegion extends ModifiableRegion {
public final String function;
public int level;
public boolean inclusive;
public StatementRegion(String function, int level) {
this.function = function;
this.level = level;
}
}
/**
* Computes folding structure of statements for the given AST.
*
* @param ast
* @param ctx
*/
private void computeStatementFoldingStructure(IASTTranslationUnit ast, FoldingStructureComputationContext ctx) {
final Stack<StatementRegion> iral = new Stack<>();
ast.accept(new StatementVisitor(iral));
while (!iral.empty()) {
StatementRegion mr = iral.pop();
IRegion aligned = alignRegion(mr, ctx, mr.inclusive);
if (aligned != null) {
Position alignedPos = new Position(aligned.getOffset(), aligned.getLength());
ctx.addProjectionRange(new CProjectionAnnotation(false, mr.function + mr.level + computeKey(mr, ctx),
CProjectionAnnotation.STATEMENT), alignedPos);
}
}
}
/**
* Compute folding structure of things related to the AST tree. Currently it
* computes the folding structure for: preprocessor branches for the given
* AST. Also, it computes statements folding (if/else do/while for and
* switch)
*
* @param ast
* @param ctx
*/
private void computeFoldingStructure(IASTTranslationUnit ast, FoldingStructureComputationContext ctx) {
if (ast == null) {
return;
}
String fileName = ast.getFilePath();
if (fileName == null) {
return;
}
if (fStatementsFoldingEnabled)
computeStatementFoldingStructure(ast, ctx);
if (fPreprocessorBranchFoldingEnabled)
computePreprocessorFoldingStructure(ast, ctx);
}
/**
* Computes folding structure for preprocessor branches for the given AST.
*
* @param ast
* @param ctx
*/
private void computePreprocessorFoldingStructure(IASTTranslationUnit ast, FoldingStructureComputationContext ctx) {
List<Branch> branches = new ArrayList<>();
Stack<Branch> branchStack = new Stack<>();
IASTPreprocessorStatement[] preprocStmts = ast.getAllPreprocessorStatements();
for (IASTPreprocessorStatement statement : preprocStmts) {
if (!statement.isPartOfTranslationUnitFile()) {
// preprocessor directive is from a different file
continue;
}
IASTNodeLocation stmtLocation = statement.getFileLocation();
if (stmtLocation == null) {
continue;
}
if (statement instanceof IASTPreprocessorIfStatement) {
IASTPreprocessorIfStatement ifStmt = (IASTPreprocessorIfStatement) statement;
branchStack.push(new Branch(stmtLocation.getNodeOffset(), ifStmt.taken(),
"#if " + new String(ifStmt.getCondition()))); //$NON-NLS-1$
} else if (statement instanceof IASTPreprocessorIfdefStatement) {
IASTPreprocessorIfdefStatement ifdefStmt = (IASTPreprocessorIfdefStatement) statement;
branchStack.push(new Branch(stmtLocation.getNodeOffset(), ifdefStmt.taken(),
"#ifdef " + new String(ifdefStmt.getCondition()))); //$NON-NLS-1$
} else if (statement instanceof IASTPreprocessorIfndefStatement) {
IASTPreprocessorIfndefStatement ifndefStmt = (IASTPreprocessorIfndefStatement) statement;
branchStack.push(new Branch(stmtLocation.getNodeOffset(), ifndefStmt.taken(),
"#ifndef " + new String(ifndefStmt.getCondition()))); //$NON-NLS-1$
} else if (statement instanceof IASTPreprocessorElseStatement) {
if (branchStack.isEmpty()) {
// #else without #if
continue;
}
Branch branch = branchStack.pop();
IASTPreprocessorElseStatement elseStmt = (IASTPreprocessorElseStatement) statement;
branchStack.push(new Branch(stmtLocation.getNodeOffset(), elseStmt.taken(), branch.fCondition));
branch.setEndOffset(stmtLocation.getNodeOffset());
branches.add(branch);
} else if (statement instanceof IASTPreprocessorElifStatement) {
if (branchStack.isEmpty()) {
// #elif without #if
continue;
}
Branch branch = branchStack.pop();
IASTPreprocessorElifStatement elifStmt = (IASTPreprocessorElifStatement) statement;
branchStack.push(new Branch(stmtLocation.getNodeOffset(), elifStmt.taken(), branch.fCondition));
branch.setEndOffset(stmtLocation.getNodeOffset());
branches.add(branch);
} else if (statement instanceof IASTPreprocessorEndifStatement) {
if (branchStack.isEmpty()) {
// #endif without #if
continue;
}
Branch branch = branchStack.pop();
branch.setEndOffset(stmtLocation.getNodeOffset() + stmtLocation.getNodeLength());
branch.setInclusive(true);
branches.add(branch);
}
}
if (!branchStack.isEmpty()) {
// unterminated #if
Branch branch = branchStack.pop();
branch.setEndOffset(getDocument().getLength());
branch.setInclusive(true);
branches.add(branch);
}
Map<String, Counter> keys = new HashMap<>(branches.size());
for (Branch branch : branches) {
IRegion aligned = alignRegion(branch, ctx, branch.fInclusive);
if (aligned != null) {
Position alignedPos = new Position(aligned.getOffset(), aligned.getLength());
final boolean collapse = !branch.taken() && ctx.collapseInactiveCode()
&& !alignedPos.includes(fCursorPosition);
// compute a stable key
String key = branch.fCondition;
Counter counter = keys.get(key);
if (counter == null) {
keys.put(key, new Counter());
} else {
key = Integer.toString(counter.fCount++) + key;
}
ctx.addProjectionRange(new CProjectionAnnotation(collapse, key, CProjectionAnnotation.BRANCH),
alignedPos);
}
}
}
/**
* Compute a key for recognizing an annotation based on the given position.
*
* @param pos
* @param ctx
* @return a key to recognize an annotation position
*/
private Object computeKey(Position pos, FoldingStructureComputationContext ctx) {
try {
final IDocument document = ctx.getDocument();
return document.getLineOfOffset(pos.offset);
} catch (BadLocationException exc) {
return exc;
}
}
/**
* Compute folding structure based on partioning information.
*
* @param partitions array of document partitions
* @param ctx the folding structure context
* @throws BadLocationException
*/
private void computeFoldingStructure(ITypedRegion[] partitions, FoldingStructureComputationContext ctx)
throws BadLocationException {
boolean collapseDoc = ctx.collapseDocComments();
boolean collapseNonDoc = ctx.collapseNonDocComments();
IDocument doc = ctx.getDocument();
int startLine = -1;
int endLine = -1;
boolean startLineIsDocComment = false;
List<Tuple> comments = new ArrayList<>();
ModifiableRegion commentRange = new ModifiableRegion();
for (ITypedRegion partition : partitions) {
boolean singleLine = false;
boolean collapse = collapseNonDoc;
if (ICPartitions.C_MULTI_LINE_DOC_COMMENT.equals(partition.getType())
|| ICPartitions.C_SINGLE_LINE_DOC_COMMENT.equals(partition.getType())) {
collapse = collapseDoc;
}
if (ICPartitions.C_MULTI_LINE_COMMENT.equals(partition.getType())
|| ICPartitions.C_MULTI_LINE_DOC_COMMENT.equals(partition.getType())) {
Position position = createCommentPosition(alignRegion(partition, ctx, true));
if (position != null) {
if (startLine >= 0 && endLine - startLine >= fMinCommentLines) {
Position projection = createCommentPosition(alignRegion(commentRange, ctx, true));
if (projection != null) {
boolean collapseLast = startLineIsDocComment ? collapseDoc : collapseNonDoc;
comments.add(new Tuple(
new CProjectionAnnotation(collapseLast,
doc.get(projection.offset, Math.min(16, projection.length)), true),
projection));
}
startLine = -1;
}
comments.add(new Tuple(new CProjectionAnnotation(collapse,
doc.get(position.offset, Math.min(16, position.length)), true), position));
} else {
singleLine = true;
}
} else {
singleLine = ICPartitions.C_SINGLE_LINE_COMMENT.equals(partition.getType())
|| ICPartitions.C_SINGLE_LINE_DOC_COMMENT.equals(partition.getType());
}
if (singleLine) {
// if comment starts at column 0 and spans only one line
// and is adjacent to a previous line comment, add it
// to the commentRange
int lineNr = doc.getLineOfOffset(partition.getOffset());
IRegion lineRegion = doc.getLineInformation(lineNr);
boolean isLineStart = partition.getOffset() == lineRegion.getOffset();
if (!isLineStart) {
continue;
}
if (startLine < 0 || lineNr - endLine > 1) {
if (startLine >= 0 && endLine - startLine >= fMinCommentLines) {
Position projection = createCommentPosition(alignRegion(commentRange, ctx, true));
if (projection != null) {
boolean collapseLast = startLineIsDocComment ? collapseDoc : collapseNonDoc;
comments.add(new Tuple(
new CProjectionAnnotation(collapseLast,
doc.get(projection.offset, Math.min(16, projection.length)), true),
projection));
}
}
startLine = lineNr;
endLine = lineNr;
startLineIsDocComment = ICPartitions.C_MULTI_LINE_DOC_COMMENT.equals(partition.getType())
|| ICPartitions.C_SINGLE_LINE_DOC_COMMENT.equals(partition.getType());
commentRange.offset = lineRegion.getOffset();
commentRange.length = lineRegion.getLength();
} else {
endLine = lineNr;
int delta = lineRegion.getOffset() + lineRegion.getLength() - commentRange.offset
- commentRange.length;
commentRange.length += delta;
}
}
}
if (startLine >= 0 && endLine - startLine >= fMinCommentLines) {
Position projection = createCommentPosition(alignRegion(commentRange, ctx, true));
if (projection != null) {
boolean collapseLast = startLineIsDocComment ? collapseDoc : collapseNonDoc;
comments.add(new Tuple(new CProjectionAnnotation(collapseLast,
doc.get(projection.offset, Math.min(16, projection.length)), true), projection));
}
}
if (!comments.isEmpty()) {
// first comment starting before line 10 is considered the header comment
Iterator<Tuple> iter = comments.iterator();
Tuple tuple = iter.next();
int lineNr = doc.getLineOfOffset(tuple.position.getOffset());
if (lineNr < 10) {
if (ctx.collapseHeaderComments()) {
tuple.annotation.markCollapsed();
} else {
tuple.annotation.markExpanded();
}
}
ctx.addProjectionRange(tuple.annotation, tuple.position);
while (iter.hasNext()) {
tuple = iter.next();
ctx.addProjectionRange(tuple.annotation, tuple.position);
}
}
}
private void computeFoldingStructure(ICElement[] elements, FoldingStructureComputationContext ctx)
throws CModelException {
for (ICElement element : elements) {
computeFoldingStructure(element, ctx);
if (element instanceof IParent) {
IParent parent = (IParent) element;
computeFoldingStructure(parent.getChildren(), ctx);
}
}
}
/**
* Computes the folding structure for a given {@link ICElement C element}. Computed
* projection annotations are
* {@link DefaultCFoldingStructureProvider.FoldingStructureComputationContext#addProjectionRange(DefaultCFoldingStructureProvider.CProjectionAnnotation, Position) added}
* to the computation context.
* <p>
* Subclasses may extend or replace. The default implementation creates projection annotations
* for the following elements:
* <ul>
* <li>structs, unions, classes</li>
* <li>functions</li>
* <li>methods</li>
* <li>multiline macro definitions</li>
* </ul>
* </p>
*
* @param element the C element to compute the folding structure for
* @param ctx the computation context
*/
protected void computeFoldingStructure(ICElement element, FoldingStructureComputationContext ctx) {
boolean collapse = false;
switch (element.getElementType()) {
case ICElement.C_STRUCT:
case ICElement.C_CLASS:
case ICElement.C_UNION:
case ICElement.C_ENUMERATION:
case ICElement.C_TEMPLATE_STRUCT:
case ICElement.C_TEMPLATE_CLASS:
case ICElement.C_TEMPLATE_UNION:
collapse = ctx.collapseStructures();
break;
case ICElement.C_MACRO:
collapse = ctx.collapseMacros();
break;
case ICElement.C_FUNCTION:
case ICElement.C_TEMPLATE_FUNCTION:
collapse = ctx.collapseFunctions();
break;
case ICElement.C_METHOD:
case ICElement.C_TEMPLATE_METHOD:
collapse = ctx.collapseMethods();
break;
case ICElement.C_NAMESPACE:
break;
default:
return;
}
IRegion[] regions = computeProjectionRanges((ISourceReference) element, ctx);
if (regions.length > 0) {
IRegion normalized = alignRegion(regions[regions.length - 1], ctx, true);
if (normalized != null) {
Position position = createElementPosition(normalized, element);
if (position != null) {
collapse = collapse && !position.includes(fCursorPosition);
ctx.addProjectionRange(new CProjectionAnnotation(collapse, element, false), position);
}
}
}
}
/**
* Computes the projection ranges for a given <code>ISourceReference</code>. More than one
* range or none at all may be returned. If there are no foldable regions, an empty array is
* returned.
* <p>
* The last region in the returned array (if not empty) describes the region for the C
* element that implements the source reference. Any preceding regions describe comments
* of that element.
* </p>
*
* @param reference a C element that is a source reference
* @param ctx the folding context
* @return the regions to be folded
*/
protected final IRegion[] computeProjectionRanges(ISourceReference reference,
FoldingStructureComputationContext ctx) {
try {
ISourceRange range = reference.getSourceRange();
return new IRegion[] { new Region(range.getStartPos(), range.getLength()) };
} catch (CModelException e) {
}
return new IRegion[0];
}
/**
* Creates a comment folding position from an
* {@link #alignRegion(IRegion, DefaultCFoldingStructureProvider.FoldingStructureComputationContext, boolean) aligned}
* region.
*
* @param aligned an aligned region
* @return a folding position corresponding to <code>aligned</code>
*/
protected final Position createCommentPosition(IRegion aligned) {
if (aligned == null) {
return null;
}
return new CommentPosition(aligned.getOffset(), aligned.getLength());
}
/**
* Creates a folding position that remembers its element from an
* {@link #alignRegion(IRegion, DefaultCFoldingStructureProvider.FoldingStructureComputationContext, boolean) aligned}
* region.
*
* @param aligned an aligned region
* @param element the element to remember
* @return a folding position corresponding to <code>aligned</code>
*/
protected final Position createElementPosition(IRegion aligned, ICElement element) {
return new CElementPosition(aligned.getOffset(), aligned.getLength(), element);
}
/**
* Aligns <code>region</code> to start and end at a line offset. The region's start is
* decreased to the next line offset, and the end offset increased to the next line start or the
* end of the document. <code>null</code> is returned if <code>region</code> is
* <code>null</code> itself or does not comprise at least one line delimiter, as a single line
* cannot be folded.
*
* @param region the region to align, may be <code>null</code>
* @param ctx the folding context
* @return a region equal or greater than <code>region</code> that is aligned with line
* offsets, <code>null</code> if the region is too small to be foldable (e.g. covers
* only one line)
*/
protected final IRegion alignRegion(IRegion region, FoldingStructureComputationContext ctx) {
return alignRegion(region, ctx, true);
}
/**
* Aligns <code>region</code> to start and end at a line offset. The region's start is
* decreased to the next line offset, and the end offset increased to the next line start or the
* end of the document. <code>null</code> is returned if <code>region</code> is
* <code>null</code> itself or does not comprise at least one line delimiter, as a single line
* cannot be folded.
*
* @param region the region to align, may be <code>null</code>
* @param ctx the folding context
* @param inclusive include line of end offset
* @return a region equal or greater than <code>region</code> that is aligned with line
* offsets, <code>null</code> if the region is too small to be foldable (e.g. covers
* only one line)
*/
protected final IRegion alignRegion(IRegion region, FoldingStructureComputationContext ctx, boolean inclusive) {
if (region == null)
return null;
IDocument document = ctx.getDocument();
try {
int start = document.getLineOfOffset(region.getOffset());
int end = document.getLineOfOffset(region.getOffset() + region.getLength());
if (start >= end)
return null;
int offset = document.getLineOffset(start);
int endOffset;
if (inclusive) {
if (document.getNumberOfLines() > end + 1)
endOffset = document.getLineOffset(end + 1);
else
endOffset = document.getLineOffset(end) + document.getLineLength(end);
} else {
endOffset = document.getLineOffset(end);
}
return new Region(offset, endOffset - offset);
} catch (BadLocationException x) {
// concurrent modification
return null;
}
}
private ProjectionAnnotationModel getModel() {
return fEditor.getAdapter(ProjectionAnnotationModel.class);
}
private IDocument getDocument() {
IDocumentProvider provider = fEditor.getDocumentProvider();
return provider.getDocument(fEditor.getEditorInput());
}
}