| /******************************************************************************* |
| * Copyright (c) 2006, 2018 IBM Corporation and others. |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| *******************************************************************************/ |
| package org.eclipse.dltk.ui.text.folding; |
| |
| import java.util.ArrayList; |
| 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 org.eclipse.core.resources.IProject; |
| import org.eclipse.core.runtime.ILog; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.dltk.ast.ASTNode; |
| import org.eclipse.dltk.ast.ASTVisitor; |
| import org.eclipse.dltk.ast.declarations.MethodDeclaration; |
| import org.eclipse.dltk.ast.declarations.ModuleDeclaration; |
| import org.eclipse.dltk.ast.declarations.TypeDeclaration; |
| import org.eclipse.dltk.ast.parser.ISourceParser; |
| import org.eclipse.dltk.compiler.env.ModuleSource; |
| import org.eclipse.dltk.core.DLTKCore; |
| import org.eclipse.dltk.core.DLTKLanguageManager; |
| import org.eclipse.dltk.core.ElementChangedEvent; |
| import org.eclipse.dltk.core.IElementChangedListener; |
| import org.eclipse.dltk.core.IMember; |
| import org.eclipse.dltk.core.IModelElement; |
| import org.eclipse.dltk.core.IModelElementDelta; |
| import org.eclipse.dltk.core.IModelElementVisitor; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.ISourceRange; |
| import org.eclipse.dltk.core.ISourceReference; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.core.SourceParserUtil; |
| import org.eclipse.dltk.corext.SourceRange; |
| import org.eclipse.dltk.internal.core.SourceMethod; |
| import org.eclipse.dltk.internal.ui.editor.EditorUtility; |
| import org.eclipse.dltk.internal.ui.editor.ScriptEditor; |
| import org.eclipse.dltk.internal.ui.text.DocumentCharacterIterator; |
| import org.eclipse.dltk.ui.DLTKUIPlugin; |
| import org.eclipse.dltk.ui.PreferenceConstants; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.Document; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IDocumentPartitioner; |
| import org.eclipse.jface.text.IRegion; |
| 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.rules.FastPartitioner; |
| import org.eclipse.jface.text.rules.IPartitionTokenScanner; |
| 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.ui.texteditor.IDocumentProvider; |
| import org.eclipse.ui.texteditor.ITextEditor; |
| |
| /** |
| * Updates the projection model of a source module using AST info. |
| * |
| * @see DelegatingFoldingStructureProvider |
| * @see IFoldingBlockProvider |
| */ |
| @Deprecated |
| public abstract class AbstractASTFoldingStructureProvider implements |
| IFoldingStructureProvider, IFoldingStructureProviderExtension { |
| |
| /** |
| * A context that contains the information needed to compute the folding |
| * structure of an {@link ISourceModule}. Computed folding regions are |
| * collected via |
| * {@link #addProjectionRange(AbstractASTFoldingStructureProvider.ScriptProjectionAnnotation, Position) |
| * addProjectionRange}. |
| */ |
| public static final class FoldingStructureComputationContext { |
| private final ProjectionAnnotationModel fModel; |
| private final IDocument fDocument; |
| private final boolean fAllowCollapsing; |
| protected LinkedHashMap<Annotation, Position> fMap = new LinkedHashMap<>(); |
| |
| public FoldingStructureComputationContext(IDocument document, |
| ProjectionAnnotationModel model, boolean allowCollapsing) { |
| fDocument = document; |
| fModel = model; |
| fAllowCollapsing = allowCollapsing; |
| } |
| |
| public Map<Annotation, Position> getMap() { |
| return fMap; |
| } |
| |
| /** |
| * 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(ScriptProjectionAnnotation annotation, |
| Position position) { |
| fMap.put(annotation, position); |
| } |
| } |
| |
| protected static final class SourceRangeStamp { |
| private int hash, length; |
| |
| public SourceRangeStamp(int hash, int lenght) { |
| this.hash = hash; |
| this.length = lenght; |
| } |
| |
| /** |
| * @return the hash |
| */ |
| public int getHash() { |
| return hash; |
| } |
| |
| /** |
| * @param hash |
| * the hash to set |
| */ |
| public void setHash(int hash) { |
| this.hash = hash; |
| } |
| |
| /** |
| * @return the length |
| */ |
| public int getLength() { |
| return length; |
| } |
| |
| /** |
| * @param length |
| * the length to set |
| */ |
| public void setLength(int length) { |
| this.length = length; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.lang.Object#equals(java.lang.Object) |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof SourceRangeStamp) { |
| SourceRangeStamp s = (SourceRangeStamp) obj; |
| return (s.hash == hash); // && s.length == length); |
| } |
| return super.equals(obj); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.lang.Object#hashCode() |
| */ |
| @Override |
| public int hashCode() { |
| return hash; |
| } |
| } |
| |
| /** |
| * A {@link ProjectionAnnotation} for code. |
| */ |
| protected static final class ScriptProjectionAnnotation |
| extends ProjectionAnnotation { |
| private boolean fIsComment; |
| private SourceRangeStamp stamp; |
| private IModelElement element; |
| |
| /** |
| * Creates a new projection annotation. |
| * |
| * @param isCollapsed |
| * <code>true</code> to set the initial state to |
| * collapsed, <code>false</code> to set it to |
| * expanded |
| * @param codeStamp |
| * the stamp of source code this annotation |
| * refers to |
| * @param isComment |
| * <code>true</code> for a foldable comment, |
| * <code>false</code> for a foldable code element |
| */ |
| public ScriptProjectionAnnotation(boolean isCollapsed, |
| boolean isComment, SourceRangeStamp codeStamp, |
| IModelElement element) { |
| super(isCollapsed); |
| fIsComment = isComment; |
| stamp = codeStamp; |
| this.element = element; |
| } |
| |
| public IModelElement getElement() { |
| return element; |
| } |
| |
| boolean isComment() { |
| return fIsComment; |
| } |
| |
| /** |
| * @return the stamp |
| */ |
| SourceRangeStamp getStamp() { |
| return stamp; |
| } |
| |
| /** |
| * @param stamp |
| * the stamp to set |
| */ |
| void setStamp(SourceRangeStamp stamp) { |
| this.stamp = stamp; |
| } |
| |
| void setIsComment(boolean isComment) { |
| fIsComment = isComment; |
| } |
| |
| /* |
| * @see java.lang.Object#toString() |
| */ |
| @Override |
| public String toString() { |
| return "ScriptProjectionAnnotation:\n" + //$NON-NLS-1$ |
| "\tcollapsed: \t" + isCollapsed() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$ |
| "\tcomment: \t" + isComment() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| private static final class Tuple { |
| ScriptProjectionAnnotation annotation; |
| Position position; |
| |
| Tuple(ScriptProjectionAnnotation annotation, Position position) { |
| this.annotation = annotation; |
| this.position = position; |
| } |
| } |
| |
| /** |
| * Filter for annotations. |
| */ |
| private static interface Filter { |
| boolean match(ScriptProjectionAnnotation annotation); |
| } |
| |
| /** |
| * Matches comments. |
| */ |
| private static final class CommentFilter implements Filter { |
| @Override |
| public boolean match(ScriptProjectionAnnotation annotation) { |
| if (annotation.isComment() && !annotation.isMarkedDeleted()) { |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Matches members. |
| */ |
| private static final class MemberFilter implements Filter { |
| @Override |
| public boolean match(ScriptProjectionAnnotation annotation) { |
| if (!annotation.isMarkedDeleted() |
| && annotation.getElement() instanceof IMember) { |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| |
| @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) { |
| // preRegion= new Region(offset + prefixEnd, contentStart - |
| // prefixEnd); |
| 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; |
| } |
| |
| /* |
| * @seeorg.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 script |
| * element, one folding away any lines after the caption. |
| */ |
| private static final class ScriptElementPosition extends Position |
| implements IProjectionPosition { |
| public ScriptElementPosition(int offset, int length) { |
| super(offset, length); |
| } |
| |
| /* |
| * @seeorg.eclipse.jface.text.source.projection.IProjectionPosition# |
| * computeFoldingRegions(org.eclipse.jface.text.IDocument) |
| */ |
| @Override |
| public IRegion[] computeProjectionRegions(IDocument document) |
| throws BadLocationException { |
| int nameStart = offset; |
| int firstLine = document.getLineOfOffset(offset); |
| int captionLine = document.getLineOfOffset(nameStart); |
| 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; |
| } |
| |
| /* |
| * @seeorg.eclipse.jface.text.source.projection.IProjectionPosition# |
| * computeCaptionOffset(org.eclipse.jface.text.IDocument) |
| */ |
| @Override |
| public int computeCaptionOffset(IDocument document) { |
| return 0; |
| } |
| } |
| |
| /** |
| * 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) { |
| 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; |
| } |
| } |
| |
| /* |
| * @seeorg.eclipse.jface.text.source.projection.IProjectionListener# |
| * projectionEnabled() |
| */ |
| @Override |
| public void projectionEnabled() { |
| handleProjectionEnabled(); |
| } |
| |
| /* |
| * @seeorg.eclipse.jface.text.source.projection.IProjectionListener# |
| * projectionDisabled() |
| */ |
| @Override |
| public void projectionDisabled() { |
| handleProjectionDisabled(); |
| } |
| } |
| |
| private class ElementChangedListener implements IElementChangedListener { |
| /* |
| * @see |
| * org.eclipse.dltk.core.IElementChangedListener#elementChanged(org. |
| * eclipse.dltk.core.ElementChangedEvent) |
| */ |
| @Override |
| public void elementChanged(ElementChangedEvent e) { |
| IModelElementDelta delta = findElement(fInput, e.getDelta()); |
| if (delta != null |
| && (delta.getFlags() & (IModelElementDelta.F_CONTENT |
| | IModelElementDelta.F_CHILDREN)) != 0) |
| update(createContext(false)); |
| } |
| |
| private IModelElementDelta findElement(IModelElement target, |
| IModelElementDelta delta) { |
| if (delta == null || target == null) |
| return null; |
| IModelElement element = delta.getElement(); |
| if (element.getElementType() > IModelElement.SOURCE_MODULE) |
| return null; |
| if (target.equals(element)) |
| return delta; |
| IModelElementDelta[] children = delta.getAffectedChildren(); |
| for (int i = 0; i < children.length; i++) { |
| IModelElementDelta d = findElement(target, children[i]); |
| if (d != null) |
| return d; |
| } |
| return null; |
| } |
| } |
| |
| /* context and listeners */ |
| private ITextEditor fEditor; |
| private ProjectionListener fProjectionListener; |
| private IModelElement fInput; |
| private IElementChangedListener fElementListener; |
| /* filters */ |
| /** Member filter, matches nested members (but not top-level types). */ |
| private final Filter fMemberFilter = new MemberFilter(); |
| /** Comment filter, matches comments. */ |
| private final Filter fCommentFilter = new CommentFilter(); |
| private IPreferenceStore fStore; |
| |
| private int fBlockLinesMin; |
| |
| protected boolean fDocsFolding; |
| protected boolean fCommentsFolding; |
| protected boolean fFoldNewLines; |
| |
| protected boolean fInitCollapseComments; |
| protected boolean fInitCollapseHeaderComments; |
| protected boolean fInitCollapseClasses; |
| protected boolean fInitCollapseMethods; |
| |
| private boolean fInitCollapseDocs; |
| |
| /** |
| * Creates a new folding provider. It must be |
| * {@link #install(ITextEditor, ProjectionViewer, IPreferenceStore) |
| * 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 AbstractASTFoldingStructureProvider() { |
| // empty constructor |
| } |
| |
| /** |
| * {@inheritDoc} |
| * <p> |
| * Subclasses may extend. |
| * </p> |
| * |
| * @param editor |
| * {@inheritDoc} |
| * @param viewer |
| * {@inheritDoc} |
| */ |
| @Override |
| public void install(ITextEditor editor, ProjectionViewer viewer, |
| IPreferenceStore store) { |
| internalUninstall(); |
| fStore = store; |
| if (editor instanceof ScriptEditor) { |
| fEditor = editor; |
| fProjectionListener = new ProjectionListener(viewer); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * <p> |
| * Subclasses may extend. |
| * </p> |
| */ |
| @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() { |
| handleProjectionDisabled(); |
| if (fEditor instanceof ScriptEditor) { |
| initialize(); |
| fElementListener = new ElementChangedListener(); |
| DLTKCore.addElementChangedListener(fElementListener); |
| } |
| } |
| |
| /** |
| * 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 (fElementListener != null) { |
| DLTKCore.removeElementChangedListener(fElementListener); |
| fElementListener = null; |
| } |
| } |
| |
| @Override |
| public final void initialize() { |
| initialize(false); |
| } |
| |
| @Override |
| public final void initialize(boolean isReinit) { |
| update(createInitialContext(isReinit)); |
| } |
| |
| protected FoldingStructureComputationContext createInitialContext( |
| boolean isReinit) { |
| initializePreferences(fStore); |
| fInput = getInputElement(); |
| if (fInput == null) |
| return null; |
| |
| // don't auto collapse if reinitializing |
| return createContext((isReinit) ? false : true); |
| } |
| |
| protected FoldingStructureComputationContext createInitialContext() { |
| return createInitialContext(true); |
| } |
| |
| protected 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 IModelElement getInputElement() { |
| if (fEditor == null) |
| return null; |
| return EditorUtility.getEditorInputModelElement(fEditor, false); |
| } |
| |
| private void update(FoldingStructureComputationContext ctx) { |
| if (ctx == null) |
| return; |
| Map<Annotation, Position> additions = new HashMap<>(); |
| List<Annotation> deletions = new ArrayList<>(); |
| List<Annotation> updates = new ArrayList<>(); |
| if (!computeFoldingStructure(ctx)) { |
| return; |
| } |
| Map<Annotation, Position> updated = ctx.fMap; |
| Map<SourceRangeStamp, List<Tuple>> previous = computeCurrentStructure( |
| ctx); |
| for (Iterator<Annotation> e = updated.keySet().iterator(); e |
| .hasNext();) { |
| ScriptProjectionAnnotation newAnnotation = (ScriptProjectionAnnotation) e |
| .next(); |
| SourceRangeStamp stamp = newAnnotation.getStamp(); |
| Position newPosition = updated.get(newAnnotation); |
| List<Tuple> annotations = previous.get(stamp); |
| if (annotations == null) { |
| additions.put(newAnnotation, newPosition); |
| } else { |
| Iterator<Tuple> x = annotations.iterator(); |
| boolean matched = false; |
| while (x.hasNext()) { |
| Tuple tuple = x.next(); |
| ScriptProjectionAnnotation existingAnnotation = tuple.annotation; |
| Position existingPosition = tuple.position; |
| if (newAnnotation.isComment() == existingAnnotation |
| .isComment()) { |
| if (existingPosition != null && (!newPosition |
| .equals(existingPosition) |
| || ctx.allowCollapsing() && existingAnnotation |
| .isCollapsed() != newAnnotation |
| .isCollapsed())) { |
| existingPosition.setOffset(newPosition.getOffset()); |
| existingPosition.setLength(newPosition.getLength()); |
| if (ctx.allowCollapsing() && existingAnnotation |
| .isCollapsed() != newAnnotation |
| .isCollapsed()) |
| if (newAnnotation.isCollapsed()) |
| existingAnnotation.markCollapsed(); |
| else |
| existingAnnotation.markExpanded(); |
| updates.add(existingAnnotation); |
| } |
| matched = true; |
| x.remove(); |
| break; |
| } |
| } |
| if (!matched) |
| additions.put(newAnnotation, newPosition); |
| if (annotations.isEmpty()) |
| previous.remove(stamp); |
| } |
| } |
| for (List<Tuple> list : previous.values()) { |
| int size = list.size(); |
| for (int i = 0; i < size; i++) |
| deletions.add(list.get(i).annotation); |
| } |
| Annotation[] removals = new Annotation[deletions.size()]; |
| deletions.toArray(removals); |
| Annotation[] changes = new Annotation[updates.size()]; |
| updates.toArray(changes); |
| ctx.getModel().modifyAnnotations(removals, additions, changes); |
| } |
| |
| private boolean computeFoldingStructure( |
| FoldingStructureComputationContext ctx) { |
| try { |
| String contents = ((ISourceReference) fInput).getSource(); |
| return computeFoldingStructure(contents, ctx); |
| } catch (ModelException e) { |
| return false; |
| } |
| } |
| |
| protected boolean computeFoldingStructure(String contents, |
| FoldingStructureComputationContext ctx) { |
| CodeBlock[] blockRegions = getCodeBlocks(contents); |
| if (blockRegions == null) { |
| return false; |
| } |
| |
| if (fCommentsFolding) { |
| IRegion[] commentRegions = computeCommentsRanges(contents); |
| addDocAnnotations(contents, ctx, commentRegions, false); |
| } |
| |
| String docPartition = getDocPartition(); |
| if (fDocsFolding && docPartition != null) { |
| IRegion[] commentRegions = computeCommentsRanges(contents, |
| docPartition); |
| addDocAnnotations(contents, ctx, commentRegions, true); |
| } |
| |
| // 2. Compute blocks regions |
| final Document d = new Document(contents); |
| final MethodCollector methodCollector = new MethodCollector(); |
| if (fInput != null) { |
| try { |
| fInput.accept(methodCollector); |
| } catch (ModelException e) { |
| // empty |
| } |
| } |
| for (int i = 0; i < blockRegions.length; i++) { |
| CodeBlock codeBlock = blockRegions[i]; |
| |
| if (!mayCollapse(codeBlock.statement, ctx)) |
| continue; |
| |
| boolean collapseCode = initiallyCollapse(codeBlock.statement, ctx); |
| IRegion reg = codeBlock.region; |
| |
| // code |
| boolean multiline = false; |
| try { |
| multiline = isMultilineRegion(d, reg); |
| } catch (BadLocationException e) { |
| // nothing to do |
| } |
| IRegion normalized = alignRegion(reg, ctx); |
| if (normalized != null && multiline) { |
| Position position = createMemberPosition(normalized); |
| if (position != null) { |
| try { |
| int len = normalized.getOffset() |
| + normalized.getLength(); |
| if (contents.length() == len + 1) { |
| len = len - 1; |
| } |
| if (contents.length() >= len) { |
| int hash = contents |
| .substring(normalized.getOffset(), len) |
| .hashCode(); |
| IModelElement element = null; |
| |
| if (codeBlock.statement instanceof MethodDeclaration) { |
| MethodDeclaration meth = (MethodDeclaration) codeBlock.statement; |
| hash = meth.getName().hashCode(); |
| element = methodCollector.get( |
| meth.getNameStart(), meth.getNameEnd() |
| - meth.getNameStart()); |
| } |
| SourceRangeStamp codeStamp = new SourceRangeStamp( |
| hash, normalized.getLength()); |
| ScriptProjectionAnnotation annotation = new ScriptProjectionAnnotation( |
| collapseCode, false, codeStamp, element); |
| ctx.addProjectionRange(annotation, position); |
| } |
| } catch (StringIndexOutOfBoundsException e) { |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| private void addDocAnnotations(String contents, |
| FoldingStructureComputationContext ctx, IRegion[] commentRegions, |
| boolean isDoc) { |
| if (commentRegions.length == 0) { |
| return; |
| } |
| final IElementCommentResolver commentResolver = fInput != null |
| ? createElementCommentResolver(fInput, contents) |
| : null; |
| for (int i = 0; i < commentRegions.length; i++) { |
| IRegion normalized = alignRegion(commentRegions[i], ctx); |
| if (normalized == null) { |
| continue; |
| } |
| |
| Position position = createCommentPosition(normalized); |
| if (position == null) { |
| continue; |
| } |
| |
| int hash = contents |
| .substring(normalized.getOffset(), |
| normalized.getOffset() + normalized.getLength()) |
| .hashCode(); |
| final IModelElement element; |
| if (commentResolver != null) { |
| element = commentResolver |
| .getElementByCommentPosition(position.offset, 0); |
| } else { |
| element = null; |
| } |
| |
| boolean initCollapse = (isDoc) |
| ? initiallyCollapseDocs(normalized, ctx) |
| : initiallyCollapseComments(normalized, ctx); |
| |
| ctx.addProjectionRange(new ScriptProjectionAnnotation(initCollapse, |
| true, new SourceRangeStamp(hash, normalized.getLength()), |
| element), position); |
| } |
| } |
| |
| /** |
| * @param modelElement |
| * @param contents |
| * @return |
| */ |
| public IElementCommentResolver createElementCommentResolver( |
| IModelElement modelElement, String contents) { |
| return new DefaultElementCommentResolver((ISourceModule) modelElement, |
| contents); |
| } |
| |
| protected static class CodeBlock { |
| public ASTNode statement; |
| public IRegion region; |
| |
| /** |
| * Represents foldable statement. |
| * |
| * @param s |
| * AST statement |
| * @param r |
| * <b>Absolute</b> statement position in source file |
| */ |
| public CodeBlock(ASTNode s, IRegion r) { |
| this.statement = s; |
| this.region = r; |
| } |
| } |
| |
| protected int getMinimalFoldableLinesCount() { |
| return fBlockLinesMin; |
| } |
| |
| protected void initializePreferences(IPreferenceStore store) { |
| fBlockLinesMin = store |
| .getInt(PreferenceConstants.EDITOR_FOLDING_LINES_LIMIT); |
| |
| fDocsFolding = store |
| .getBoolean(PreferenceConstants.EDITOR_DOCS_FOLDING_ENABLED); |
| |
| fCommentsFolding = store.getBoolean( |
| PreferenceConstants.EDITOR_COMMENTS_FOLDING_ENABLED); |
| |
| fFoldNewLines = store.getBoolean( |
| PreferenceConstants.EDITOR_COMMENT_FOLDING_JOIN_NEWLINES); |
| |
| fInitCollapseComments = store |
| .getBoolean(PreferenceConstants.EDITOR_FOLDING_INIT_COMMENTS); |
| |
| fInitCollapseHeaderComments = store.getBoolean( |
| PreferenceConstants.EDITOR_FOLDING_INIT_HEADER_COMMENTS); |
| |
| fInitCollapseDocs = store |
| .getBoolean(PreferenceConstants.EDITOR_FOLDING_INIT_DOCS); |
| |
| fInitCollapseClasses = store.getBoolean(getInitiallyFoldClassesKey()); |
| fInitCollapseMethods = store.getBoolean(getInitiallyFoldMethodsKey()); |
| } |
| |
| /** |
| * Returns the preference key used to indicate if classes should be |
| * 'initially' folded. |
| * |
| * <p> |
| * Sub-classes may override this method to provide an alternative preference |
| * key if they are not using the one in {@link PreferenceConstants}. |
| * </p> |
| */ |
| protected String getInitiallyFoldClassesKey() { |
| return PreferenceConstants.EDITOR_FOLDING_INIT_CLASSES; |
| } |
| |
| /** |
| * Returns the preference key used to indicate if methods should be |
| * 'initially' folded. |
| * |
| * <p> |
| * Sub-classes may override this method to provide an alternative preference |
| * key if they are not using the one in {@link PreferenceConstants}. |
| * </p> |
| */ |
| protected String getInitiallyFoldMethodsKey() { |
| return PreferenceConstants.EDITOR_FOLDING_INIT_METHODS; |
| } |
| |
| protected boolean isEmptyRegion(IDocument d, ITypedRegion r) |
| throws BadLocationException { |
| return isEmptyRegion(d, r.getOffset(), r.getLength()); |
| } |
| |
| /** |
| * Tests if the specified region contains only space or tab characters. |
| * |
| * @param document |
| * @param region |
| * @return |
| * @throws BadLocationException |
| * @since 2.0 |
| */ |
| protected boolean isBlankRegion(IDocument document, ITypedRegion region) |
| throws BadLocationException { |
| String value = document.get(region.getOffset(), region.getLength()); |
| for (int i = 0; i < value.length(); ++i) { |
| char ch = value.charAt(i); |
| if (ch != ' ' && ch != '\t') { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| protected boolean isEmptyRegion(IDocument d, int offset, int length) |
| throws BadLocationException { |
| return d.get(offset, length).trim().length() == 0; |
| } |
| |
| protected boolean isMultilineRegion(IDocument d, IRegion region) |
| throws BadLocationException { |
| int line1 = d.getLineOfOffset(region.getOffset()); |
| int line2 = d.getLineOfOffset(region.getOffset() + region.getLength()); |
| final int foldMinLines = getMinimalFoldableLinesCount(); |
| if (foldMinLines > 0) { |
| return line2 - line1 + 1 >= foldMinLines; |
| } |
| return line1 != line2; |
| } |
| |
| /** |
| * Creates a comment folding position from an |
| * {@link #alignRegion(IRegion, AbstractASTFoldingStructureProvider.FoldingStructureComputationContext) |
| * aligned} region. |
| * |
| * @param aligned |
| * an aligned region |
| * @return a folding position corresponding to <code>aligned</code> |
| */ |
| protected final Position createCommentPosition(IRegion aligned) { |
| return new CommentPosition(aligned.getOffset(), aligned.getLength()); |
| } |
| |
| /** |
| * Creates a folding position that remembers its member from an |
| * {@link #alignRegion(IRegion, AbstractASTFoldingStructureProvider.FoldingStructureComputationContext) |
| * aligned} region. |
| * |
| * @param aligned |
| * an aligned region |
| * |
| * @return a folding position corresponding to <code>aligned</code> |
| */ |
| protected final Position createMemberPosition(IRegion aligned) { |
| return new ScriptElementPosition(aligned.getOffset(), |
| aligned.getLength()); |
| } |
| |
| /** |
| * 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 IRegion alignRegion(IRegion region, |
| FoldingStructureComputationContext ctx) { |
| 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 (document.getNumberOfLines() > end + 1) { |
| endOffset = document.getLineOffset(end + 1); |
| } else { |
| endOffset = document.getLineOffset(end) |
| + document.getLineLength(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()); |
| } |
| |
| private Map<SourceRangeStamp, List<Tuple>> computeCurrentStructure( |
| FoldingStructureComputationContext ctx) { |
| Map<SourceRangeStamp, List<Tuple>> map = new HashMap<>(); |
| ProjectionAnnotationModel model = ctx.getModel(); |
| Iterator<Annotation> e = model.getAnnotationIterator(); |
| while (e.hasNext()) { |
| Annotation annotation = e.next(); |
| if (annotation instanceof ScriptProjectionAnnotation) { |
| ScriptProjectionAnnotation ann = (ScriptProjectionAnnotation) annotation; |
| Position position = model.getPosition(ann); |
| List<Tuple> list = map.get(ann.getStamp()); |
| if (list == null) { |
| list = new ArrayList<>(2); |
| map.put(ann.getStamp(), list); |
| } |
| list.add(new Tuple(ann, position)); |
| } |
| } |
| Comparator<Tuple> comparator = (o1, o2) -> o1.position.getOffset() |
| - o2.position.getOffset(); |
| for (Iterator<List<Tuple>> it = map.values().iterator(); it |
| .hasNext();) { |
| List<Tuple> list = it.next(); |
| Collections.sort(list, comparator); |
| } |
| return map; |
| } |
| |
| @Override |
| public final void collapseMembers() { |
| modifyFiltered(fMemberFilter, false); |
| } |
| |
| @Override |
| public final void collapseComments() { |
| modifyFiltered(fCommentFilter, false); |
| } |
| |
| /** |
| * Collapses or expands all annotations matched by the passed filter. |
| * |
| * @param filter |
| * the filter to use to select which annotations to |
| * collapse |
| * @param expand |
| * <code>true</code> to expand the matched annotations, |
| * <code>false</code> to collapse them |
| */ |
| private void modifyFiltered(Filter filter, boolean expand) { |
| if (!isInstalled()) |
| return; |
| ProjectionAnnotationModel model = getModel(); |
| if (model == null) |
| return; |
| List<Annotation> modified = new ArrayList<>(); |
| Iterator<Annotation> iter = model.getAnnotationIterator(); |
| while (iter.hasNext()) { |
| Annotation annotation = iter.next(); |
| if (annotation instanceof ScriptProjectionAnnotation) { |
| ScriptProjectionAnnotation annot = (ScriptProjectionAnnotation) annotation; |
| if (expand == annot.isCollapsed() && filter.match(annot)) { |
| if (expand) |
| annot.markExpanded(); |
| else |
| annot.markCollapsed(); |
| modified.add(annot); |
| } |
| } |
| } |
| model.modifyAnnotations(null, null, |
| modified.toArray(new Annotation[modified.size()])); |
| } |
| |
| protected abstract String getPartition(); |
| |
| protected abstract String getCommentPartition(); |
| |
| protected String getDocPartition() { |
| return null; |
| } |
| |
| protected abstract IPartitionTokenScanner getPartitionScanner(); |
| |
| protected abstract String getNatureId(); |
| |
| protected abstract String[] getPartitionTypes(); |
| |
| protected abstract ILog getLog(); |
| |
| protected FoldingASTVisitor getFoldingVisitor(int offset) { |
| return new FoldingASTVisitor(offset); |
| } |
| |
| protected static class FoldingASTVisitor extends ASTVisitor { |
| private final List<CodeBlock> result = new ArrayList<>(); |
| private final int offset; |
| |
| protected FoldingASTVisitor(int offset) { |
| this.offset = offset; |
| } |
| |
| @Override |
| public boolean visit(MethodDeclaration s) throws Exception { |
| add(s); |
| return super.visit(s); |
| } |
| |
| @Override |
| public boolean visit(TypeDeclaration s) throws Exception { |
| add(s); |
| return super.visit(s); |
| } |
| |
| public CodeBlock[] getResults() { |
| return result.toArray(new CodeBlock[result.size()]); |
| } |
| |
| protected final void add(ASTNode s) { |
| int start = offset + s.sourceStart(); |
| int end = s.sourceEnd() - s.sourceStart(); |
| |
| result.add(new CodeBlock(s, new Region(start, end))); |
| } |
| |
| protected void add(CodeBlock block) { |
| result.add(block); |
| } |
| |
| } |
| |
| protected final ISourceParser getSourceParser() { |
| final IProject project = fInput != null |
| ? fInput.getScriptProject().getProject() |
| : null; |
| return DLTKLanguageManager.getSourceParser(project, getNatureId()); |
| } |
| |
| /** |
| * Should locate all statements and return |
| * |
| * @param code |
| */ |
| protected CodeBlock[] getCodeBlocks(String code) { |
| return getCodeBlocks(code, 0); |
| } |
| |
| protected CodeBlock[] getCodeBlocks(String code, int offset) { |
| ModuleDeclaration decl = parse(code, offset); |
| if (decl == null) { |
| return null; |
| } |
| return buildCodeBlocks(decl, offset); |
| } |
| |
| protected final IModelElement getModuleElement() { |
| return fInput; |
| } |
| |
| protected final ModuleDeclaration parse(String code, int offset) { |
| if (offset == 0 && fInput instanceof ISourceModule) { |
| final ISourceModule module = (ISourceModule) fInput; |
| try { |
| if (code.equals(module.getSource())) { |
| // use the cache luke! ;) |
| return SourceParserUtil.getModuleDeclaration(module); |
| } |
| } catch (ModelException e) { |
| getLog().log(new Status(IStatus.WARNING, DLTKUIPlugin.PLUGIN_ID, |
| e.getMessage(), e)); |
| } |
| } |
| return (ModuleDeclaration) getSourceParser() |
| .parse(new ModuleSource(code), null); |
| } |
| |
| protected CodeBlock[] buildCodeBlocks(ModuleDeclaration decl, int offset) { |
| FoldingASTVisitor visitor = getFoldingVisitor(offset); |
| |
| try { |
| decl.traverse(visitor); |
| } catch (Exception e) { |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| } |
| |
| return visitor.getResults(); |
| } |
| |
| /** |
| * Returns is it possible to collapse statement, or it should never be |
| * folded |
| * |
| * @param s |
| * @param ctx |
| */ |
| protected boolean mayCollapse(ASTNode s, |
| FoldingStructureComputationContext ctx) { |
| if (s instanceof TypeDeclaration) { |
| return true; |
| } |
| |
| if (s instanceof MethodDeclaration) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| protected boolean initiallyCollapse(ASTNode s, |
| FoldingStructureComputationContext ctx) { |
| if (ctx.allowCollapsing()) { |
| return initiallyCollapse(s); |
| } |
| |
| return false; |
| } |
| |
| protected boolean initiallyCollapse(ASTNode s) { |
| // classes, modules, etc |
| if (s instanceof TypeDeclaration && fInitCollapseClasses) { |
| return true; |
| } |
| |
| // methods, subroutines, etc |
| if (s instanceof MethodDeclaration && fInitCollapseMethods) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| protected boolean initiallyCollapseComments(IRegion commentRegion, |
| FoldingStructureComputationContext ctx) { |
| if (ctx.allowCollapsing()) { |
| return isHeaderRegion(commentRegion, ctx) |
| ? fInitCollapseHeaderComments |
| : fInitCollapseComments; |
| } |
| return false; |
| } |
| |
| protected boolean initiallyCollapseDocs(IRegion commentRegion, |
| FoldingStructureComputationContext ctx) { |
| if (ctx.allowCollapsing()) { |
| return fInitCollapseDocs; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Checks if the specified region is located at the beginning of the |
| * document |
| * |
| * @param region |
| * @param ctx |
| */ |
| protected boolean isHeaderRegion(IRegion region, |
| FoldingStructureComputationContext ctx) { |
| final int offset = region.getOffset(); |
| if (offset == 0) { |
| return true; |
| } else if (offset < 100) { |
| try { |
| return isEmptyRegion(ctx.getDocument(), 0, offset); |
| } catch (BadLocationException e) { |
| // |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Installs a partitioner with <code>document</code>. |
| * |
| * @param document |
| * the document |
| */ |
| private void installDocumentStuff(Document document) { |
| IDocumentPartitioner partitioner = getDocumentPartitioner(); |
| partitioner.connect(document); |
| document.setDocumentPartitioner(getPartition(), partitioner); |
| } |
| |
| protected IDocumentPartitioner getDocumentPartitioner() { |
| return new FastPartitioner(getPartitionScanner(), getPartitionTypes()); |
| } |
| |
| /** |
| * Removes partitioner with <code>document</code>. |
| * |
| * @param document |
| * the document |
| */ |
| private void removeDocumentStuff(Document document) { |
| document.setDocumentPartitioner(getPartition(), null); |
| } |
| |
| @Override |
| public void expandElements(final IModelElement[] array) { |
| modifyFiltered(annotation -> { |
| IModelElement element = annotation.getElement(); |
| if (element == null) |
| return false; |
| for (int a = 0; a < array.length; a++) { |
| IModelElement e = array[a]; |
| if (e.equals(element)) { |
| return true; |
| } |
| } |
| return false; |
| }, true); |
| } |
| |
| @Override |
| public void collapseElements(IModelElement[] modelElements) { |
| // empty implementation |
| } |
| |
| private ITypedRegion getRegion(IDocument d, int offset) |
| throws BadLocationException { |
| return TextUtilities.getPartition(d, getPartition(), offset, true); |
| } |
| |
| protected IRegion[] computeCommentsRanges(String contents) { |
| // for backwards compatibility incase anyone has overridden this.. |
| return computeCommentsRanges(contents, getCommentPartition()); |
| } |
| |
| protected IRegion[] computeCommentsRanges(String contents, |
| String partition) { |
| try { |
| if (contents == null) |
| return new IRegion[0]; |
| Document d = new Document(contents); |
| installDocumentStuff(d); |
| List<ITypedRegion> docRegionList = new ArrayList<>(); |
| int offset = 0; |
| for (;;) { |
| try { |
| ITypedRegion region = getRegion(d, offset); |
| docRegionList.add(region); |
| offset = region.getLength() + region.getOffset() + 1; |
| } catch (BadLocationException e1) { |
| break; |
| } |
| } |
| ITypedRegion start = null; |
| ITypedRegion lastRegion = null; |
| List<IRegion> regions = new ArrayList<>(); |
| for (ITypedRegion region : docRegionList) { |
| if (region.getType().equals(partition) |
| && startsAtLineBegin(d, region)) { |
| if (start == null) |
| start = region; |
| } else if (start != null && (isBlankRegion(d, region) |
| || isEmptyRegion(d, region) && collapseEmptyLines())) { |
| // blanks or empty lines |
| // TODO introduce line limit for collapseEmptyLines() ? |
| } else { |
| if (start != null) { |
| int offset0 = start.getOffset(); |
| int length0 = lastRegion.getOffset() |
| + lastRegion.getLength() - offset0 - 1; |
| length0 = contents.substring(offset0, offset0 + length0) |
| .trim().length(); |
| IRegion fullRegion = new Region(offset0, length0); |
| if (isMultilineRegion(d, fullRegion)) { |
| regions.add(fullRegion); |
| } |
| } |
| start = null; |
| } |
| lastRegion = region; |
| } |
| if (start != null) { |
| int offset0 = start.getOffset(); |
| int length0 = lastRegion.getOffset() - offset0 |
| + lastRegion.getLength() - 1; |
| IRegion fullRegion = new Region(offset0, length0); |
| if (isMultilineRegion(d, fullRegion)) { |
| regions.add(fullRegion); |
| } |
| } |
| prepareRegions(d, regions); |
| removeDocumentStuff(d); |
| return regions.toArray(new IRegion[regions.size()]); |
| } catch (BadLocationException e) { |
| e.printStackTrace(); |
| } |
| return new IRegion[0]; |
| } |
| |
| /** |
| * @param d |
| * @param regions |
| * @since 2.0 |
| */ |
| protected void prepareRegions(Document d, List<IRegion> regions) { |
| // override in descendants |
| } |
| |
| private boolean startsAtLineBegin(Document d, ITypedRegion region) |
| throws BadLocationException { |
| int lineStart = d.getLineOffset(d.getLineOfOffset(region.getOffset())); |
| if (lineStart != region.getOffset()) { |
| if (!isEmptyRegion(d, lineStart, region.getOffset() - lineStart)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| protected boolean collapseEmptyLines() { |
| return fFoldNewLines; |
| } |
| |
| /** |
| * @deprecated |
| */ |
| @Deprecated |
| protected final void getElementCommentResolver() { |
| // will be deleted |
| } |
| |
| public static class MethodCollector implements IModelElementVisitor { |
| private final Map<SourceRange, IModelElement> methodByNameRange = new HashMap<>(); |
| |
| @Override |
| public boolean visit(IModelElement element) { |
| |
| if (element instanceof SourceMethod) { |
| try { |
| final ISourceRange nameRange = ((SourceMethod) element) |
| .getNameRange(); |
| methodByNameRange.put(new SourceRange(nameRange), element); |
| } catch (ModelException e) { |
| // empty |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * @param offset |
| * @param length |
| */ |
| public IModelElement get(int offset, int length) { |
| return methodByNameRange.get(new SourceRange(offset, length)); |
| } |
| |
| } |
| } |