/**
- * Copyright (c) 2013-2016 Angelo ZERR.
* 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:
* Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package tern.eclipse.ide.ui.views;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.navigator.CommonViewer;
import tern.eclipse.ide.internal.ui.TernUIMessages;
import tern.eclipse.ide.ui.TernUIPlugin;
import tern.server.protocol.outline.IJSNode;
import tern.server.protocol.outline.IJSNodeRoot;
import tern.server.protocol.outline.TernOutlineCollector;
/**
* Job used to refresh tern outline.
*
*/
class RefreshOutlineJob extends Job implements IOutlineProvider {
private static final int UPDATE_DELAY = 500;
private final AbstractTernOutlineView view;
private State state;
private TernOutlineCollector outline;
public RefreshOutlineJob(AbstractTernOutlineView view) {
super(TernUIMessages.refreshOutline);
super.setSystem(true);
super.setPriority(Job.SHORT);
this.view = view;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
final CommonViewer viewer = view.getCurrentViewer();
if (viewer == null) {
return Status.OK_STATUS;
}
AbstractTernContentOutlinePage page = view.getCurrentTernPage();
IFile file = page.getCurrentFile();
if (!view.isOutlineAvailable(file)) {
state = State.Unavailable;
} else {
state = State.Computing;
}
if (viewer.getInput() == null) {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
Control refreshControl = viewer.getControl();
if ((refreshControl != null) && !refreshControl.isDisposed()) {
viewer.setInput(RefreshOutlineJob.this);
}
}
});
}
if (state != State.Unavailable) {
try {
IDocument document = page.getCurrentDocument();
outline = view.loadOutline(file, document);
if (outline != null) {
state = State.Done;
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
Control refreshControl = viewer.getControl();
if ((refreshControl != null) && !refreshControl.isDisposed()) {
TreePath[] expendedPaths = null;
if (viewer instanceof TreeViewer) {
expendedPaths = ((TreeViewer) viewer).getExpandedTreePaths();
}
viewer.refresh();
if (viewer instanceof TreeViewer && expendedPaths != null) {
((TreeViewer) viewer)
.setExpandedTreePaths(toNewTreePaths(expendedPaths, outline.getRoot()));
}
}
}
});
}
} catch (Exception e) {
return new Status(Status.ERROR, TernUIPlugin.PLUGIN_ID, "Error while loading tern outline...", e);
}
}
return Status.OK_STATUS;
}
private TreePath[] toNewTreePaths(TreePath[] originExpandedPaths, IJSNode newRoot) {
List<TreePath> res = new ArrayList<TreePath>();
for (TreePath originExpanded : originExpandedPaths) {
int i = 0;
List<Object> newPathItems = new ArrayList<Object>();
IJSNode previousJSNode = null;
while (i < originExpanded.getSegmentCount()) {
Object originSegment = originExpanded.getSegment(i);
if (originSegment instanceof IJSNode) {
IJSNode originNode = (IJSNode) originSegment;
IJSNode matchingNode = null;
if (previousJSNode == null) {
if (originNode instanceof IJSNodeRoot) {
matchingNode = newRoot;
} else {
matchingNode = findSimilarChild(newRoot, originNode);
}
} else {
matchingNode = findSimilarChild(previousJSNode, originNode);
}
if (matchingNode != null) {
newPathItems.add(matchingNode);
previousJSNode = matchingNode;
}
} else {
newPathItems.add(originSegment);
}
i++;
}
res.add(new TreePath(newPathItems.toArray()));
}
return res.toArray(new TreePath[res.size()]);
}
private IJSNode findSimilarChild(IJSNode newParentNode, IJSNode originChildNode) {
IJSNode matchingNode = null;
// First search node with same name
if (originChildNode.getName() != null) {
for (IJSNode child : newParentNode.getChildren()) {
if (child.getName() != null && child.getName().equals(originChildNode.getName())) {
matchingNode = child;
}
}
}
// If not found, fail back to index
if (matchingNode == null) {
int index = originChildNode.getParent().getChildren().indexOf(originChildNode);
if (index >= 0 && newParentNode.getChildren().size() > index) {
matchingNode = newParentNode.getChildren().get(index);
}
}
return matchingNode;
}
@Override
public State getOutlineState() {
return state;
}
@Override
public IJSNode getRoot() {
return outline != null ? outline.getRoot() : null;
}
public void refreshOutline() {
if (getState() != Job.NONE) {
cancel();
}
schedule(UPDATE_DELAY);
}
}