/******************************************************************************* * Copyright (c) 2005, 2012 eBay Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * *******************************************************************************/ package org.eclipse.vjet.eclipse.internal.ui.text.folding; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.ILog; import org.eclipse.dltk.mod.ast.ASTNode; import org.eclipse.dltk.mod.core.IMember; import org.eclipse.dltk.mod.core.IMethod; import org.eclipse.dltk.mod.core.IModelElement; import org.eclipse.dltk.mod.core.IParent; import org.eclipse.dltk.mod.core.ISourceModule; import org.eclipse.dltk.mod.core.ISourceRange; import org.eclipse.dltk.mod.core.ISourceReference; import org.eclipse.dltk.mod.core.IType; import org.eclipse.dltk.mod.core.ModelException; import org.eclipse.dltk.mod.corext.SourceRange; import org.eclipse.dltk.mod.internal.core.IJSInitializer; import org.eclipse.dltk.mod.ui.text.folding.AbstractASTFoldingStructureProvider; import org.eclipse.dltk.mod.ui.text.folding.IElementCommentResolver; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.ui.text.folding.DefaultJavaFoldingStructureProvider; 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.Position; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.rules.IPartitionTokenScanner; import org.eclipse.jface.text.source.projection.IProjectionPosition; import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel; import org.eclipse.jface.text.source.projection.ProjectionViewer; import org.eclipse.vjet.eclipse.codeassist.CodeassistUtils; import org.eclipse.vjet.eclipse.core.IImportContainer; import org.eclipse.vjet.eclipse.core.IJSType; import org.eclipse.vjet.eclipse.core.VjoNature; import org.eclipse.vjet.eclipse.internal.ui.scriptdoc.IScanner; import org.eclipse.vjet.eclipse.internal.ui.scriptdoc.PublicScanner; import org.eclipse.vjet.eclipse.internal.ui.text.IJavaScriptPartitions; import org.eclipse.vjet.eclipse.internal.ui.text.JavascriptPartitionScanner; import org.eclipse.vjet.eclipse.ui.VjetPreferenceConstants; import org.eclipse.vjet.eclipse.ui.VjetUIPlugin; public class VjoFoldingStructureProvider extends AbstractASTFoldingStructureProvider { /** * 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); } /* * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument) */ 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; } /* * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument) */ public int computeCaptionOffset(IDocument document) throws BadLocationException { return 0; } } /* preferences */ private boolean m_CollapseImportContainer = true; private boolean m_CollapseJavadoc= false; private boolean m_CollapseInnerTypes = false; private boolean m_CollapseMembers = false; private boolean m_CollapseHeaderComments = true; @Override protected String[] getCommentPartition() { return new String[]{/*IJavaScriptPartitions.JS_SINGLE_COMMENT,*/ IJavaScriptPartitions.JS_MULTI_COMMENT,IJavaScriptPartitions.JS_DOC}; } @Override protected ILog getLog() { // TODO Auto-generated method stub return null; } @Override protected String getNatureId() { return VjoNature.NATURE_ID; } @Override protected String getPartition() { return IJavaScriptPartitions.JS_PARTITIONING; } @Override protected IPartitionTokenScanner getPartitionScanner() { return new JavascriptPartitionScanner(); } @Override protected String[] getPartitionTypes() { return IJavaScriptPartitions.JS_PARTITION_TYPES; } @Override protected boolean initiallyCollapse(ASTNode s, FoldingStructureComputationContext ctx) { // TODO Auto-generated method stub return false; } @Override protected boolean mayCollapse(ASTNode s, FoldingStructureComputationContext ctx) { // TODO Auto-generated method stub return false; } @Override protected boolean computeFoldingStructure(String contents, FoldingStructureComputationContext ctx2) { FoldingStructureComputationContext2 ctx = (FoldingStructureComputationContext2) ctx2; // ctx.getScanner().setSource(contents.toCharArray()); IModelElement[] elements; try { IModelElement element = getInputElement(); if (CodeassistUtils.isVjoSourceModule(element) && CodeassistUtils.isModuleInBuildPath(element)) { elements = ((IParent) getInputElement()).getChildren(); computeFoldingStructure(elements, ctx); } } catch (ModelException e) { e.printStackTrace(); return false; } if (fCommentsFolding) { // 1. Compute regions for comments IRegion[] commentRegions = computeCommentsRanges(contents); // modify by patrick for bug 1288 if (commentRegions.length == 0) { return true; } // comments Map<Position, ScriptProjectionAnnotation> typeAnnotationList = new HashMap<Position, ScriptProjectionAnnotation>(); for (int i = 0; i < commentRegions.length; i++) { IRegion normalized = alignRegion(commentRegions[i], ctx); if (normalized != null) { Position position = createCommentPosition(normalized); if (position != null) { int hash = contents .substring( normalized.getOffset(), normalized.getOffset() + normalized.getLength()) .hashCode(); IModelElement element = null; IElementCommentResolver res = getElementCommentResolver(); if (res != null && fInput != null) { element = res.getElementByCommentPosition( (ISourceModule) fInput, position.offset, position.length); } boolean toAdd = initiallyCollapseComments(normalized, ctx); ScriptProjectionAnnotation annotation = new ScriptProjectionAnnotation( toAdd, true, new SourceRangeStamp(hash, normalized.getLength()), element); typeAnnotationList.put(position, annotation); if (element instanceof IType) { if (isInnerType((IType) element)) { continue; } if (!ctx.hasFirstType()) ctx.setFirstType((IType) element); } } } } handleHeaderComment(ctx, contents, typeAnnotationList); // set to ctx Set<Position> positionSet = typeAnnotationList.keySet(); for (Position pos : positionSet) { ctx.addProjectionRange( (ScriptProjectionAnnotation) typeAnnotationList .get(pos), pos); } // end modify } return true; } //add by patrick private void handleHeaderComment(FoldingStructureComputationContext2 ctx, String contents, Map<Position, ScriptProjectionAnnotation> typeAnnotationMap) { try { if (!ctx.hasHeaderComment() && fInput != null && ((ISourceModule) fInput).hasChildren()) { // find the first element's position ISourceModule module = (ISourceModule) fInput; int firstElementOffset = getFirstElementOffset(module); Set<Position> positionSet = typeAnnotationMap.keySet(); List<Position> positions = getSortedPositionList(positionSet); // check multiple header comments List<Position> headerCommentPositions = getHeaderCommentList( firstElementOffset, positions); if (headerCommentPositions == null || headerCommentPositions.isEmpty()) { return; } ctx.setHasHeaderComment(); if (headerCommentPositions.size() > 1) { // merge multiple comments to one annotation Position lastHeaderComment = headerCommentPositions .get(headerCommentPositions.size() - 1); IModelElement firstElement = typeAnnotationMap.get( lastHeaderComment).getElement(); int newOffset = headerCommentPositions.get(0).offset; int newLength = lastHeaderComment.getOffset() + lastHeaderComment.getLength() - newOffset; Position newHeaderCommentPosition = createCommentPosition(new Region( newOffset, newLength)); int hash = contents.substring(newOffset, newOffset + newLength).hashCode(); ScriptProjectionAnnotation newHeaderCommentAnnotation = new ScriptProjectionAnnotation( ctx.collapseHeaderComments(), true, new SourceRangeStamp(hash, newLength), firstElement); // remove old header comments firstly for (Position p : headerCommentPositions) { typeAnnotationMap.remove(p); } typeAnnotationMap.put(newHeaderCommentPosition, newHeaderCommentAnnotation); } else { // one header comment ScriptProjectionAnnotation headerCommentAnno = typeAnnotationMap .get(headerCommentPositions.get(0)); if (ctx.collapseHeaderComments()) { headerCommentAnno.markCollapsed(); } else { headerCommentAnno.markExpanded(); } } } } catch (ModelException e) { VjetUIPlugin.log(e); } } private List<Position> getHeaderCommentList(int firstElementOffset, List<Position> positions) { List<Position> headerCommentPositions = null; final int len = positions.size(); int i = -1; for (i = 0; i < len; i++) { if (firstElementOffset < positions.get(i).getOffset()) { headerCommentPositions = positions.subList(0, i); break; } } if(i == len){ headerCommentPositions = positions; } return headerCommentPositions; } private List<Position> getSortedPositionList(Set<Position> positionSet) { List<Position> positions = new ArrayList<Position>(positionSet); if (positionSet.size() > 1) {// if more than 1, start compare Collections.sort(positions, new Comparator<Position>() { public int compare(Position o1, Position o2) { return o1.getOffset() - o2.getOffset(); } }); } return positions; } private int getFirstElementOffset(ISourceModule module) throws ModelException { int firstElementOffset = -1; if (module.hasChildren()) { List<ISourceRange> ranges = new ArrayList<ISourceRange>(); IModelElement[] children = module.getChildren(); for (IModelElement child : children) { if (child instanceof ISourceReference) { ranges.add(((ISourceReference) child).getSourceRange()); } } if (ranges.size() > 0) { Collections.sort(ranges, new Comparator<ISourceRange>() { @Override public int compare(ISourceRange object1, ISourceRange object2) { return object1.getOffset() - object2.getOffset(); } }); firstElementOffset = ranges.get(0).getOffset(); } } return firstElementOffset; } //end add @Override protected boolean initiallyCollapseComments(IRegion commentRegion, FoldingStructureComputationContext ctx2) { FoldingStructureComputationContext2 ctx=(FoldingStructureComputationContext2)ctx2; return ctx.collapseJavadoc()&&ctx.allowCollapsing(); } private void computeFoldingStructure(IModelElement[] elements, FoldingStructureComputationContext ctx) throws ModelException { for (int i = 0; i < elements.length; i++) { IModelElement element = elements[i]; computeFoldingStructure(element, ctx); if (element instanceof IParent) { IParent parent = (IParent) element; computeFoldingStructure(parent.getChildren(), ctx); } } } @Override public IElementCommentResolver getElementCommentResolver() { return new VjetElementCommentResolver(); } @Override 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 FoldingStructureComputationContext2(doc, model, allowCollapse); } /** * Returns <code>true</code> if <code>type</code> is an anonymous enum declaration, * <code>false</code> otherwise. See also https://bugs.eclipse.org/bugs/show_bug.cgi?id=143276 * * @param type the type to test * @return <code>true</code> if <code>type</code> is an anonymous enum declaration * @since 3.3 */ private boolean isAnonymousEnum(IType type) { if(type instanceof IJSType){ IJSType jsType=(IJSType)type; try { return jsType.isEnum() && jsType.getElementName().length()==0; } catch (ModelException x) { return false; // optimistically } } return false; } /** * Returns <code>true</code> if <code>type</code> is not a top-level type, <code>false</code> if it is. * * @param type the type to test * @return <code>true</code> if <code>type</code> is an inner type */ private boolean isInnerType(IType type) { return type.getDeclaringType() != null&& !isAnonymousEnum(type); } /** * Computes the folding structure for a given * {@link IJavaElement java element}. Computed projection annotations are * {@link DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext#addProjectionRange(DefaultJavaFoldingStructureProvider.JavaProjectionAnnotation, Position) added} * to the computation context. * <p> * Subclasses may extend or replace. The default implementation creates * projection annotations for the following elements: * <ul> * <li>true members (not for top-level types)</li> * <li>the javadoc comments of any member</li> * <li>header comments (javadoc or multi-line comments appearing before the * first type's javadoc or before the package or import declarations).</li> * </ul> * </p> * * @param element * the java element to compute the folding structure for * @param ctx * the computation context */ protected void computeFoldingStructure(IModelElement element, FoldingStructureComputationContext ctx2) { FoldingStructureComputationContext2 ctx=(FoldingStructureComputationContext2)ctx2; boolean collapse = false; boolean collapseCode = true; switch (element.getElementType()) { case IImportContainer.ELEMENT_TYPE: collapse = ctx.allowCollapsing() && m_CollapseImportContainer; break; case IModelElement.TYPE: //collapseCode = false; collapseCode = isInnerType((IType) element) ; collapse = ctx.collapseInnerTypes() && collapseCode; break; case IModelElement.METHOD: case IModelElement.FIELD: // case IModelElement.INITIALIZER: collapse = ctx.allowCollapsing() && m_CollapseMembers; break; case IJSInitializer.ELEMENT_TYPE: // case IModelElement.INITIALIZER: collapse = ctx.allowCollapsing() && m_CollapseMembers; break; default: return; } IRegion[] regions = computeProjectionRanges((ISourceReference) element, ctx); if (regions.length > 0) { // // comments // for (int i = 0; i < regions.length - 1; i++) { // IRegion normalized = alignRegion(regions[i], ctx); // if (normalized != null) { // Position position = createCommentPosition(normalized); // if (position != null) { // boolean commentCollapse; // if (i == 0 && (regions.length > 2 || ctx.hasHeaderComment()) && element == ctx.getFirstType()) { // commentCollapse = ctx.collapseHeaderComments(); // } else { // commentCollapse = ctx.collapseJavadoc(); // } // //ctx.addProjectionRange(new ScriptProjectionAnnotation(commentCollapse, element, true), position); // ctx.addProjectionRange(new ScriptProjectionAnnotation(commentCollapse,true,new SourceRangeStamp(0, position.length),element), position); // } // } // } // code if (collapseCode) { IRegion normalized = alignRegion(regions[regions.length - 1], ctx); if (normalized != null) { Position position = element instanceof IMember ? createMemberPosition(normalized, (IMember) element) : createCommentPosition(normalized); if (position != null) ctx.addProjectionRange(new ScriptProjectionAnnotation(collapse, false, new SourceRangeStamp(0, position.length), element), 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 java element that implements the source reference. Any preceding * regions describe javadoc comments of that java element. * </p> * * @param reference * a java element that is a source reference * @param ctx * the folding context * @return the regions to be folded */ protected final IRegion[] computeProjectionRanges( ISourceReference reference, FoldingStructureComputationContext ctx2) { try { FoldingStructureComputationContext2 ctx=(FoldingStructureComputationContext2)ctx2; ISourceRange range = reference.getSourceRange(); if (!SourceRange.isAvailable(range)) return new IRegion[0]; String contents = reference.getSource(); if (contents == null) return new IRegion[0]; if(reference instanceof IType&& isInnerType((IType)reference) ){ IType innerType=(IType)reference; ISourceRange sourceRange=innerType.getSourceRange(); //TODO handle sourceRange. Inner Type suport failed as Inner Type offset is minus 1 } // Fixed bug: collapsing comment should not collapse // the following function too if (reference instanceof IMethod) { ISourceRange actuallyMemberSourceRange = ((IMember) reference).getNameRange(); // the function has preceding comment if (actuallyMemberSourceRange.getOffset() > range.getOffset()) { int end = range.getOffset() + range.getLength(); range = new SourceRange(actuallyMemberSourceRange.getOffset(), end - actuallyMemberSourceRange.getOffset()); } } List<IRegion> regions = new ArrayList<IRegion>(); // if (!ctx.hasFirstType() && reference instanceof IType) { // ctx.setFirstType((IType) reference); // IRegion headerComment = computeHeaderComment(ctx); // if (headerComment != null) { // regions.add(headerComment); // ctx.setHasHeaderComment(); // } // } // // final int shift = range.getOffset(); // IScanner scanner = ctx.getScanner(); // scanner.resetTo(shift, shift + range.getLength()); // // int start = shift; // while (true) { // // int token = scanner.getNextToken(); // start = scanner.getCurrentTokenStartPosition(); // // switch (token) { // case ITerminalSymbols.TokenNameCOMMENT_JAVADOC: // case ITerminalSymbols.TokenNameCOMMENT_BLOCK: { // int end = scanner.getCurrentTokenEndPosition() + 1; // regions.add(new Region(start, end - start)); // continue; // } // case ITerminalSymbols.TokenNameCOMMENT_LINE: // continue; // } // // break; // } // // regions.add(new Region(start, shift + range.getLength() - start)); regions.add(new Region(range.getOffset(), range.getLength())); IRegion[] result = new IRegion[regions.size()]; regions.toArray(result); return result; } catch (Exception e) { e.printStackTrace(); } return new IRegion[0]; } /** * Creates a folding position that remembers its member from an * {@link #alignRegion(IRegion, DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext) aligned} * region. * * @param aligned * an aligned region * @param member * the member to remember * @return a folding position corresponding to <code>aligned</code> */ protected final Position createMemberPosition(IRegion aligned, IMember member) { return new ScriptElementPosition(aligned.getOffset(), aligned .getLength()); } @Override protected void initializePreferences(IPreferenceStore store) { m_CollapseInnerTypes = store .getBoolean(VjetPreferenceConstants.EDITOR_FOLDING_INNERTYPES); m_CollapseImportContainer = store .getBoolean(VjetPreferenceConstants.EDITOR_FOLDING_IMPORTS); m_CollapseJavadoc= store.getBoolean(VjetPreferenceConstants.EDITOR_FOLDING_JAVADOC); m_CollapseMembers = store .getBoolean(VjetPreferenceConstants.EDITOR_FOLDING_METHODS); m_CollapseHeaderComments = store .getBoolean(VjetPreferenceConstants.EDITOR_FOLDING_HEADERS); super.initializePreferences(store); } @Override protected CodeBlock[] getCodeBlocks(String code, int offset) { // ISourceParser parser = getSourceParser(); // IResource resource = getInputElement().getResource(); // // ModuleDeclaration decl = parser.parse(resource.getFullPath().toString() // .toCharArray(), code.toCharArray(), null); // return buildCodeBlocks(decl, offset); return null;// do not need parse } protected final class FoldingStructureComputationContext2 extends FoldingStructureComputationContext { public FoldingStructureComputationContext2(IDocument document, ProjectionAnnotationModel model, boolean allowCollapsing) { super(document, model, allowCollapsing); } private IType fFirstType; private boolean fHasHeaderComment; private IScanner fScanner; private void setFirstType(IType type) { if (hasFirstType()) throw new IllegalStateException(); fFirstType= type; } boolean hasFirstType() { return fFirstType != null; } private IType getFirstType() { return fFirstType; } private boolean hasHeaderComment() { return fHasHeaderComment; } private void setHasHeaderComment() { fHasHeaderComment= true; } private IScanner getScanner() { if (fScanner == null) fScanner= new PublicScanner(true /*comment*/, false /*whitespace*/, false /*nls*/, 4 /*sourceLevel*/, null/*taskTag*/, null/*taskPriorities*/, true /*taskCaseSensitive*/); return fScanner; // return null; } /** * 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); } /** * Returns <code>true</code> if header comments should be collapsed. * * @return <code>true</code> if header comments should be collapsed */ public boolean collapseHeaderComments() { return allowCollapsing() && m_CollapseHeaderComments; } /** * Returns <code>true</code> if import containers should be collapsed. * * @return <code>true</code> if import containers should be collapsed */ public boolean collapseImportContainer() { return allowCollapsing() && m_CollapseImportContainer; } /** * Returns <code>true</code> if inner types should be collapsed. * * @return <code>true</code> if inner types should be collapsed */ public boolean collapseInnerTypes() { return allowCollapsing() && m_CollapseInnerTypes; } /** * Returns <code>true</code> if javadoc comments should be collapsed. * * @return <code>true</code> if javadoc comments should be collapsed */ public boolean collapseJavadoc() { return allowCollapsing() && m_CollapseJavadoc; } /** * Returns <code>true</code> if methods should be collapsed. * * @return <code>true</code> if methods should be collapsed */ public boolean collapseMembers() { return allowCollapsing() && m_CollapseMembers; } } }