/******************************************************************************* * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. 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 com.cisco.yangide.editor.editors.text; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPositionCategoryException; 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.IToken; import org.eclipse.jface.text.source.Annotation; 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 com.cisco.yangide.core.dom.ASTNode; import com.cisco.yangide.core.dom.ASTVisitor; import com.cisco.yangide.core.dom.Module; import com.cisco.yangide.editor.YangEditorPlugin; import com.cisco.yangide.editor.editors.YangEditor; import com.cisco.yangide.editor.editors.YangPartitionScanner; /** * @author Alexey Kholupko */ @SuppressWarnings({"rawtypes", "unchecked"}) public class YangFoldingStructureProvider { private YangEditor fEditor; private IDocument fDocument; /** * A mapping of the foldable position to the * <code>AntElementNode<code> that represent that region */ private Map fPositionToElement = new HashMap(); public YangFoldingStructureProvider(YangEditor editor) { fEditor = editor; } private void updateFoldingRegions(ProjectionAnnotationModel model, List currentRegions) { Annotation[] deletions = computeDifferences(model, currentRegions); if (currentRegions.isEmpty()) { return; } Map additionsMap = new HashMap(); Position headerCommentCandidate = (Position) currentRegions.get(0); ProjectionAnnotation headerCommentCandidateAnnotation = null; if(headerCommentCandidate != null){ headerCommentCandidateAnnotation = new ProjectionAnnotation(false); additionsMap.put(headerCommentCandidateAnnotation, headerCommentCandidate); } for (int i = 1; i < currentRegions.size(); i++) { Position position = (Position) currentRegions.get(i); additionsMap.put(new ProjectionAnnotation(false), position); } if ((deletions.length != 0 || additionsMap.size() != 0)) { model.modifyAnnotations(deletions, additionsMap, new Annotation[] {}); } if (isHeaderComment(headerCommentCandidate)) { model.collapse(headerCommentCandidateAnnotation); } } /** * @param headerCommentCandidate * @return */ private boolean isHeaderComment(Position headerCommentCandidate) { if(headerCommentCandidate == null) { return false; } YangPartitionScanner scanner = new YangPartitionScanner(); scanner.setRange(fDocument, 0, fDocument.getLength()); try { // first token must be YANG_COMMENT String contentType = null; IToken token; do { token = scanner.nextToken(); contentType = getTokenContentType(token); } while (contentType == null && !token.isEOF()); if (contentType != null && contentType.equals(YangPartitionScanner.YANG_COMMENT)) { int tokenStartLine = fDocument.getLineOfOffset(scanner.getTokenOffset()); int tokenEndLine = fDocument.getLineOfOffset(scanner.getTokenOffset() + scanner.getTokenLength()); int start = fDocument.getLineOffset(tokenStartLine); int end = fDocument.getLineOffset(tokenEndLine) + fDocument.getLineLength(tokenEndLine); Position tokenPosition = new Position(start, end - start); if (headerCommentCandidate.equals(tokenPosition)) { return true; } } } catch (BadLocationException e) { YangEditorPlugin.log(e); } return false; } private Annotation[] computeDifferences(ProjectionAnnotationModel model, List currentRegions) { List deletions = new ArrayList(); for (Iterator iter = model.getAnnotationIterator(); iter.hasNext();) { Object annotation = iter.next(); if (annotation instanceof ProjectionAnnotation) { Position position = model.getPosition((Annotation) annotation); if (currentRegions.contains(position)) { currentRegions.remove(position); } else { deletions.add(annotation); } } } return (Annotation[]) deletions.toArray(new Annotation[deletions.size()]); } public void updateFoldingRegions(Module yangModule) { if (yangModule != null) { fPositionToElement = new HashMap(); try { ProjectionAnnotationModel model = (ProjectionAnnotationModel) fEditor .getAdapter(ProjectionAnnotationModel.class); if (model == null) { return; } final List root = new ArrayList(); yangModule.accept(new ASTVisitor() { @Override public void preVisit(ASTNode node) { root.add(node); } }); List currentRegions = new ArrayList<>(); // order is important, to know about header comment addFoldingNonASTregions(currentRegions); addFoldingRegions(currentRegions, root); updateFoldingRegions(model, currentRegions); } catch (BadLocationException be) { // ignore as document has changed } } } private void addFoldingNonASTregions(List currentRegions) { // litle hack here, because of FastPartitioner odd privacy String[] categories = fDocument.getPositionCategories(); for (String category : categories) { if (category.startsWith("__content_types_category")) { try { Position[] positions = fDocument.getPositions(category); for (Position position : positions) { int positionOffset = position.getOffset(); int positionLength = position.getLength(); // for single line comment - EndOfLineRule if (fDocument.getChar(positionOffset + position.getLength() - 1) == '\n') { positionLength--; } int startLine = fDocument.getLineOfOffset(positionOffset); int endLine = fDocument.getLineOfOffset(positionOffset + positionLength); if (startLine < endLine) { int start = fDocument.getLineOffset(startLine); int end = fDocument.getLineOffset(endLine) + fDocument.getLineLength(endLine); Position foldingPosition = // new Position(start, end - start); new CommentPosition(start, end - start); currentRegions.add(foldingPosition); // fPositionToElement.put(foldingPosition, element); } } } catch (BadPositionCategoryException | BadLocationException e) { YangEditorPlugin.log(e); } } } } protected String getTokenContentType(IToken token) { Object data = token.getData(); if (data instanceof String) { return (String) data; } return null; } private void addFoldingRegions(List currentRegions, List children) throws BadLocationException { // add a Position to 'regions' for each foldable region Iterator iter = children.iterator(); while (iter.hasNext()) { ASTNode element = (ASTNode) iter.next(); // if (element.getImportNode() != null || element.isExternal()) { // continue; //elements are not really in this document and therefore are not foldable // } int startLine = fDocument.getLineOfOffset(element.getStartPosition()); int endLine = fDocument.getLineOfOffset(element.getStartPosition() + element.getLength()); if (startLine < endLine) { int start = fDocument.getLineOffset(startLine); int end = fDocument.getLineOffset(endLine) + fDocument.getLineLength(endLine); Position position = new Position(start, end - start); currentRegions.add(position); fPositionToElement.put(position, element); } // List childNodes= element.getChildNodes(); // if (childNodes != null) { // addFoldingRegions(regions, childNodes); // } } } public void setDocument(IDocument document) { fDocument = document; } /** * 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) { // 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); int postLength = offset + length - postOffset; if (postLength > 0) { IRegion postRegion = new Region(postOffset, postLength); 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 * @param prefixEnd the end of the prefix * @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) throws BadLocationException { DocumentCharacterIterator sequence = new DocumentCharacterIterator(document, offset, offset + length); return findFirstContent(sequence, 0); } } }