/*******************************************************************************
* Copyright (c) 2009-2011 CWI
* 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:
* * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI
* * Anya Helene Bagge - A.H.S.Bagge@cwi.nl (Univ. Bergen)
*******************************************************************************/
package org.rascalmpl.eclipse.terms;
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.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.graphics.Point;
import org.rascalmpl.eclipse.Activator;
import org.rascalmpl.eclipse.editor.highlight.ShowAsHTML;
import org.rascalmpl.eclipse.editor.highlight.ShowAsLatex;
import org.rascalmpl.eclipse.nature.RascalMonitor;
import org.rascalmpl.eclipse.nature.WarningsToErrorLog;
import org.rascalmpl.interpreter.result.ICallableValue;
import org.rascalmpl.interpreter.types.FunctionType;
import org.rascalmpl.interpreter.types.OverloadedFunctionType;
import org.rascalmpl.interpreter.types.RascalTypeFactory;
import io.usethesource.vallang.IBool;
import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IList;
import io.usethesource.vallang.ISet;
import io.usethesource.vallang.ISourceLocation;
import io.usethesource.vallang.IString;
import io.usethesource.vallang.IValue;
import io.usethesource.vallang.IValueFactory;
import io.usethesource.vallang.type.Type;
import io.usethesource.vallang.type.TypeFactory;
import org.rascalmpl.values.ValueFactoryFactory;
import org.rascalmpl.values.uptr.ITree;
import org.rascalmpl.values.uptr.ProductionAdapter;
import org.rascalmpl.values.uptr.TreeAdapter;
import io.usethesource.impulse.editor.UniversalEditor;
import io.usethesource.impulse.services.ILanguageActionsContributor;
public class ActionContributor implements ILanguageActionsContributor {
private static final class Runner extends Action {
private final UniversalEditor editor;
private final RascalAction job;
private final boolean sync;
public Runner(boolean synchronous, String label, UniversalEditor editor, RascalAction job) {
super(label);
this.editor = editor;
this.job = job;
this.sync = synchronous;
}
public Runner(boolean checked, boolean synchronous, String label, UniversalEditor editor, RascalAction job) {
super(label, Action.AS_CHECK_BOX);
setChecked(checked);
this.editor = editor;
this.job = job;
this.sync = synchronous;
}
public void run() {
Point selection = editor.getSelection();
job.init(editor);
job.schedule();
if (sync) {
try {
job.join();
if (job.result != null) {
replaceText(selection, job.result);
}
} catch (InterruptedException e) {
Activator.getInstance().logException("action interrupted", e);
}
}
}
private void replaceText(Point selection, IString newTree) {
try {
String newText = newTree.getValue();
IDocument doc = editor.getDocumentProvider().getDocument(editor.getEditorInput());
doc.replace(0, doc.getLength(), newText);
if (selection.x < doc.getLength()) {
editor.selectAndReveal(selection.x, 0);
}
} catch (BadLocationException e) {
Activator.getInstance().logException("could not replace text", e);
}
}
}
private static final class RascalAction extends Job {
private final ICallableValue func;
private final WarningsToErrorLog warnings;
private ITree tree;
private Point selection;
public IString result = null;
private RascalAction(String text, ICallableValue func) {
super(text);
this.func = func;
this.warnings = new WarningsToErrorLog();
}
public void init(UniversalEditor editor) {
this.tree = (ITree) editor.getParseController().getCurrentAst();
this.selection = editor.getSelection();
}
@Override
public IStatus run(IProgressMonitor monitor) {
RascalMonitor rascalMonitor = new RascalMonitor(monitor, warnings);
if (tree != null) {
Type[] actualTypes = new Type[] { RTF.nonTerminalType(ProductionAdapter.getType(TreeAdapter.getProduction(tree))), TF.sourceLocationType() };
ISourceLocation loc = TreeAdapter.getLocation(tree);
IValue[] actuals = new IValue[] { tree, VF.sourceLocation(loc, selection.x, selection.y)};
try {
rascalMonitor.startJob("Executing " + getName(), 10000);
IValue result;
synchronized(func.getEval()){
result = func.call(rascalMonitor, actualTypes, actuals, null).getValue();
}
if ( (func.getType() instanceof OverloadedFunctionType) && (((OverloadedFunctionType) func.getType()).getReturnType() != TF.voidType()) ) {
this.result = (IString) result;
}
if ( (func.getType() instanceof FunctionType) && (((FunctionType) func.getType()).getReturnType() != TF.voidType())) {
this.result = (IString) result;
}
}
catch (Throwable e) {
Activator.getInstance().logException("error while executing action:" + e.getMessage(), e);
}
finally {
rascalMonitor.endJob(true);
}
}
return Status.OK_STATUS;
}
}
private final static TypeFactory TF = TypeFactory.getInstance();
private final static RascalTypeFactory RTF = RascalTypeFactory.getInstance();
private final static IValueFactory VF = ValueFactoryFactory.getValueFactory();
public void contributeToEditorMenu(UniversalEditor editor,
IMenuManager menuManager) {
ISet contribs = getContribs(editor);
menuManager.add(new ShowAsHTML(editor));
menuManager.add(new ShowAsLatex(editor));
for (IValue contrib : contribs) {
IConstructor node = (IConstructor) contrib;
if (node.getName().equals("popup")) {
contribute(menuManager, editor, (IConstructor) node.get("menu"));
}
}
}
private void contribute(IMenuManager menuManager, final UniversalEditor editor, IConstructor menu) {
String label = ((IString) menu.get("label")).getValue();
if (menu.getName().equals("action") ||
menu.getName().equals("toggle") ||
menu.getName().equals("edit")) {
contributeAction(menuManager, editor, menu, label);
}
else if (menu.getName().equals("group")) {
menuManager.add(new Separator(label));
for (IValue member : (IList) menu.get("members")) {
contribute(menuManager, editor, (IConstructor) member);
}
}
else if (menu.getName().equals("menu")) {
MenuManager sub = new MenuManager(label);
menuManager.add(sub);
for (IValue member : (IList) menu.get("members")) {
contribute(sub, editor, (IConstructor) member);
}
}
}
private boolean getState(ICallableValue func) {
Type[] actualTypes = new Type[] { };
IValue[] actuals = new IValue[] { };
synchronized(func.getEval()){
func.getEval().__setInterrupt(false);
return ((IBool) func.call(actualTypes, actuals, null).getValue()).getValue();
}
}
private void contributeAction(IMenuManager menuManager,
final UniversalEditor editor, IConstructor menu, String label) {
if (menu.has("state")) { // toggle, order of evaluation is important as state also has action
final ICallableValue func = (ICallableValue) menu.get("action");
menuManager.add(new Runner(getState((ICallableValue) menu.get("state")), true, label, editor, new RascalAction(label, func)));
}
else if (menu.has("action")) {
final ICallableValue func = (ICallableValue) menu.get("action");
menuManager.add(new Runner(false, label, editor, new RascalAction(label, func)));
}
else if (menu.has("edit")) {
final ICallableValue func = (ICallableValue) menu.get("edit");
menuManager.add(new Runner(true, label, editor, new RascalAction(label, func)));
}
}
private ISet getContribs(UniversalEditor editor) {
ISet result = TermLanguageRegistry.getInstance().getContributions(editor.getLanguage());
if (result == null) {
result = ValueFactoryFactory.getValueFactory().set();
}
return result;
}
public void contributeToMenuBar(UniversalEditor editor, IMenuManager menuManager) {
ISet contribs = getContribs(editor);
for (IValue contrib : contribs) {
IConstructor node = (IConstructor) contrib;
if (node.getName().equals("menu")) {
contribute(menuManager, editor, (IConstructor) node.get("menu"));
}
}
}
public void contributeToStatusLine(UniversalEditor editor,
IStatusLineManager statusLineManager) {
}
public void contributeToToolBar(UniversalEditor editor,
IToolBarManager toolbarManager) {
}
}