blob: 257e6e66c25e4d6145730182a95e7739a9f1663a [file] [log] [blame]
/*******************************************************************************
* 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);
}
}