| /******************************************************************************* |
| * Copyright (c) 2005, 2017 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.ruby.internal.ui.text.folding; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Stack; |
| |
| import org.eclipse.core.runtime.ILog; |
| import org.eclipse.dltk.ast.ASTNode; |
| import org.eclipse.dltk.ast.declarations.Declaration; |
| 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.CallExpression; |
| import org.eclipse.dltk.ruby.ast.FakeModuleDeclaration; |
| import org.eclipse.dltk.ruby.core.RubyConstants; |
| import org.eclipse.dltk.ruby.core.RubyNature; |
| import org.eclipse.dltk.ruby.internal.ui.RubyPreferenceConstants; |
| import org.eclipse.dltk.ruby.internal.ui.RubyUI; |
| import org.eclipse.dltk.ruby.internal.ui.text.IRubyPartitions; |
| import org.eclipse.dltk.ruby.internal.ui.text.RubyPartitionScanner; |
| import org.eclipse.dltk.ui.text.folding.AbstractASTFoldingStructureProvider; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.jface.text.rules.IPartitionTokenScanner; |
| |
| public class RubyFoldingStructureProvider |
| extends AbstractASTFoldingStructureProvider { |
| |
| @Override |
| protected String getCommentPartition() { |
| return IRubyPartitions.RUBY_COMMENT; |
| } |
| |
| @Override |
| protected String getDocPartition() { |
| return IRubyPartitions.RUBY_DOC; |
| } |
| |
| @Override |
| protected String getPartition() { |
| return IRubyPartitions.RUBY_PARTITIONING; |
| } |
| |
| @Override |
| protected IPartitionTokenScanner getPartitionScanner() { |
| return new RubyPartitionScanner(); |
| } |
| |
| @Override |
| protected String[] getPartitionTypes() { |
| return IRubyPartitions.RUBY_PARTITION_TYPES; |
| } |
| |
| @Override |
| protected String getNatureId() { |
| return RubyNature.NATURE_ID; |
| } |
| |
| @Override |
| protected ILog getLog() { |
| return RubyUI.getDefault().getLog(); |
| } |
| |
| @Override |
| protected CodeBlock[] getCodeBlocks(String code, int offset) { |
| ModuleDeclaration decl = parse(code, offset); |
| if (decl instanceof FakeModuleDeclaration) { |
| return null; |
| } |
| return buildCodeBlocks(decl, offset); |
| } |
| |
| @Override |
| protected boolean mayCollapse(ASTNode s, |
| FoldingStructureComputationContext ctx) { |
| return super.mayCollapse(s, ctx) || s instanceof CallExpression; |
| } |
| |
| @Override |
| protected boolean initiallyCollapse(ASTNode s) { |
| return super.initiallyCollapse(s) |
| || (s instanceof CallExpression && fInitCollapseRequires); |
| } |
| |
| private boolean fInitCollapseRequires; |
| |
| @Override |
| protected void initializePreferences(IPreferenceStore store) { |
| super.initializePreferences(store); |
| fInitCollapseRequires = store.getBoolean( |
| RubyPreferenceConstants.EDITOR_FOLDING_INIT_REQUIRES); |
| } |
| |
| /** |
| * This folding visitor implementation intentionally does not fold top level |
| * classes, but methods and inner classes are folded. This behavior is |
| * similar to the JDT. |
| */ |
| protected static class RubyFoldingASTVisitor extends FoldingASTVisitor { |
| |
| static class DeclarationContainer { |
| final List<Object> children = new ArrayList<>(); |
| final Declaration declaration; |
| final boolean foldAlways; |
| |
| public DeclarationContainer(Declaration declaration, |
| boolean foldAlways) { |
| this.declaration = declaration; |
| this.foldAlways = foldAlways; |
| } |
| |
| void addChild(DeclarationContainer child) { |
| children.add(child); |
| } |
| |
| int countChildren() { |
| return children.size(); |
| } |
| |
| @Override |
| public String toString() { |
| return declaration != null ? declaration.toString() : "(TOP)"; //$NON-NLS-1$ |
| } |
| |
| } |
| |
| static class ModuleDeclarationContainer extends DeclarationContainer { |
| |
| final List<CallExpression> requires = new ArrayList<>(); |
| |
| public ModuleDeclarationContainer() { |
| super(null, false); |
| } |
| |
| public void addChild(CodeBlock block) { |
| children.add(block); |
| } |
| |
| } |
| |
| private final Stack<DeclarationContainer> declarations = new Stack<>(); |
| |
| private DeclarationContainer peekDeclaration() { |
| return declarations.peek(); |
| } |
| |
| private DeclarationContainer popDeclaration() { |
| return declarations.pop(); |
| } |
| |
| private ModuleDeclarationContainer peekModuleDeclaration() { |
| if (declarations.size() == 1) { |
| DeclarationContainer container = declarations.peek(); |
| if (container instanceof ModuleDeclarationContainer) { |
| return (ModuleDeclarationContainer) container; |
| } |
| } |
| return null; |
| } |
| |
| protected RubyFoldingASTVisitor(int offset) { |
| super(offset); |
| } |
| |
| @Override |
| public boolean visit(ModuleDeclaration s) throws Exception { |
| declarations.push(new ModuleDeclarationContainer()); |
| return visitGeneral(s); |
| } |
| |
| @Override |
| public boolean visit(TypeDeclaration s) throws Exception { |
| handleRequireStatements(); |
| final DeclarationContainer child = new DeclarationContainer(s, |
| false); |
| peekDeclaration().addChild(child); |
| declarations.push(child); |
| return visitGeneral(s); |
| } |
| |
| @Override |
| public boolean endvisit(TypeDeclaration s) throws Exception { |
| declarations.pop(); |
| return super.endvisit(s); |
| } |
| |
| @Override |
| public boolean visit(MethodDeclaration s) throws Exception { |
| handleRequireStatements(); |
| final DeclarationContainer child = new DeclarationContainer(s, |
| true); |
| peekDeclaration().addChild(child); |
| declarations.push(child); |
| return visitGeneral(s); |
| } |
| |
| @Override |
| public boolean endvisit(MethodDeclaration s) throws Exception { |
| declarations.pop(); |
| return super.endvisit(s); |
| } |
| |
| private void processDeclarations(DeclarationContainer container, |
| int level, boolean collapsible) { |
| if (container.declaration != null |
| && (collapsible || container.foldAlways)) { |
| add(container.declaration); |
| } |
| final boolean nextCollabsible = collapsible |
| || (level > 0 && container.countChildren() > 1); |
| for (Iterator<?> i = container.children.iterator(); i.hasNext();) { |
| final Object child = i.next(); |
| if (child instanceof DeclarationContainer) { |
| processDeclarations((DeclarationContainer) child, level + 1, |
| nextCollabsible); |
| } else if (child instanceof CodeBlock) { |
| add((CodeBlock) child); |
| } |
| } |
| } |
| |
| @Override |
| public boolean endvisit(ModuleDeclaration s) throws Exception { |
| handleRequireStatements(); |
| final DeclarationContainer container = popDeclaration(); |
| processDeclarations(container, 0, false); |
| return super.endvisit(s); |
| } |
| |
| @Override |
| public boolean visitGeneral(ASTNode node) throws Exception { |
| if (declarations.size() == 1) { |
| if (node instanceof CallExpression) { |
| final CallExpression call = (CallExpression) node; |
| if (RubyConstants.REQUIRE.equals(call.getName())) { |
| final ModuleDeclarationContainer container = peekModuleDeclaration(); |
| if (container != null) { |
| container.requires.add(call); |
| } |
| return false; |
| } |
| } else { |
| handleRequireStatements(); |
| } |
| } |
| return super.visitGeneral(node); |
| } |
| |
| private void handleRequireStatements() { |
| final ModuleDeclarationContainer container = peekModuleDeclaration(); |
| if (container != null && !container.requires.isEmpty()) { |
| final CallExpression firstRequire = container.requires.get(0); |
| final CallExpression lastRequire = container.requires |
| .get(container.requires.size() - 1); |
| container.addChild(new CodeBlock(firstRequire, new Region( |
| firstRequire.sourceStart(), |
| lastRequire.sourceEnd() - firstRequire.sourceStart()))); |
| container.requires.clear(); |
| } |
| } |
| |
| } |
| |
| @Override |
| protected FoldingASTVisitor getFoldingVisitor(int offset) { |
| return new RubyFoldingASTVisitor(offset); |
| } |
| } |