/******************************************************************************* * Copyright (c) 2010 xored software, 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 * * Contributors: * xored software, Inc. - initial API and Implementation (Alex Panchenko) *******************************************************************************/ package org.eclipse.dltk.javascript.internal.ui.text.folding; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; import org.eclipse.dltk.annotations.Internal; import org.eclipse.dltk.ast.ASTNode; import org.eclipse.dltk.ast.parser.IModuleDeclaration; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.IMethod; import org.eclipse.dltk.core.IModelElement; import org.eclipse.dltk.core.IModelElementVisitor; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.ISourceRange; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.SourceParserUtil; import org.eclipse.dltk.javascript.ast.AbstractNavigationVisitor; import org.eclipse.dltk.javascript.ast.BinaryOperation; import org.eclipse.dltk.javascript.ast.Expression; import org.eclipse.dltk.javascript.ast.FunctionStatement; import org.eclipse.dltk.javascript.ast.IVariableStatement; import org.eclipse.dltk.javascript.ast.Method; import org.eclipse.dltk.javascript.ast.ObjectInitializer; import org.eclipse.dltk.javascript.ast.Script; import org.eclipse.dltk.javascript.ast.StringLiteral; import org.eclipse.dltk.javascript.ast.VariableDeclaration; import org.eclipse.dltk.javascript.ast.VariableStatement; import org.eclipse.dltk.javascript.ast.XmlLiteral; import org.eclipse.dltk.javascript.parser.JSParser; import org.eclipse.dltk.javascript.parser.JavaScriptParser; import org.eclipse.dltk.javascript.parser.PropertyExpressionUtils; import org.eclipse.dltk.ui.PreferenceConstants; import org.eclipse.dltk.ui.text.folding.IFoldingBlockProvider; import org.eclipse.dltk.ui.text.folding.IFoldingBlockRequestor; import org.eclipse.dltk.ui.text.folding.IFoldingContent; import org.eclipse.jface.preference.IPreferenceStore; public class JavaScriptCodeFoldingBlockProvider extends AbstractNavigationVisitor<Object> implements IFoldingBlockProvider { private static Script parse(IFoldingContent content) { if (content.getModelElement() instanceof ISourceModule) { IModuleDeclaration declaration = SourceParserUtil.parse( (ISourceModule) content.getModelElement(), null); if (declaration instanceof Script) { return (Script) declaration; } } JavaScriptParser parser = new JavaScriptParser(); return parser.parse(content, null); } private boolean collapseMethods; private boolean collapseObjectInitializers; private boolean collapseXml; private boolean collapseStrings; private int fBlockLinesMin; public void initializePreferences(IPreferenceStore preferenceStore) { collapseMethods = preferenceStore .getBoolean(PreferenceConstants.EDITOR_FOLDING_INIT_METHODS); fBlockLinesMin = preferenceStore .getInt(PreferenceConstants.EDITOR_FOLDING_LINES_LIMIT); } public int getMinimalLineCount() { return fBlockLinesMin; } private IFoldingBlockRequestor requestor; public void setRequestor(IFoldingBlockRequestor requestor) { this.requestor = requestor; } @SuppressWarnings("serial") @Internal static class MethodCollector extends HashMap<Integer, IModelElement> implements IModelElementVisitor { public boolean visit(IModelElement element) { if (element instanceof IMethod) { try { final ISourceRange range = ((IMethod) element) .getSourceRange(); // end offset works for both function declarations and // expressions. put(range.getOffset() + range.getLength(), element); } catch (ModelException e) { // empty } } return true; } } private final MethodCollector methodCollector = new MethodCollector(); public void computeFoldableBlocks(IFoldingContent content) { names.clear(); final Script script = parse(content); if (script != null) { methodCollector.clear(); try { content.getModelElement().accept(methodCollector); } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } visitScript(script); names.clear(); } } @Override public Object visitFunctionStatement(FunctionStatement node) { final IModelElement element = methodCollector.get(node.end()); requestor.acceptBlock(node.sourceStart(), node.sourceEnd(), JavaScriptFoldingBlockKind.FUNCTION, element, collapseMethods); return super.visitFunctionStatement(node); } @Override protected Object visitMethod(Method method) { requestor.acceptBlock(method.sourceStart(), method.sourceEnd(), JavaScriptFoldingBlockKind.FUNCTION, null, collapseMethods); return super.visitMethod(method); } @Override public Object visitObjectInitializer(ObjectInitializer node) { if (node.isMultiline()) { final Key assignemtKey = getAssignementKey(node.getParent()); if (assignemtKey != null) { requestor.acceptBlock(node.sourceStart(), node.sourceEnd(), JavaScriptFoldingBlockKind.OBJECT_INITIALIZER, assignemtKey, collapseObjectInitializers); } } return super.visitObjectInitializer(node); } private Key getAssignementKey(ASTNode node) { if (node instanceof VariableDeclaration) { final VariableDeclaration declaration = (VariableDeclaration) node; final IVariableStatement statement = (IVariableStatement) declaration .getParent(); if (statement.getVariables().size() == 1) { return registerName(declaration.getVariableName(), declaration.getIdentifier()); } } else if (node instanceof BinaryOperation) { final BinaryOperation operation = (BinaryOperation) node; if (operation.getOperation() == JSParser.ASSIGN) { final String path = PropertyExpressionUtils.getPath(operation .getLeftExpression()); if (path != null) { return registerName(path, operation.getLeftExpression()); } } } return null; } @Override public Object visitBinaryOperation(BinaryOperation node) { if (node.getOperation() == JSParser.ASSIGN) { final Expression expression = node.getLeftExpression(); final String name = PropertyExpressionUtils.getPath(expression); if (name != null) { registerName(name, expression); } } return super.visitBinaryOperation(node); } @Override public Object visitVariableStatement(VariableStatement node) { for (VariableDeclaration declaration : node.getVariables()) { registerName(declaration.getVariableName(), declaration); } return super.visitVariableStatement(node); } private static class Key { final String name; final int occurence; public Key(String name, int occurence) { this.name = name; this.occurence = occurence; } @Override public int hashCode() { return name.hashCode() * 31 + occurence; } @Override public boolean equals(Object obj) { if (obj instanceof Key) { final Key other = (Key) obj; return occurence == other.occurence && name.equals(other.name); } return false; } } @SuppressWarnings("serial") private static class Scope extends IdentityHashMap<ASTNode, Key> { final String name; public Scope(String name) { this.name = name; } private int lastValue; Key getKey(ASTNode node) { Key key = get(node); if (key == null) { key = new Key(name, lastValue++); put(node, key); } return key; } } private final Map<String, Scope> names = new HashMap<String, Scope>(); private Key registerName(String name, ASTNode node) { Scope scope = names.get(name); if (scope == null) { scope = new Scope(name); names.put(name, scope); } return scope.getKey(node); } @Override public Object visitXmlLiteral(XmlLiteral node) { requestor.acceptBlock(node.sourceStart(), node.sourceEnd(), JavaScriptFoldingBlockKind.XML, null, collapseXml); return super.visitXmlLiteral(node); } @Override public Object visitStringLiteral(StringLiteral node) { requestor.acceptBlock(node.sourceStart(), node.sourceEnd(), JavaScriptFoldingBlockKind.MULTILINESTRING, null, collapseStrings); return super.visitStringLiteral(node); } }