/******************************************************************************* * Copyright (c) 2005, 2017 IBM Corporation 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 org.eclipse.dltk.tcl.internal.ui.text.folding; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import org.eclipse.core.runtime.ILog; import org.eclipse.dltk.ast.ASTNode; 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.expressions.Expression; import org.eclipse.dltk.ast.references.SimpleReference; import org.eclipse.dltk.ast.statements.Block; import org.eclipse.dltk.ast.statements.Statement; import org.eclipse.dltk.core.IModelElement; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.tcl.ast.ITclStatementLookLike; import org.eclipse.dltk.tcl.ast.TclStatement; import org.eclipse.dltk.tcl.ast.expressions.TclBlockExpression; import org.eclipse.dltk.tcl.core.TclNature; import org.eclipse.dltk.tcl.core.ast.IfStatement; import org.eclipse.dltk.tcl.core.ast.TclCatchStatement; import org.eclipse.dltk.tcl.core.ast.TclForStatement; import org.eclipse.dltk.tcl.core.ast.TclForeachStatement; import org.eclipse.dltk.tcl.core.ast.TclSwitchStatement; import org.eclipse.dltk.tcl.core.ast.TclWhileStatement; import org.eclipse.dltk.tcl.internal.ui.TclUI; import org.eclipse.dltk.tcl.internal.ui.text.TclPartitionScanner; import org.eclipse.dltk.tcl.ui.TclPreferenceConstants; import org.eclipse.dltk.tcl.ui.text.TclPartitions; import org.eclipse.dltk.ui.text.folding.AbstractASTFoldingStructureProvider; import org.eclipse.dltk.ui.text.folding.IElementCommentResolver; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.rules.IPartitionTokenScanner; /** */ public class TclFoldingStructureProvider extends AbstractASTFoldingStructureProvider { /* preferences */ private int fBlockFolding = 0; private List fBlockIncludeList = new ArrayList(); private List fBlockExcludeList = new ArrayList(); private boolean fInitCollapseOtherBlocks; @Override public IElementCommentResolver createElementCommentResolver( IModelElement modelElement, String contents) { return new TclElementCommentResolver((ISourceModule) modelElement, contents); } @Override protected CodeBlock[] getCodeBlocks(String code, int offset) { /* * if an ASTVisitor implementation is created for this, just override * getFoldingVisitor() and remove this method */ ModuleDeclaration md = parse(code, offset); List<ASTNode> statements = md.getStatements(); if (statements == null) { return new CodeBlock[0]; } List<CodeBlock> result = new ArrayList<>(); traverse(result, statements, offset, code); return result.toArray(new CodeBlock[result.size()]); } private void checkStatement(String code, int offset, List<CodeBlock> result, Statement sst) { if (sst instanceof TclStatement) { TclStatement statement = (TclStatement) sst; result.add(new CodeBlock(statement, new Region(offset + statement.sourceStart(), statement.sourceEnd() - statement.sourceStart()))); Iterator<ASTNode> si = statement.getExpressions().iterator(); while (si.hasNext()) { Expression ex = (Expression) si.next(); if (ex instanceof TclBlockExpression) { TclBlockExpression be = (TclBlockExpression) ex; try { String newContents = code.substring( be.sourceStart() + 1, be.sourceEnd() - 1); CodeBlock[] cb = getCodeBlocks(newContents, offset + be.sourceStart() + 1); for (int j = 0; j < cb.length; j++) { result.add(cb[j]); } } catch (StringIndexOutOfBoundsException e) { } } } } } private class TclFoldBlock extends Block { public TclFoldBlock(Block block) { super(block.sourceStart(), block.sourceEnd()); this.getStatements().addAll(block.getStatements()); } } private void traverse(List<CodeBlock> result, List<ASTNode> statements, int offset, String code) { for (Iterator<ASTNode> iterator = statements.iterator(); iterator .hasNext();) { ASTNode node = iterator.next(); if (node instanceof TclStatement) { checkStatement(code, offset, result, (Statement) node); continue; } boolean fold = false; List<ASTNode> children = null; if (node instanceof TypeDeclaration) { TypeDeclaration statement = (TypeDeclaration) node; children = statement.getStatements(); fold = true; } else if (node instanceof MethodDeclaration) { MethodDeclaration statement = (MethodDeclaration) node; children = statement.getStatements(); fold = true; } else if (node instanceof IfStatement) { fold = true; children = new ArrayList<>(); IfStatement statement = (IfStatement) node; Statement thenNode = statement.getThen(); if (thenNode instanceof Block) { children.addAll(((Block) thenNode).getStatements()); } ASTNode elseNode = statement.getElse(); if (elseNode instanceof Block) { children.addAll(((Block) elseNode).getStatements()); } } else if (node instanceof TclCatchStatement) { fold = true; TclCatchStatement statement = (TclCatchStatement) node; children = statement.getStatements(); } else if (node instanceof TclSwitchStatement) { fold = true; TclSwitchStatement statement = (TclSwitchStatement) node; Block alts = statement.getAlternatives(); if (alts != null) { List<ASTNode> childs = alts.getStatements(); children = new ArrayList<>(); for (int i = 0; i < childs.size(); i++) { ASTNode child = childs.get(i); if (child instanceof Block) { result.add(new CodeBlock( new TclFoldBlock((Block) child), new Region(offset + child.sourceStart(), child.sourceEnd() - child.sourceStart()))); children.addAll(((Block) child).getStatements()); } } } } else if (node instanceof TclWhileStatement) { fold = true; TclWhileStatement statement = (TclWhileStatement) node; Block block = statement.getBlock(); if (block != null) { children = block.getStatements(); } } else if (node instanceof TclForeachStatement) { fold = true; TclForeachStatement statement = (TclForeachStatement) node; Block block = statement.getBlock(); if (block != null) { children = block.getStatements(); } } else if (node instanceof TclForStatement) { fold = true; TclForStatement statement = (TclForStatement) node; Block block = statement.getBlock(); if (block != null) { children = block.getStatements(); } } if (fold) { result.add(new CodeBlock(node, new Region(offset + node.sourceStart(), node.sourceEnd() - node.sourceStart()))); } if (children != null && children.size() > 0) { traverse(result, children, offset, code); } } } @Override protected String getCommentPartition() { return TclPartitions.TCL_COMMENT; } @Override protected ILog getLog() { return TclUI.getDefault().getLog(); } @Override protected String getPartition() { return TclPartitions.TCL_PARTITIONING; } @Override protected IPartitionTokenScanner getPartitionScanner() { return new TclPartitionScanner(); } @Override protected String[] getPartitionTypes() { return TclPartitions.TCL_PARTITION_TYPES; } @Override protected String getNatureId() { return TclNature.NATURE_ID; } @Override protected void initializePreferences(IPreferenceStore store) { super.initializePreferences(store); fInitCollapseOtherBlocks = store .getBoolean(TclPreferenceConstants.EDITOR_FOLDING_INIT_OTHER); fBlockFolding = store .getInt(TclPreferenceConstants.EDITOR_FOLDING_BLOCKS); fBlockExcludeList = initializeList(store, TclPreferenceConstants.EDITOR_FOLDING_EXCLUDE_LIST); fBlockIncludeList = initializeList(store, TclPreferenceConstants.EDITOR_FOLDING_INCLUDE_LIST); } @Override protected String getInitiallyFoldClassesKey() { return TclPreferenceConstants.EDITOR_FOLDING_INIT_NAMESPACES; } @Override protected String getInitiallyFoldMethodsKey() { return TclPreferenceConstants.EDITOR_FOLDING_INIT_BLOCKS; } @Override protected boolean initiallyCollapse(ASTNode s) { if (s instanceof TclStatement || s instanceof ITclStatementLookLike) { TclStatement statement = null; if (s instanceof ITclStatementLookLike) { statement = ((ITclStatementLookLike) s).getStatement(); } else { statement = (TclStatement) s; } if (statement == null) { return false; } if (!(statement.getAt(0) instanceof SimpleReference)) { return false; } String name = null; name = ((SimpleReference) statement.getAt(0)).getName(); if (name.equals("namespace")) { return fInitCollapseClasses; } } if (mayCollapse(s)) { return fInitCollapseOtherBlocks; } return super.initiallyCollapse(s); } private enum CommentType { SHEBANG, HEADER } private static class CommentRegion extends Region { final CommentType commentType; public CommentRegion(CommentType commentType, int offset, int length) { super(offset, length); this.commentType = commentType; } /** * @param commentType * @param region */ public CommentRegion(CommentType commentType, IRegion region) { super(region.getOffset(), region.getLength()); this.commentType = commentType; } } @Override protected void prepareRegions(Document d, List<IRegion> regions) { if (regions.isEmpty()) { return; } IRegion region = regions.get(0); if (region.getOffset() != 0) { return; } try { String content = d.get(region.getOffset(), region.getLength()); if (!content.startsWith("#!")) { return; } String line0 = d.get(d.getLineOffset(0), d.getLineLength(0)); if (Pattern.compile("/(sh|bash|dash|tcsh)").matcher(line0).find()) { int i = 1; for (;;) { int offset = d.getLineOffset(i); final int length = d.getLineLength(i); String line = d.get(offset, length).trim(); if (!line.startsWith("#") && !line.endsWith("\\")) { break; } final String delimiter = d.getLineDelimiter(i); if (delimiter != null) { offset += delimiter.length(); } if (offset + length >= region.getLength()) { break; } ++i; } final IRegion line = d.getLineInformation(i); IRegion shebang = new CommentRegion(CommentType.SHEBANG, 0, line.getOffset() + line.getLength()); int headerOffset = line.getOffset() + d.getLineLength(i); final CommentRegion header = new CommentRegion( CommentType.HEADER, headerOffset, region.getLength() - headerOffset); regions.set(0, shebang); if (isMultilineRegion(d, header)) { regions.add(1, header); } } } catch (BadLocationException e) { // return } } @Override protected IRegion alignRegion(IRegion region, FoldingStructureComputationContext ctx) { final IRegion result = super.alignRegion(region, ctx); if (result != region && region instanceof CommentRegion) { return new CommentRegion(((CommentRegion) region).commentType, result); } return result; } @Override protected boolean initiallyCollapseComments(IRegion region, FoldingStructureComputationContext ctx) { if (ctx.allowCollapsing()) { if (region instanceof CommentRegion) { if (((CommentRegion) region).commentType == CommentType.SHEBANG) { return false; } } return isHeaderRegion(region, ctx) ? fInitCollapseHeaderComments : fInitCollapseComments; } return false; } @Override protected boolean isHeaderRegion(IRegion region, FoldingStructureComputationContext ctx) { if (region instanceof CommentRegion) { return ((CommentRegion) region).commentType == CommentType.HEADER; } return super.isHeaderRegion(region, ctx); } protected boolean canFold(String name) { switch (fBlockFolding) { case TclPreferenceConstants.EDITOR_FOLDING_BLOCKS_OFF: { if (name.equals("proc") || name.equals("namespace")) { return true; } return false; } case TclPreferenceConstants.EDITOR_FOLDING_BLOCKS_INCLUDE: { if (fBlockIncludeList.contains(name)) { return true; } return false; } case TclPreferenceConstants.EDITOR_FOLDING_BLOCKS_EXCLUDE: { if (fBlockExcludeList.contains(name)) { return false; } return true; } } return false; } @Override protected boolean mayCollapse(ASTNode s, FoldingStructureComputationContext ctx) { return mayCollapse(s); } protected boolean mayCollapse(ASTNode s) { if (s instanceof TypeDeclaration) { return canFold("namespace"); } else if (s instanceof MethodDeclaration) { return canFold("proc"); } else if (s instanceof IfStatement) { return canFold("if"); } else if (s instanceof TclSwitchStatement) { return canFold("switch"); } else if (s instanceof TclFoldBlock) { return canFold("switch"); } else if (s instanceof TclCatchStatement) { return canFold("catch"); } else if (s instanceof TclWhileStatement) { return canFold("while"); } else if (s instanceof TclForeachStatement) { return canFold("foreach"); } else if (s instanceof TclForStatement) { return canFold("for"); } else if (s instanceof TclStatement) { TclStatement statement = (TclStatement) s; if (!(statement.getAt(0) instanceof SimpleReference)) { return false; } String name = null; name = ((SimpleReference) statement.getAt(0)).getName(); return canFold(name); } return false; } private List<String> initializeList(IPreferenceStore store, String key) { String t = store.getString(key); String[] items = t.split(","); //$NON-NLS-1$ List<String> list = new ArrayList<>(items.length); for (int i = 0; i < items.length; i++) { if (items[i].trim().length() > 0) { list.add(items[i]); } } return list; } }