/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package org.python.pydev.outline;
import java.util.ArrayList;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.widgets.Display;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.PyEdit;
import org.python.pydev.editor.model.IModelListener;
import org.python.pydev.parser.ErrorDescription;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.Attribute;
import org.python.pydev.parser.jython.ast.ClassDef;
import org.python.pydev.parser.jython.ast.FunctionDef;
import org.python.pydev.parser.jython.ast.Import;
import org.python.pydev.parser.jython.ast.ImportFrom;
import org.python.pydev.parser.jython.ast.aliasType;
import org.python.pydev.parser.visitors.scope.ASTEntryWithChildren;
import org.python.pydev.parser.visitors.scope.OutlineCreatorVisitor;
/**
* ParsedModel represents a python file, parsed for OutlineView display
* It takes PyParser, and converts it into a tree of ParsedItems
*/
public class ParsedModel implements IOutlineModel {
PyEdit editor;
PyOutlinePage outline;
IModelListener modelListener;
ParsedItem root = null; // A list of top nodes in this document. Used as a tree root
/**
* @param outline - If not null, view to notify when parser changes
*/
public ParsedModel(PyOutlinePage outline, PyEdit editor) {
this.editor = editor;
this.outline = outline;
// The notifications are only propagated to the outline page
//
// Tell parser that we want to know about all the changes
// make sure that the changes are propagated on the main thread
modelListener = new IModelListener() {
public void modelChanged(final SimpleNode ast) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
synchronized (this) {
OutlineCreatorVisitor visitor = OutlineCreatorVisitor.create(ast);
setRoot(new ParsedItem(visitor.getAll().toArray(new ASTEntryWithChildren[0]),
ParsedModel.this.editor.getErrorDescription()));
}
}
});
}
public void errorChanged(final ErrorDescription errorDesc) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
synchronized (this) {
ParsedItem currRoot = getRoot();
ParsedItem newRoot;
if (currRoot != null) {
newRoot = new ParsedItem(currRoot.getAstChildrenEntries(), errorDesc);
} else {
newRoot = new ParsedItem(new ASTEntryWithChildren[0], errorDesc);
}
setRoot(newRoot);
}
}
});
}
};
OutlineCreatorVisitor visitor = OutlineCreatorVisitor.create(editor.getAST());
root = new ParsedItem(visitor.getAll().toArray(new ASTEntryWithChildren[0]), editor.getErrorDescription());
editor.addModelListener(modelListener);
}
public void dispose() {
editor.removeModelListener(modelListener);
}
public ParsedItem getRoot() {
return root;
}
// patchRootHelper makes oldItem just like the newItem
// the differnce between the two is
private void patchRootHelper(ParsedItem oldItem, ParsedItem newItem, ArrayList<ParsedItem> itemsToRefresh,
ArrayList<ParsedItem> itemsToUpdate) {
ParsedItem[] newChildren = newItem.getChildren();
ParsedItem[] oldChildren = oldItem.getChildren();
// stuctural change, different number of children, can stop recursion
if (newChildren.length != oldChildren.length) {
//at this point, it'll recalculate the children...
oldItem.updateTo(newItem);
itemsToRefresh.add(oldItem);
} else {
// Number of children is the same, fix up all the children
for (int i = 0; i < oldChildren.length; i++) {
patchRootHelper(oldChildren[i], newChildren[i], itemsToRefresh, itemsToUpdate);
}
// see if the node needs redisplay
String oldTitle = oldItem.toString();
String newTitle = newItem.toString();
if (!oldTitle.equals(newTitle)) {
itemsToUpdate.add(oldItem);
} else {
ASTEntryWithChildren astThisOld = oldItem.getAstThis();
ASTEntryWithChildren astThisNew = newItem.getAstThis();
if (astThisOld != null && astThisNew != null && astThisOld.node != null && astThisNew.node != null
&& astThisOld.node.getClass() != astThisNew.node.getClass()) {
itemsToUpdate.add(oldItem);
}
}
oldItem.setAstThis(newItem.getAstThis());
oldItem.setErrorDesc(newItem.getErrorDesc());
}
}
/**
* Replaces current root
*/
public void setRoot(ParsedItem newRoot) {
// We'll try to do the 'least flicker replace'
// compare the two root structures, and tell outline what to refresh
try {
if (root != null) {
ArrayList<ParsedItem> itemsToRefresh = new ArrayList<ParsedItem>();
ArrayList<ParsedItem> itemsToUpdate = new ArrayList<ParsedItem>();
patchRootHelper(root, newRoot, itemsToRefresh, itemsToUpdate);
if (outline != null) {
if (outline.isDisposed()) {
return;
}
//to update
int itemsToUpdateSize = itemsToUpdate.size();
if (itemsToUpdateSize > 0) {
outline.updateItems(itemsToUpdate.toArray(new ParsedItem[itemsToUpdateSize]));
}
//to refresh
int itemsToRefreshSize = itemsToRefresh.size();
if (itemsToRefreshSize > 0) {
outline.refreshItems(itemsToRefresh.toArray(new ParsedItem[itemsToRefreshSize]));
}
}
} else {
Log.log("No old model root?");
}
} catch (Throwable e) {
Log.log(e);
}
}
public SimpleNode[] getSelectionPosition(StructuredSelection sel) {
if (sel.size() == 1) { // only sync the editing view if it is a single-selection
Object firstElement = sel.getFirstElement();
ASTEntryWithChildren p = ((ParsedItem) firstElement).getAstThis();
if (p == null) {
return null;
}
SimpleNode node = p.node;
if (node instanceof ClassDef) {
ClassDef def = (ClassDef) node;
node = def.name;
} else if (node instanceof Attribute) {
Attribute attribute = (Attribute) node;
node = attribute.attr;
} else if (node instanceof FunctionDef) {
FunctionDef def = (FunctionDef) node;
node = def.name;
} else if (node instanceof Import) {
ArrayList<SimpleNode> ret = new ArrayList<SimpleNode>();
Import importToken = (Import) node;
for (int i = 0; i < importToken.names.length; i++) {
aliasType aliasType = importToken.names[i];
//as ...
if (aliasType.asname != null) {
ret.add(aliasType.asname);
}
ret.add(aliasType.name);
}
return ret.toArray(new SimpleNode[0]);
} else if (node instanceof ImportFrom) {
ArrayList<SimpleNode> ret = new ArrayList<SimpleNode>();
ImportFrom importToken = (ImportFrom) node;
boolean found = false;
for (int i = 0; i < importToken.names.length; i++) {
found = true;
aliasType aliasType = importToken.names[i];
//as ...
if (aliasType.asname != null) {
ret.add(aliasType.asname);
}
ret.add(aliasType.name);
}
if (!found) {
ret.add(importToken.module);
}
return ret.toArray(new SimpleNode[0]);
}
return new SimpleNode[] { node };
}
return null;
}
}