org.eclipse.acceleo.ui.interpreter.view.InterpreterView.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.acceleo.ui.interpreter.view.InterpreterView.java

Source

/*******************************************************************************
 * Copyright (c) 2010, 2012 Obeo.
 * 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:
 *     Obeo - initial API and implementation
 *******************************************************************************/
package org.eclipse.acceleo.ui.interpreter.view;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.eclipse.acceleo.ui.interpreter.InterpreterPlugin;
import org.eclipse.acceleo.ui.interpreter.internal.IInterpreterConstants;
import org.eclipse.acceleo.ui.interpreter.internal.InterpreterImages;
import org.eclipse.acceleo.ui.interpreter.internal.InterpreterMessages;
import org.eclipse.acceleo.ui.interpreter.internal.SWTUtil;
import org.eclipse.acceleo.ui.interpreter.internal.compatibility.view.FormMessageManagerFactory;
import org.eclipse.acceleo.ui.interpreter.internal.compatibility.view.IFormMessageManager;
import org.eclipse.acceleo.ui.interpreter.internal.language.DefaultLanguageInterpreter;
import org.eclipse.acceleo.ui.interpreter.internal.language.LanguageInterpreterDescriptor;
import org.eclipse.acceleo.ui.interpreter.internal.language.LanguageInterpreterRegistry;
import org.eclipse.acceleo.ui.interpreter.internal.optional.InterpreterDependencyChecks;
import org.eclipse.acceleo.ui.interpreter.internal.optional.debug.DebugViewHelper;
import org.eclipse.acceleo.ui.interpreter.internal.view.GeneratedTextDialog;
import org.eclipse.acceleo.ui.interpreter.internal.view.InterpreterFileStorage;
import org.eclipse.acceleo.ui.interpreter.internal.view.ResultDragListener;
import org.eclipse.acceleo.ui.interpreter.internal.view.StorageEditorInput;
import org.eclipse.acceleo.ui.interpreter.internal.view.VariableContentProvider;
import org.eclipse.acceleo.ui.interpreter.internal.view.VariableDropListener;
import org.eclipse.acceleo.ui.interpreter.internal.view.VariableLabelProvider;
import org.eclipse.acceleo.ui.interpreter.internal.view.actions.ClearExpressionViewerAction;
import org.eclipse.acceleo.ui.interpreter.internal.view.actions.ClearResultViewerAction;
import org.eclipse.acceleo.ui.interpreter.internal.view.actions.ClearVariableViewerAction;
import org.eclipse.acceleo.ui.interpreter.internal.view.actions.DeleteVariableOrValueAction;
import org.eclipse.acceleo.ui.interpreter.internal.view.actions.EvaluateAction;
import org.eclipse.acceleo.ui.interpreter.internal.view.actions.LinkWithEditorContextAction;
import org.eclipse.acceleo.ui.interpreter.internal.view.actions.NewVariableAction;
import org.eclipse.acceleo.ui.interpreter.internal.view.actions.NewVariableWizardAction;
import org.eclipse.acceleo.ui.interpreter.internal.view.actions.RenameVariableAction;
import org.eclipse.acceleo.ui.interpreter.internal.view.actions.ToggleRealTimeAction;
import org.eclipse.acceleo.ui.interpreter.internal.view.actions.ToggleVariableVisibilityAction;
import org.eclipse.acceleo.ui.interpreter.language.AbstractLanguageInterpreter;
import org.eclipse.acceleo.ui.interpreter.language.CompilationResult;
import org.eclipse.acceleo.ui.interpreter.language.EvaluationContext;
import org.eclipse.acceleo.ui.interpreter.language.EvaluationResult;
import org.eclipse.acceleo.ui.interpreter.language.IInterpreterSourceViewer;
import org.eclipse.acceleo.ui.interpreter.language.InterpreterContext;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.edit.ui.dnd.LocalTransfer;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.commands.ActionHandler;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.ITextListener;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.TextEvent;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.contexts.IContextActivation;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.Form;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.handlers.IHandlerActivation;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.views.navigator.LocalSelectionTransfer;

/**
 * The Actual "Interpreter" view that will be displayed in the Eclipse workbench.
 * 
 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
 */
public class InterpreterView extends ViewPart {
    /** Prefix of the messages corresponding to compilation problems. */
    protected static final String COMPILATION_MESSAGE_PREFIX = "compilation.message"; //$NON-NLS-1$

    /** Prefix of the messages corresponding to evaluation problems. */
    protected static final String EVALUATION_MESSAGE_PREFIX = "evaluation.message"; //$NON-NLS-1$

    /**
     * This will be used as the key for the "information" message that the interpreter will display for
     * compilation "OK" status. There will only be one.
     */
    private static final String COMPILATION_INFO_MESSAGE_KEY = "interpreter.evaluation.info.message"; //$NON-NLS-1$

    /**
     * This will be used as the key for the "information" message that the interpreter will display for
     * evaluation "OK" status. There will only be one.
     */
    private static final String EVALUATION_INFO_MESSAGE_KEY = "interpreter.evaluation.info.message"; //$NON-NLS-1$

    /** ID of the interpreter view context. Must be kept in sync with the plugin.xml declaration. */
    private static final String INTERPRETER_VIEW_CONTEXT_ID = "org.eclipse.acceleo.ui.interpreter.interpreterview"; //$NON-NLS-1$

    /** ID of the toolbar's group in which we'll add language specific actions. */
    private static final String LANGUAGE_SPECIFIC_ACTION_GROUP = "LanguageSpecificActions"; //$NON-NLS-1$

    /** Key for the currently selected language as stored in this view's memento. */
    private static final String MEMENTO_CURRENT_LANGUAGE_KEY = "org.eclipse.acceleo.ui.interpreter.memento.current.language"; //$NON-NLS-1$

    /** Key for the expression as stored in this view's memento. */
    private static final String MEMENTO_EXPRESSION_KEY = "org.eclipse.acceleo.ui.interpreter.memento.expression"; //$NON-NLS-1$

    /** Key for the real-time compilation state as stored in this view's memento. */
    private static final String MEMENTO_REAL_TIME_KEY = "org.eclipse.acceleo.ui.interpreter.memento.realtime"; //$NON-NLS-1$

    /** Key for the hidden state of the variable viewer as stored in this view's memento. */
    private static final String MEMENTO_VARIABLES_VISIBLE_KEY = "org.eclipse.acceleo.ui.interpreter.memento.variables.hide"; //$NON-NLS-1$

    /** We'll use this as the id of our viewers' menus. */
    private static final String MENU_ID = "#PopupMenu"; //$NON-NLS-1$

    /**
     * Id for command "Redo" in category "Edit". This should be directly referenced from
     * org.eclipse.ui.IWorkbenchCommandConstants.EDIT_REDO ... though that would break our Eclipse 3.4
     * compatibility.
     */
    private static final String WORKBENCH_CONSTANT_EDIT_REDO = "org.eclipse.ui.edit.redo"; //$NON-NLS-1$

    /**
     * Id for command "Undo" in category "Edit". This should be directly referenced from
     * org.eclipse.ui.IWorkbenchCommandConstants.EDIT_UNDO ... though that would break our Eclipse 3.4
     * compatibility.
     */
    private static final String WORKBENCH_CONSTANT_EDIT_UNDO = "org.eclipse.ui.edit.undo"; //$NON-NLS-1$

    /**
     * If we have a compilation result, this will contain it (note that some language are not compiled, thus
     * an evaluation task can legally be created while this is <code>null</code>.
     */
    protected CompilationResult compilationResult;

    /** Thread which purpose is to compile the expression and update the context with the result. */
    protected CompilationThread compilationThread;

    /** Keeps a reference to the "link with editor" action. */
    protected LinkWithEditorContextAction linkWithEditorContextAction;

    /** This executor service will be used in order to launch the evaluation tasks of this interpreter. */
    /* package */ExecutorService evaluationPool = Executors.newSingleThreadExecutor();

    /** This will hold the real-time evaluation thread. */
    /* package */RealTimeThread realTimeThread;

    /** Content assist activation token. This will be needed to deactivate our handler. */
    private IHandlerActivation activationTokenContentAssist;

    /** Redo activation token. This will be needed to deactivate our handler. */
    private IHandlerActivation activationTokenRedo;

    /** Undo action activation token. This will be needed to deactivate our handler. */
    private IHandlerActivation activationTokenUndo;

    /** This executor service will be used in order to launch the compilation tasks of this interpreter. */
    private ExecutorService compilationPool = Executors.newSingleThreadExecutor();

    /** Context activation token. This will be needed to deactivate it. */
    private IContextActivation contextActivationToken;

    /** Currently selected language descriptor. */
    private LanguageInterpreterDescriptor currentLanguage;

    /** Currently selected language interpreter. */
    private AbstractLanguageInterpreter currentLanguageInterpreter;

    /** This will be used to listen to editor focus changes in the workbench. */
    private IPartListener2 editorPartListener = new InterpreterEditorPartListener();

    /** This will be used to listen to EObject selection within the workbench. */
    private ISelectionListener eobjectSelectionListener;

    /** Thread which purpose is to evaluate the expression and update the view with the result. */
    private EvaluationThread evaluationThread;

    /**
     * Keeps a reference to the "expression" section of the interpreter form. This will be used to re-create
     * the result viewer when changing language.
     */
    private Section expressionSection;

    /**
     * This source viewer will be used in order to display the "expression" area in which the user can type
     * the expression that is to be evaluated.
     */
    private SourceViewer expressionViewer;

    /** We'll create this {@link SashForm} as the main body of the interpreter form. */
    private SashForm formBody;

    /** Keeps a reference to the toolkit used to create our form. This will be used when switching languages. */
    private FormToolkit formToolkit;

    /** Form that will contain the interpreter itself (left part of the view). */
    private Form interpreterForm;

    /** Kept as an instance member, this will allow us to set unique identifiers to the status messages. */
    private int messageCount;

    /**
     * We'll use this indirection layer for the form's message manager in order to bypass the API breakage for
     * Ganymede.
     */
    private IFormMessageManager messageManager;

    /** Memento from which to restore this view's state. */
    private IMemento partMemento;

    /** This indicates whether we should be compiling the expression in real-time. */
    private boolean realTime;

    /**
     * Keeps a reference to the "result" section of the interpreter form. This will be used to re-create the
     * result viewer when changing language.
     */
    private Section resultSection;

    /** Viewer in which we'll display the result of the evaluations. */
    private Viewer resultViewer;

    /** This will hold the current selection of EObjects (in the workspace). */
    private List<EObject> selectedEObjects;

    /** The "right column" composite of the interpreter form, displaying the variables when not hidden. */
    private Composite variableColumn;

    /** Viewer in which we'll display the accessible variables. */
    private TreeViewer variableViewer;

    /** Indicates whether the variable viewer is visible. */
    private boolean variableVisible;

    /**
     * Creates a tool bar for the given section.
     * 
     * @param section
     *            The section for which we need a tool bar.
     * @return The created tool bar.
     */
    protected static final ToolBarManager createSectionToolBar(Section section) {
        final ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL);
        final ToolBar toolBar = toolBarManager.createControl(section);

        final Cursor handCursor = new Cursor(Display.getCurrent(), SWT.CURSOR_HAND);
        toolBar.setCursor(handCursor);
        // Cursor needs to be explicitly disposed
        toolBar.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                if (!handCursor.isDisposed()) {
                    handCursor.dispose();
                }
            }
        });

        section.setTextClient(toolBar);
        toolBar.setData(toolBarManager);
        toolBar.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                toolBar.setData(null);
            }
        });

        return toolBarManager;
    }

    /**
     * Returns the toolbar of the given section if any.
     * 
     * @param section
     *            The section of which we need the toolbar.
     * @return The toolbar of the given section if any.
     */
    protected static final ToolBarManager getSectionToolBar(Section section) {
        Control textClient = section.getTextClient();
        if (textClient instanceof ToolBar) {
            ToolBar toolBar = (ToolBar) textClient;
            Object data = toolBar.getData();
            if (data instanceof ToolBarManager) {
                return (ToolBarManager) data;
            }
        }
        return null;
    }

    /**
     * Opens the "add variable" wizard in order to create a new variable with the given value.
     * 
     * @param variableValue
     *            The variable value
     */
    public void addVariables(Object variableValue) {
        NewVariableAction action = new NewVariableAction(variableViewer, variableValue);
        action.run();
    }

    /**
     * Asks for the compilation of the current expression.
     * <p>
     * This will take a snapshot of the current interpreter context and launch a new thread for the
     * compilation. This thread can and will be cancelled whenever a new compilation is required.
     * </p>
     */
    public void compileExpression() {
        if (this.expressionViewer == null || this.expressionViewer.getTextWidget() == null
                || this.expressionViewer.getTextWidget().isDisposed()) {
            return;
        }

        final InterpreterContext context = getInterpreterContext();
        final Callable<CompilationResult> compilationTask = getCurrentLanguageInterpreter()
                .getCompilationTask(context);

        // Cancel previous compilation task
        if (compilationThread != null && !compilationThread.isInterrupted()) {
            compilationThread.interrupt();
        }
        // Cancel running evaluation task
        if (evaluationThread != null && !evaluationThread.isInterrupted()) {
            evaluationThread.interrupt();
        }

        clearCompilationMessages();

        if (compilationTask != null) {
            Future<CompilationResult> compilationFuture = compilationPool.submit(compilationTask);
            compilationThread = new CompilationThread(compilationFuture);
            compilationThread.start();
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
     */
    @Override
    public void createPartControl(Composite parent) {
        Composite rootContainer = new SashForm(parent, SWT.HORIZONTAL);
        GridLayout layout = new GridLayout();
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        layout.verticalSpacing = 0;
        layout.horizontalSpacing = 0;
        rootContainer.setLayout(layout);
        GridData gridData = new GridData(GridData.FILL_BOTH);
        gridData.horizontalIndent = 1;
        rootContainer.setLayoutData(gridData);

        formToolkit = new FormToolkit(rootContainer.getDisplay());

        createInterpreterForm(formToolkit, rootContainer);

        // The view's state has been restored on-the-fly. We can now discard the memento.
        partMemento = null;
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.ui.part.WorkbenchPart#dispose()
     */
    @Override
    public void dispose() {
        if (this.compilationThread != null && !this.compilationThread.isInterrupted()) {
            this.compilationThread.interrupt();
        }
        if (this.evaluationThread != null && !this.evaluationThread.isInterrupted()) {
            this.evaluationThread.interrupt();
        }

        if (contextActivationToken != null) {
            IContextService contextService = (IContextService) getSite().getService(IContextService.class);
            contextService.deactivateContext(contextActivationToken);
        }

        IHandlerService handlerService = (IHandlerService) getSite().getService(IHandlerService.class);
        if (activationTokenContentAssist != null) {
            handlerService.deactivateHandler(activationTokenContentAssist);
        }
        if (activationTokenRedo != null) {
            handlerService.deactivateHandler(activationTokenRedo);
        }
        if (activationTokenUndo != null) {
            handlerService.deactivateHandler(activationTokenUndo);
        }

        IWorkbenchWindow workbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
        if (workbenchWindow != null && workbenchWindow.getActivePage() != null
                && eobjectSelectionListener != null) {
            workbenchWindow.getActivePage().removeSelectionListener(eobjectSelectionListener);
        }
        clearSelection();

        IWorkbenchPage currentPage = getSite().getPage();
        if (currentPage != null) {
            currentPage.removePartListener(editorPartListener);
        }

        super.dispose();
    }

    /**
     * Evaluates the currently entered expression with the current context.
     */
    public void evaluate() {
        if (evaluationThread != null && !evaluationThread.isInterrupted()) {
            evaluationThread.interrupt();
        }

        clearEvaluationMessages();

        evaluationThread = new EvaluationThread(getInterpreterContext());
        evaluationThread.start();
    }

    /**
     * Returns the currently selected language.
     * 
     * @return The currently selected language.
     */
    public final AbstractLanguageInterpreter getCurrentLanguageInterpreter() {
        if (currentLanguageInterpreter == null) {
            if (getCurrentLanguageDescriptor() != null) {
                currentLanguageInterpreter = getCurrentLanguageDescriptor().createLanguageInterpreter();
            } else {
                currentLanguageInterpreter = new DefaultLanguageInterpreter();
            }
        }
        return currentLanguageInterpreter;
    }

    /**
     * Returns the current interpreter context. This will mainly be used by the compilation tasks of the
     * language interpreters.
     * 
     * @return The current interpreter context.
     */
    @SuppressWarnings("unchecked")
    public InterpreterContext getInterpreterContext() {
        String fullExpression = expressionViewer.getTextWidget().getText();

        List<EObject> targetEObjects = selectedEObjects;
        if (targetEObjects == null) {
            targetEObjects = Collections.emptyList();
        }

        final List<Variable> variables;
        Object variableViewerInput = variableViewer.getInput();
        if (variableViewerInput instanceof List<?>) {
            variables = new ArrayList<Variable>((List<Variable>) variableViewerInput);
        } else {
            variables = Collections.emptyList();
        }

        ISelection selection = expressionViewer.getSelection();
        if (selection == null
                || (selection instanceof ITextSelection && ((ITextSelection) selection).getLength() == 0)) {
            selection = new TextSelection(expressionViewer.getDocument(), 0, fullExpression.length());
        }

        // Is the "debug" view currently active and showing an Acceleo thread?
        if (InterpreterDependencyChecks.isDebugAccessible()) {
            List<Variable> debugVariables = DebugViewHelper.getCurrentDebugThreadVariables();
            for (Variable var : debugVariables) {
                boolean duplicate = false;
                for (int i = 0; i < variables.size() && !duplicate; i++) {
                    duplicate = variables.get(i).getName().equals(var.getName());
                }
                if (!duplicate) {
                    variables.addAll(debugVariables);
                }
            }
        }

        return new InterpreterContext(fullExpression, selection, targetEObjects, variables);
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite, org.eclipse.ui.IMemento)
     */
    @Override
    public void init(IViewSite site, IMemento memento) throws PartInitException {
        super.init(site, memento);
        this.partMemento = memento;

        IContextService contextService = (IContextService) site.getService(IContextService.class);
        contextActivationToken = contextService.activateContext(INTERPRETER_VIEW_CONTEXT_ID);

        eobjectSelectionListener = new EObjectSelectionListener();
        site.getPage().addSelectionListener(eobjectSelectionListener);
    }

    /**
     * Indicates if the variables are visible in the view.
     * 
     * @return <code>true</code> if the variables are visible, <code>false</code> otherwise.
     */
    public boolean isVariableVisible() {
        return variableVisible;
    }

    /** Link the current language interpreter with the current editor. */
    public void linkWithEditorContext() {
        IWorkbenchPage page = getSite().getPage();
        if (!linkWithEditorContextAction.isEnabled() || page == null || page.getActiveEditor() == null) {
            return;
        }

        IEditorPart activeEditor = page.getActiveEditor();
        if (linkWithEditorContextAction.isChecked()) {
            getCurrentLanguageInterpreter().linkWithEditor(activeEditor);
            linkWithEditorContextAction.changeTooltip(activeEditor);
        } else {
            getCurrentLanguageInterpreter().linkWithEditor(null);
            linkWithEditorContextAction.changeTooltip(null);
            /*
             * Re-compute enablement : we may have left the toggle "enabled" while there is no active
             * "linkable with" editors
             */
            if (activeEditor == null) {
                linkWithEditorContextAction.setEnabled(false);
            } else {
                linkWithEditorContextAction
                        .setEnabled(getCurrentLanguageInterpreter().canLinkWithEditor(activeEditor));
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.ui.part.ViewPart#saveState(org.eclipse.ui.IMemento)
     */
    @Override
    public void saveState(IMemento memento) {
        if (partMemento != null) {
            // Part had not been restored, keep old state
            memento.putMemento(partMemento);
        } else {
            if (getCurrentLanguageDescriptor() != null) {
                memento.putString(MEMENTO_CURRENT_LANGUAGE_KEY, getCurrentLanguageDescriptor().getClassName());
            }
            memento.putString(MEMENTO_EXPRESSION_KEY, expressionViewer.getTextWidget().getText());
            memento.putBoolean(MEMENTO_REAL_TIME_KEY, Boolean.valueOf(realTime));
            memento.putBoolean(MEMENTO_VARIABLES_VISIBLE_KEY,
                    Boolean.valueOf(variableViewer.getControl().isVisible()));
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
     */
    @Override
    public void setFocus() {
        if (expressionViewer != null) {
            expressionViewer.getControl().setFocus();
        }
    }

    /** Enables (or disables) the real-time evaluation of expressions. */
    public synchronized void toggleRealTime() {
        realTime = !realTime;
        if (realTime) {
            realTimeThread = new RealTimeThread();

            final String text = expressionViewer.getTextWidget().getText();
            if (text != null && text.length() > 0) {
                // Launch a compilation right from the get-go
                compileExpression();
                evaluate();
            }

            realTimeThread.start();
        } else {
            if (realTimeThread != null) {
                realTimeThread.interrupt();
                realTimeThread = null;
            }
        }
    }

    /**
     * Shows or hides the variables viewer.
     */
    public void toggleVariableVisibility() {
        if (variableColumn != null && !variableColumn.isDisposed()) {
            variableVisible = !variableVisible;
            variableColumn.setVisible(variableVisible);
            final int[] newWeights;
            if (variableVisible) {
                newWeights = new int[] { 3, 1, };
            } else {
                newWeights = new int[] { 1, 0, };
            }
            formBody.setWeights(newWeights);
            getForm().layout();
        }
    }

    /**
     * Adds a new message to the form.
     * 
     * @param messageKey
     *            Key of the message. Will be used to find and remove it later on.
     * @param message
     *            Text of the message that is to be added to the form.
     * @param messageType
     *            Type of the message as defined in {@link IMessageProvider}.
     */
    protected final void addMessage(String messageKey, String message, int messageType) {
        Control targetControl = null;
        if (messageType != IMessageProvider.NONE && messageKey.startsWith(COMPILATION_MESSAGE_PREFIX)) {
            targetControl = expressionSection;
        } else if (messageType != IMessageProvider.NONE && messageKey.startsWith(EVALUATION_MESSAGE_PREFIX)) {
            targetControl = resultSection;
        }

        if (!getForm().isDisposed()) {
            if (targetControl != null) {
                getMessageManager().addMessage(messageKey, message, messageType, targetControl);
            } else {
                getMessageManager().addMessage(messageKey, message, messageType);
            }
        }
    }

    /**
     * This will be used by the {@link CompilationThread}s in order to parse the compilation or evaluation
     * results' status and add the necessary problem message to the form.
     * 
     * @param status
     *            Status which messages is to be added to the form.
     * @param keyPrefix
     *            Prefix of the message key.
     */
    protected final void addStatusMessages(IStatus status, String keyPrefix) {
        if (status instanceof MultiStatus) {
            for (IStatus child : status.getChildren()) {
                addStatusMessages(child, keyPrefix);
            }
        } else {
            String messageKey;
            if (status.getSeverity() == IStatus.OK) {
                if (keyPrefix.equals(COMPILATION_MESSAGE_PREFIX)) {
                    messageKey = COMPILATION_INFO_MESSAGE_KEY;
                } else {
                    messageKey = EVALUATION_INFO_MESSAGE_KEY;
                }
            } else {
                messageKey = keyPrefix + "." + messageCount++; //$NON-NLS-1$
            }

            addMessage(messageKey, status.getMessage(), convertStatusToMessageSeverity(status.getSeverity()));
        }
    }

    /**
     * Creates a new list for the selection if needed, and adds the given object to it.
     * 
     * @param object
     *            The element that is to be added to the current selection.
     */
    protected void addToSelection(EObject object) {
        if (selectedEObjects == null) {
            // Assumes the "usual" selection is always 1 element long
            selectedEObjects = new ArrayList<EObject>(1);
        }
        selectedEObjects.add(object);
    }

    /**
     * Clears all compilation messages out of the interpreter view.
     */
    protected void clearCompilationMessages() {
        /*
         * add a dummy message to ensure there is always one while we clear the rest (we'll need to reset the
         * color without having _all_ messages removed, lest the color stays at its previous state : bug
         * 357906 in FormHeading.MessageRegion#showMessage().
         */
        final String dummyMessageKey = "none"; //$NON-NLS-1$
        getMessageManager().addMessage(dummyMessageKey, "", IMessageProvider.ERROR); //$NON-NLS-1$
        // Remove all actual messages
        getMessageManager().removeMessage(COMPILATION_MESSAGE_PREFIX);
        getMessageManager().removeMessages(expressionSection);
        // Now, reset the color by modifying our (existing) dummy message to a lesser severity
        getMessageManager().addMessage(dummyMessageKey, "", IMessageProvider.NONE); //$NON-NLS-1$
        // Finally, remove our dummy
        getMessageManager().removeMessage(dummyMessageKey);
    }

    /**
     * Clears all evaluation messages out of the interpreter view.
     */
    protected void clearEvaluationMessages() {
        /*
         * add a dummy message to ensure there is always one while we clear the rest (we'll need to reset the
         * color without having _all_ messages removed, lest the color stays at its previous state : bug
         * 357906 in FormHeading.MessageRegion#showMessage().
         */
        final String dummyMessageKey = "none"; //$NON-NLS-1$
        getMessageManager().addMessage(dummyMessageKey, "", IMessageProvider.ERROR); //$NON-NLS-1$
        // Remove all actual messages
        getMessageManager().removeMessage(EVALUATION_INFO_MESSAGE_KEY);
        getMessageManager().removeMessages(resultSection);
        // Now, reset the color by modifying our (existing) dummy message to a lesser severity
        getMessageManager().addMessage(dummyMessageKey, "", IMessageProvider.NONE); //$NON-NLS-1$
        // Finally, remove our dummy
        getMessageManager().removeMessage(dummyMessageKey);
    }

    /**
     * If we currently have EObjects selected, this will clear the whole list.
     */
    protected void clearSelection() {
        if (selectedEObjects != null) {
            selectedEObjects.clear();
            selectedEObjects = null;
        }
    }

    /**
     * Creates the adapter factory that will be used for our label and content providers.
     * 
     * @return The adapter factory that will be used for our label and content providers.
     */
    protected AdapterFactory createAdapterFactory() {
        ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(
                ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
        adapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());
        return adapterFactory;
    }

    /**
     * Creates the expression viewer menu listener. This listener is in charge of filling the menu's actions.
     * 
     * @param viewer
     *            The expression viewer.
     * @return The newly created listener.
     */
    protected IMenuListener createExpressionMenuListener(SourceViewer viewer) {
        return new ExpressionMenuListener(viewer);
    }

    /**
     * This will be called to create the "Expression" section (top part of the left column) of the
     * "Interpreter" form.
     * 
     * @param toolkit
     *            Toolkit that can be used to create form parts.
     * @param leftColumn
     *            Left column of the "Interpreter" form.
     */
    protected void createExpressionSection(FormToolkit toolkit, Composite leftColumn) {
        expressionSection = toolkit.createSection(leftColumn, ExpandableComposite.TITLE_BAR);
        expressionSection.setText(InterpreterMessages.getString("interpreter.view.expression.section.name")); //$NON-NLS-1$

        Composite expressionSectionBody = toolkit.createComposite(expressionSection);
        GridLayout expressionSectionLayout = new GridLayout();
        expressionSectionBody.setLayout(expressionSectionLayout);

        expressionViewer = createExpressionViewer(expressionSectionBody);
        GridData gridData = new GridData(GridData.FILL_BOTH);
        expressionViewer.getControl().setLayoutData(gridData);

        createSectionToolBar(expressionSection);
        populateExpressionSectionToolbar(expressionSection);

        toolkit.paintBordersFor(expressionSectionBody);
        expressionSection.setClient(expressionSectionBody);
    }

    /**
     * Creates the source viewer for the currently selected language.
     * 
     * @param parent
     *            Parent Composite of the result viewer.
     * @return The source viewer for the currently selected language.
     */
    protected SourceViewer createExpressionViewer(Composite parent) {
        SourceViewer viewer = getCurrentLanguageInterpreter().createSourceViewer(parent);
        if (viewer == null) {
            viewer = SWTUtil.createScrollableSourceViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
            viewer.configure(new TextSourceViewerConfiguration());
            viewer.setDocument(new Document());
        }
        getCurrentLanguageInterpreter().configureSourceViewer(viewer);

        if (viewer instanceof IInterpreterSourceViewer) {
            setUpContentAssist((IInterpreterSourceViewer) viewer);
        }
        setUpRealTimeCompilation(viewer);
        setUpDefaultTextAction(viewer);

        createExpressionMenu(viewer);

        if (partMemento != null) {
            String expression = partMemento.getString(MEMENTO_EXPRESSION_KEY);
            if (expression != null) {
                viewer.getTextWidget().setText(expression);
            }
        }
        return viewer;
    }

    /**
     * This will be called in order to create the "Interpreter" form.
     * 
     * @param toolkit
     *            Toolkit that can be used to create the form.
     * @param parent
     *            Parent composite of the form.
     */
    protected void createInterpreterForm(FormToolkit toolkit, Composite parent) {
        interpreterForm = toolkit.createForm(parent);
        messageManager = FormMessageManagerFactory.createFormMessageManager(interpreterForm);
        getMessageManager().setDecorationPosition(SWT.LEFT | SWT.TOP);
        toolkit.decorateFormHeading(getForm());

        populateLanguageMenu(getForm().getMenuManager());

        /*
         * This will be called at initialization only. When initializing the "languages" menu, we do select
         * the accurate descriptor, but we do not set the language text and icon. There is a chance that no
         * languages have been provided. Use a default text and image for these.
         */
        String languageName = ""; //$NON-NLS-1$
        ImageDescriptor titleImageDescriptor = null;
        if (getCurrentLanguageDescriptor() != null) {
            languageName = getCurrentLanguageDescriptor().getLabel();
            titleImageDescriptor = getCurrentLanguageDescriptor().getIcon();
        }
        getForm().setText(InterpreterMessages.getString("interpreter.view.title", //$NON-NLS-1$
                languageName));
        final Image titleImage;
        if (titleImageDescriptor != null) {
            titleImage = titleImageDescriptor.createImage();
        } else {
            titleImage = InterpreterImages.getImageDescriptor(IInterpreterConstants.INTERPRETER_VIEW_DEFAULT_ICON)
                    .createImage();
        }
        setTitleImage(titleImage);
        getForm().setImage(titleImage);

        Composite mainBody = getForm().getBody();
        mainBody.setLayout(new GridLayout());
        formBody = new SashForm(mainBody, SWT.HORIZONTAL | SWT.SMOOTH);
        toolkit.adapt(formBody);
        formBody.setLayoutData(new GridData(GridData.FILL_BOTH));

        SashForm leftColumn = new SashForm(formBody, SWT.VERTICAL | SWT.SMOOTH);
        toolkit.adapt(leftColumn);

        createExpressionSection(toolkit, leftColumn);
        createResultSection(toolkit, leftColumn);

        leftColumn.setWeights(new int[] { 2, 3, });

        variableColumn = toolkit.createComposite(formBody);
        variableColumn.setLayout(new FillLayout());
        /*
         * Variables are invisible by default. The toolbar initialization will restore their state, making
         * them visible if they were previously.
         */
        variableColumn.setVisible(false);

        createVariableSection(toolkit, variableColumn);

        formBody.setWeights(new int[] { 3, 1, });

        createToolBar(getForm());
    }

    /**
     * Creates the result viewer menu listener. This listener is in charge of filling the menu's actions.
     * 
     * @param viewer
     *            The result viewer.
     * @return The newly created listener.
     */
    protected IMenuListener createResultMenuListener(Viewer viewer) {
        return new ResultMenuListener(viewer);
    }

    /**
     * This will be called to create the "Evaluation Result" section (bottom part of the left column) of the
     * "Interpreter" form.
     * 
     * @param toolkit
     *            Toolkit that can be used to create form parts.
     * @param leftColumn
     *            Left column of the "Interpreter" form.
     */
    protected void createResultSection(FormToolkit toolkit, Composite leftColumn) {
        resultSection = toolkit.createSection(leftColumn, ExpandableComposite.TITLE_BAR);
        resultSection.setText(InterpreterMessages.getString("interpreter.view.result.section.name")); //$NON-NLS-1$

        Composite resultSectionBody = toolkit.createComposite(resultSection);
        GridLayout resultLayout = new GridLayout();
        resultSectionBody.setLayout(resultLayout);

        resultViewer = createResultViewer(resultSectionBody);
        GridData gridData = new GridData(GridData.FILL_BOTH);
        resultViewer.getControl().setLayoutData(gridData);

        createSectionToolBar(resultSection);
        populateResultSectionToolbar(resultSection);

        toolkit.paintBordersFor(resultSectionBody);
        resultSection.setClient(resultSectionBody);
    }

    /**
     * Creates the result viewer for the currently selected language.
     * 
     * @param parent
     *            Parent Composite of the result viewer.
     * @return The result viewer for the currently selected language.
     */
    protected Viewer createResultViewer(Composite parent) {
        Viewer viewer = getCurrentLanguageInterpreter().createResultViewer(parent);
        if (viewer == null) {
            viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
            ColumnViewerToolTipSupport.enableFor((TreeViewer) viewer);

            AdapterFactory adapterFactory = createAdapterFactory();
            TreeViewer treeViewer = (TreeViewer) viewer;
            treeViewer.setContentProvider(new ResultContentProvider(adapterFactory));
            treeViewer.setLabelProvider(new ResultLabelProvider(adapterFactory));
        }

        if (viewer instanceof TreeViewer) {
            setUpResultDragSupport((TreeViewer) viewer);

            ((TreeViewer) viewer).addDoubleClickListener(new ResultDoubleClickListener());
        }

        createResultMenu(viewer);

        return viewer;
    }

    /**
     * Creates the toolbar of our interpreter form.
     * 
     * @param form
     *            The interpreter form.
     */
    protected void createToolBar(Form form) {
        IAction realTimeAction = new ToggleRealTimeAction(this);
        if (partMemento != null) {
            Boolean isRealTime = partMemento.getBoolean(MEMENTO_REAL_TIME_KEY);
            if (isRealTime != null && isRealTime.booleanValue()) {
                toggleRealTime();
                realTimeAction.setChecked(realTime);
            }
        } else {
            // Real-time is active by default
            toggleRealTime();
            realTimeAction.setChecked(realTime);
        }

        linkWithEditorContextAction = new LinkWithEditorContextAction(this);
        final IWorkbenchPage currentPage = getSite().getPage();
        if (currentPage != null) {
            editorPartListener = new InterpreterEditorPartListener();
            getSite().getPage().addPartListener(editorPartListener);

            IEditorPart currentEditor = currentPage.getActiveEditor();
            if (currentEditor == null) {
                linkWithEditorContextAction.setEnabled(false);
            } else {
                linkWithEditorContextAction
                        .setEnabled(getCurrentLanguageInterpreter().canLinkWithEditor(currentEditor));
            }
        } else {
            linkWithEditorContextAction.setEnabled(false);
        }

        IAction variableVisibilityAction = new ToggleVariableVisibilityAction(this);
        if (partMemento != null) {
            Boolean isVariableVisible = partMemento.getBoolean(MEMENTO_VARIABLES_VISIBLE_KEY);
            if (isVariableVisible != null && isVariableVisible.booleanValue()) {
                toggleVariableVisibility();
                variableVisibilityAction.setChecked(variableVisible);
            }
        }

        IToolBarManager toolBarManager = form.getToolBarManager();
        toolBarManager.add(linkWithEditorContextAction);
        toolBarManager.add(realTimeAction);
        toolBarManager.add(variableVisibilityAction);

        toolBarManager.add(new Separator(LANGUAGE_SPECIFIC_ACTION_GROUP));

        getCurrentLanguageInterpreter().addToolBarActions(this, toolBarManager);

        toolBarManager.update(true);
    }

    /**
     * Creates the variable viewer menu listener. This listener is in charge of filling the menu's actions.
     * 
     * @param viewer
     *            The variable viewer.
     * @return The newly created listener.
     */
    protected IMenuListener createVariableMenuListener(TreeViewer viewer) {
        return new VariableMenuListener(viewer);
    }

    /**
     * This will be called in order to create the "Variables" section (right column) of the "Interpreter"
     * form.
     * 
     * @param toolkit
     *            Toolkit that can be used to create form parts.
     * @param rightColumn
     *            Right column of the "Interpreter" form.
     */
    protected void createVariableSection(FormToolkit toolkit, Composite rightColumn) {
        Section variableSection = toolkit.createSection(rightColumn, ExpandableComposite.TITLE_BAR);
        variableSection.setText(InterpreterMessages.getString("interpreter.view.variable.section.name")); //$NON-NLS-1$

        Composite variableSectionBody = toolkit.createComposite(variableSection);
        variableSectionBody.setLayout(new FillLayout());

        variableViewer = createVariableViewer(toolkit, variableSectionBody);

        ToolBarManager toolBarManager = createSectionToolBar(variableSection);
        toolBarManager.add(new ClearVariableViewerAction(variableViewer));
        toolBarManager.update(true);

        toolkit.paintBordersFor(variableSectionBody);
        variableSection.setClient(variableSectionBody);
    }

    /**
     * This will be called in order to create the TreeViewer used to display variables.
     * 
     * @param toolkit
     *            Toolkit that can be used to create form parts.
     * @param sectionBody
     *            Parent composite of the TreeViewer.
     * @return The newly created viewer.
     */
    protected TreeViewer createVariableViewer(FormToolkit toolkit, Composite sectionBody) {
        Tree variableTree = toolkit.createTree(sectionBody, SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI);

        TreeViewer viewer = new TreeViewer(variableTree);
        ColumnViewerToolTipSupport.enableFor(viewer);
        AdapterFactory adapterFactory = createAdapterFactory();
        viewer.setContentProvider(new VariableContentProvider(adapterFactory));
        viewer.setLabelProvider(new VariableLabelProvider(adapterFactory));

        setUpVariableDropSupport(viewer);
        createVariableMenu(viewer);
        setUpVariableActions(viewer);

        viewer.setInput(new ArrayList<Variable>());

        return viewer;
    }

    /**
     * Returns the interpreter form.
     * 
     * @return The interpreter form.
     */
    protected Form getForm() {
        return interpreterForm;
    }

    /**
     * Returns the indirection layer for the form's message manager.
     * 
     * @return The indirection layer for the form's message manager.
     */
    protected IFormMessageManager getMessageManager() {
        return messageManager;
    }

    /**
     * Returns the current source viewer.
     * <p>
     * Note that the source viewer can change and be disposed over time; don't keep references to it.
     * </p>
     * 
     * @return The current source viewer.
     */
    protected SourceViewer getSourceViewer() {
        return expressionViewer;
    }

    /**
     * Populates the {@link #expressionSection}'s toolbar.
     * 
     * @param section
     *            the expresssion section.
     */
    protected void populateExpressionSectionToolbar(Section section) {
        ToolBarManager toolBarManager = getSectionToolBar(section);
        if (toolBarManager != null) {
            toolBarManager.removeAll();
            toolBarManager.add(new EvaluateAction(this));
            toolBarManager.add(new ClearExpressionViewerAction(getSourceViewer()));
            toolBarManager.update(true);
        }
    }

    /**
     * Adds the "language selection" radio buttons to the interpreter's menu.
     * 
     * @param menuManager
     *            The interpreter form's menu manager.
     */
    protected final void populateLanguageMenu(IMenuManager menuManager) {
        for (LanguageInterpreterDescriptor descriptor : LanguageInterpreterRegistry.getRegisteredInterpreters()) {
            IAction action = new ChangeLanguageAction(descriptor);
            menuManager.add(action);
            if (getCurrentLanguageDescriptor() == null) {
                if (partMemento == null || partMemento.getString(MEMENTO_CURRENT_LANGUAGE_KEY) == null) {
                    currentLanguage = descriptor;
                    action.setChecked(true);
                } else if (partMemento.getString(MEMENTO_CURRENT_LANGUAGE_KEY).equals(descriptor.getClassName())) {
                    currentLanguage = descriptor;
                    action.setChecked(true);
                }
            }
        }
    }

    /**
     * Populates the {@link #resultSection}'s toolbar.
     * 
     * @param section
     *            the result section.
     */
    protected void populateResultSectionToolbar(Section section) {
        ToolBarManager toolBarManager = getSectionToolBar(section);
        if (toolBarManager != null) {
            toolBarManager.removeAll();
            toolBarManager.add(new ClearResultViewerAction(resultViewer));
            toolBarManager.update(true);
        }
    }

    /**
     * Switch this intepreter to the given language. This will also re-title and re-create the viewers of this
     * view.
     * 
     * @param selectedLanguage
     *            The language to which this interpreter should be switched.
     */
    protected void selectLanguage(LanguageInterpreterDescriptor selectedLanguage) {
        if (currentLanguage == selectedLanguage) {
            return;
        }

        if (compilationThread != null && !compilationThread.isInterrupted()) {
            compilationThread.interrupt();
        }
        if (evaluationThread != null && !evaluationThread.isInterrupted()) {
            evaluationThread.interrupt();
        }

        /*
         * We need to remove all actions from the menu : it somehow freeze if we do not. The "trigger" for
         * this menu freeze is when we remove all messages from the message manager.
         */
        IContributionItem[] changeLanguageActions = getForm().getMenuManager().getItems();
        getMessageManager().removeAllMessages();

        // Dispose of the language specific actions
        IToolBarManager toolBarManager = getForm().getToolBarManager();
        IContributionItem[] items = toolBarManager.getItems();
        boolean dispose = false;
        for (int i = 0; i < items.length; i++) {
            IContributionItem item = items[i];
            if (dispose) {
                toolBarManager.remove(item);
                item.dispose();
            } else if (item instanceof Separator
                    && LANGUAGE_SPECIFIC_ACTION_GROUP.equals(((Separator) item).getGroupName())) {
                dispose = true;
            }
        }

        getCurrentLanguageInterpreter().dispose();
        currentLanguageInterpreter = null;

        LanguageInterpreterDescriptor previousLanguage = getCurrentLanguageDescriptor();
        currentLanguage = selectedLanguage;

        // Change interpreter title
        getForm().setText(InterpreterMessages.getString("interpreter.view.title", //$NON-NLS-1$
                getCurrentLanguageDescriptor().getLabel()));

        // Change view's icon
        Image previousImage = null;
        if (previousLanguage != null && previousLanguage.getIcon() != null
                || getCurrentLanguageDescriptor().getIcon() != null) {
            previousImage = getTitleImage();
        }
        if (getCurrentLanguageDescriptor().getIcon() != null) {
            setTitleImage(getCurrentLanguageDescriptor().getIcon().createImage());
        } else if (previousLanguage != null && previousLanguage.getIcon() != null) {
            setTitleImage(InterpreterImages.getImageDescriptor(IInterpreterConstants.INTERPRETER_VIEW_DEFAULT_ICON)
                    .createImage());
        }
        if (previousImage != null) {
            previousImage.dispose();
        }
        getForm().setImage(getTitleImage());

        if (expressionSection != null) {
            Composite expressionSectionBody = (Composite) expressionSection.getClient();
            expressionViewer.getControl().dispose();

            expressionViewer = createExpressionViewer(expressionSectionBody);
            GridData gridData = new GridData(GridData.FILL_BOTH);
            final int expressionHeight = 80;
            gridData.heightHint = expressionHeight;
            expressionViewer.getControl().setLayoutData(gridData);

            formToolkit.paintBordersFor(expressionSectionBody);

            expressionSectionBody.layout();
        }

        if (resultSection != null) {
            Composite resultSectionBody = (Composite) resultSection.getClient();
            resultViewer.getControl().dispose();

            resultViewer = createResultViewer(resultSectionBody);
            GridData gridData = new GridData(GridData.FILL_BOTH);
            resultViewer.getControl().setLayoutData(gridData);

            formToolkit.paintBordersFor(resultSectionBody);

            resultSectionBody.layout();
        }

        // Re-fill the menu now
        for (IContributionItem action : changeLanguageActions) {
            getForm().getMenuManager().add(action);
        }
        // re-fill the sections' toolbars
        populateExpressionSectionToolbar(expressionSection);
        populateResultSectionToolbar(resultSection);
        // Change the state of the link with editor action
        final IWorkbenchPage currentPage = getSite().getPage();
        if (currentPage != null) {
            IEditorPart currentEditor = currentPage.getActiveEditor();
            if (currentEditor == null) {
                linkWithEditorContextAction.setEnabled(false);
            } else {
                linkWithEditorContextAction
                        .setEnabled(getCurrentLanguageInterpreter().canLinkWithEditor(currentEditor));
            }
        } else {
            linkWithEditorContextAction.setEnabled(false);
        }
        // re-add language specific actions to the toolbar
        getCurrentLanguageInterpreter().addToolBarActions(this, toolBarManager);
        toolBarManager.update(true);
    }

    /**
     * Sets the result of the compilation to the given instance.
     * 
     * @param compilationResult
     *            The current expression's compilation result.
     */
    protected final void setCompilationResult(CompilationResult compilationResult) {
        this.compilationResult = compilationResult;
    }

    /**
     * Update the result viewer.
     * 
     * @param result
     *            The current expressions's evaluation result.
     */
    protected final void setEvaluationResult(EvaluationResult result) {
        List<Object> input = new ArrayList<Object>();
        Object evaluationResult = result.getEvaluationResult();
        if (evaluationResult instanceof Collection<?>) {
            for (Object child : (Collection<?>) evaluationResult) {
                if (child != null) {
                    input.add(child);
                }
            }
        } else if (evaluationResult != null) {
            input.add(evaluationResult);
        }
        resultViewer.setInput(input);
    }

    /**
     * Sets up the content assist action for the given source viewer.
     * 
     * @param viewer
     *            The viewer we need content assist on.
     */
    protected final void setUpContentAssist(final IInterpreterSourceViewer viewer) {
        IHandlerService service = (IHandlerService) getSite().getService(IHandlerService.class);
        if (activationTokenContentAssist != null) {
            service.deactivateHandler(activationTokenContentAssist);
        }

        IAction contentAssistAction = new Action() {
            @Override
            public void run() {
                viewer.showContentAssist(getInterpreterContext());
            }
        };
        IHandler contentAssistHandler = new ActionHandler(contentAssistAction);

        activationTokenContentAssist = service
                .activateHandler(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS, contentAssistHandler);
    }

    /**
     * Sets up the real-time compilation action on the given viewer. The real-time thread specifically needs
     * to be told whenever the expression is dirty as well as when the timer should be reset (in order not to
     * compile when the user is entering the expression).
     * 
     * @param viewer
     *            The viewer for which to set up the real-time compilation thread.
     */
    protected void setUpRealTimeCompilation(SourceViewer viewer) {
        viewer.addTextListener(new ITextListener() {
            public void textChanged(TextEvent event) {
                if (realTimeThread != null) {
                    realTimeThread.reset();
                    realTimeThread.setDirty();
                }
            }
        });
    }

    /**
     * Sets up the drag and drop support for the result viewer.
     * 
     * @param viewer
     *            The result viewer.
     */
    protected void setUpResultDragSupport(TreeViewer viewer) {
        int operations = DND.DROP_COPY | DND.DROP_LINK | DND.DROP_MOVE;
        Transfer[] transfers = new Transfer[] { LocalTransfer.getInstance(), };

        viewer.addDragSupport(operations, transfers, new ResultDragListener(viewer));
    }

    /**
     * This will be called in order to set up the actions available on the given variable viewer.
     * 
     * @param viewer
     *            The viewer on which to activate actions.
     */
    protected void setUpVariableActions(final TreeViewer viewer) {
        Tree tree = viewer.getTree();
        final Action renameAction = new RenameVariableAction(viewer);
        final Action deleteAction = new DeleteVariableOrValueAction(viewer);

        tree.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent event) {
                if (event.keyCode == SWT.DEL && event.stateMask == 0) {
                    if (deleteAction.isEnabled()) {
                        deleteAction.run();
                    }
                } else if (event.keyCode == SWT.F2 && event.stateMask == 0) {
                    if (renameAction.isEnabled()) {
                        renameAction.run();
                    }
                }
            }
        });
    }

    /**
     * Sets up the drag and drop support for the variable viewer.
     * 
     * @param viewer
     *            The variable viewer.
     */
    protected void setUpVariableDropSupport(TreeViewer viewer) {
        int operations = DND.DROP_DEFAULT | DND.DROP_COPY | DND.DROP_LINK | DND.DROP_MOVE;
        Transfer[] transfers = new Transfer[] { LocalTransfer.getInstance(),
                LocalSelectionTransfer.getTransfer(), };

        viewer.addDropSupport(operations, transfers, new VariableDropListener(viewer));
    }

    /**
     * Converts an IStatus severity to a IMessageProviderSeverity.
     * 
     * @param statusSeverity
     *            The status severity to be converted.
     * @return The corresponding IMessageProvider severity.
     */
    private int convertStatusToMessageSeverity(int statusSeverity) {
        int severity = IMessageProvider.NONE;
        switch (statusSeverity) {
        case IStatus.INFO:
            severity = IMessageProvider.INFORMATION;
            break;
        case IStatus.WARNING:
            severity = IMessageProvider.WARNING;
            break;
        case IStatus.ERROR:
            severity = IMessageProvider.ERROR;
            break;
        default:
            severity = IMessageProvider.NONE;
        }
        return severity;
    }

    /**
     * Creates the right-click menu that will be displayed for the expression viewer.
     * 
     * @param viewer
     *            The expression viewer.
     */
    private void createExpressionMenu(SourceViewer viewer) {
        MenuManager menuManager = new MenuManager(MENU_ID);
        menuManager.setRemoveAllWhenShown(true);
        menuManager.addMenuListener(createExpressionMenuListener(viewer));
        Menu menu = menuManager.createContextMenu(viewer.getControl());
        viewer.getControl().setMenu(menu);
    }

    /**
     * Creates the right-click menu that will be displayed for the result viewer.
     * 
     * @param viewer
     *            The result viewer.
     */
    private void createResultMenu(Viewer viewer) {
        MenuManager menuManager = new MenuManager(MENU_ID);
        menuManager.setRemoveAllWhenShown(true);
        menuManager.addMenuListener(createResultMenuListener(viewer));
        Menu menu = menuManager.createContextMenu(viewer.getControl());
        viewer.getControl().setMenu(menu);
    }

    /**
     * Creates the right-click menu that will be displayed for the variable viewer.
     * 
     * @param viewer
     *            The variable viewer.
     */
    private void createVariableMenu(TreeViewer viewer) {
        MenuManager menuManager = new MenuManager(MENU_ID);
        menuManager.setRemoveAllWhenShown(true);
        menuManager.addMenuListener(createVariableMenuListener(viewer));
        Menu menu = menuManager.createContextMenu(viewer.getControl());
        viewer.getControl().setMenu(menu);
    }

    /**
     * Returns the currently selected language's descriptor.
     * 
     * @return The currently selected language's descriptor.
     */
    private LanguageInterpreterDescriptor getCurrentLanguageDescriptor() {
        return currentLanguage;
    }

    /**
     * Sets up the default text actions (undo, redo) on the given source viewer.
     * 
     * @param viewer
     *            The viewer on which to activate default text actions.
     */
    private void setUpDefaultTextAction(final SourceViewer viewer) {
        IHandlerService service = (IHandlerService) getSite().getService(IHandlerService.class);
        if (activationTokenRedo != null) {
            service.deactivateHandler(activationTokenRedo);
        }
        if (activationTokenUndo != null) {
            service.deactivateHandler(activationTokenUndo);
        }

        IAction redoAction = new Action() {
            @Override
            public void run() {
                viewer.doOperation(ITextOperationTarget.REDO);
            }
        };
        IHandler redoHandler = new ActionHandler(redoAction);

        IAction undoAction = new Action() {
            @Override
            public void run() {
                viewer.doOperation(ITextOperationTarget.UNDO);
            }
        };
        IHandler undoHandler = new ActionHandler(undoAction);

        activationTokenRedo = service.activateHandler(WORKBENCH_CONSTANT_EDIT_REDO, redoHandler);
        activationTokenUndo = service.activateHandler(WORKBENCH_CONSTANT_EDIT_UNDO, undoHandler);
    }

    /**
     * This class will be used in order to populate the right-click menu of the Expression viewer.
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
     */
    protected class ExpressionMenuListener implements IMenuListener {
        /** The viewer on which this menu listener operates. */
        private final SourceViewer sourceViewer;

        /**
         * Creates this menu listener given the viewer on which it operates.
         * 
         * @param sourceViewer
         *            The viewer on which this menu listener operates.
         */
        public ExpressionMenuListener(SourceViewer sourceViewer) {
            this.sourceViewer = sourceViewer;
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.jface.action.IMenuListener#menuAboutToShow(org.eclipse.jface.action.IMenuManager)
         */
        public void menuAboutToShow(IMenuManager manager) {
            manager.add(new EvaluateAction(InterpreterView.this));
            manager.add(new ClearExpressionViewerAction(sourceViewer));
            manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
        }
    }

    /**
     * This implementation of a part listener will allow us to determine at all times whether the
     * "work in editor context" action should be enabled.
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
     */
    protected class InterpreterEditorPartListener implements IPartListener2 {
        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.ui.IPartListener2#partActivated(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partActivated(IWorkbenchPartReference partRef) {
            // If the toggle is checked, defer enablement computing till we uncheck it
            if (!linkWithEditorContextAction.isChecked() && partRef instanceof IEditorReference) {
                linkWithEditorContextAction.setEnabled(getCurrentLanguageInterpreter()
                        .canLinkWithEditor(((IEditorReference) partRef).getEditor(false)));
            }
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.ui.IPartListener2#partBroughtToTop(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partBroughtToTop(IWorkbenchPartReference partRef) {
            // No need to react to this event
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.ui.IPartListener2#partClosed(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partClosed(IWorkbenchPartReference partRef) {
            // If the toggle is checked, defer enablement computing till we uncheck it
            if (!linkWithEditorContextAction.isChecked() && partRef instanceof IEditorReference
                    && getSite().getPage() != null) {
                final IEditorPart editorPart = getSite().getPage().getActiveEditor();
                if (editorPart == null) {
                    linkWithEditorContextAction.setEnabled(false);
                } else {
                    linkWithEditorContextAction
                            .setEnabled(getCurrentLanguageInterpreter().canLinkWithEditor(editorPart));
                }
            }
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.ui.IPartListener2#partDeactivated(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partDeactivated(IWorkbenchPartReference partRef) {
            // No need to react to this event
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.ui.IPartListener2#partHidden(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partHidden(IWorkbenchPartReference partRef) {
            // No need to react to this event
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.ui.IPartListener2#partInputChanged(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partInputChanged(IWorkbenchPartReference partRef) {
            // No need to react to this event
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.ui.IPartListener2#partOpened(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partOpened(IWorkbenchPartReference partRef) {
            // No need to react to this event
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.ui.IPartListener2#partVisible(org.eclipse.ui.IWorkbenchPartReference)
         */
        public void partVisible(IWorkbenchPartReference partRef) {
            // No need to react to this event
        }
    }

    /**
     * This will allow us to react to double click events in the result view in order to display the long
     * Strings and generated files in a more suitable way.
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
     */
    protected class ResultDoubleClickListener implements IDoubleClickListener {
        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.jface.viewers.IDoubleClickListener#doubleClick(org.eclipse.jface.viewers.DoubleClickEvent)
         */
        public void doubleClick(DoubleClickEvent event) {
            if (event.getSelection().isEmpty() || !(event.getSelection() instanceof IStructuredSelection)) {
                return;
            }
            final Object target = ((IStructuredSelection) event.getSelection()).getFirstElement();
            if (target instanceof InterpreterFile) {
                showGeneratedFile((InterpreterFile) target);
            } else if (target instanceof String
                    && (((String) target).indexOf('\n') >= 0 || ((String) target).indexOf('\r') >= 0)) {
                GeneratedTextDialog dialog = new GeneratedTextDialog(Display.getCurrent().getActiveShell(),
                        "Evaluation result", (String) target); //$NON-NLS-1$
                dialog.open();
            }
        }

        /**
         * Displays the given generated file in its own read-only editor.
         * 
         * @param file
         *            The file we are to display.
         */
        private void showGeneratedFile(InterpreterFile file) {
            final IWorkbench workbench = PlatformUI.getWorkbench();
            if (workbench.getActiveWorkbenchWindow() == null
                    || workbench.getActiveWorkbenchWindow().getActivePage() == null) {
                return;
            }
            final IWorkbenchPage page = workbench.getActiveWorkbenchWindow().getActivePage();
            final IStorage storage = new InterpreterFileStorage(file);
            final IEditorDescriptor editor = workbench.getEditorRegistry().getDefaultEditor(file.getFileName());
            final IEditorInput input = new StorageEditorInput(storage);

            try {
                final String editorID;
                if (editor == null) {
                    editorID = EditorsUI.DEFAULT_TEXT_EDITOR_ID;
                } else {
                    editorID = editor.getId();
                }
                page.openEditor(input, editorID);
            } catch (PartInitException e) {
                // swallow : we just won't open editors
            }
        }
    }

    /**
     * This class will be used in order to populate the right-click menu of the result viewer.
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
     */
    protected class ResultMenuListener implements IMenuListener {
        /** The viewer on which this menu listener operates. */
        private Viewer resultViewer;

        /**
         * Creates this menu listener given the viewer on which it operates.
         * 
         * @param resultViewer
         *            The viewer on which this menu listener operates.
         */
        public ResultMenuListener(Viewer resultViewer) {
            this.resultViewer = resultViewer;
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.jface.action.IMenuListener#menuAboutToShow(org.eclipse.jface.action.IMenuManager)
         */
        public void menuAboutToShow(IMenuManager manager) {
            manager.add(new ClearResultViewerAction(resultViewer));
            manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
        }
    }

    /**
     * This class will be used in order to populate the right-click menu of the Variable viewer.
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
     */
    protected class VariableMenuListener implements IMenuListener {
        /** The viewer on which this menu listener operates. */
        private TreeViewer variableViewer;

        /**
         * Creates this menu listener given the viewer on which it operates.
         * 
         * @param variableViewer
         *            The viewer on which this menu listener operates.
         */
        public VariableMenuListener(TreeViewer variableViewer) {
            this.variableViewer = variableViewer;
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.jface.action.IMenuListener#menuAboutToShow(org.eclipse.jface.action.IMenuManager)
         */
        public void menuAboutToShow(IMenuManager manager) {
            final Variable variable = getCurrentVariable();
            manager.add(new NewVariableWizardAction(variableViewer, variable));
            manager.add(new ClearVariableViewerAction(variableViewer));
            manager.add(new Separator());
            manager.add(new DeleteVariableOrValueAction(variableViewer));
            manager.add(new RenameVariableAction(variableViewer));
            manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
        }

        /**
         * Returns the first of the currently selected Variable(s).
         * 
         * @return The first of the currently selected Variable(s)
         */
        private Variable getCurrentVariable() {
            if (variableViewer == null || variableViewer.getTree() == null
                    || variableViewer.getTree().isDisposed()) {
                return null;
            }
            Tree tree = variableViewer.getTree();

            TreeItem[] selectedItems = tree.getSelection();
            Variable selectedVariable = null;
            if (selectedItems != null && selectedItems.length > 0) {
                for (int i = 0; i < selectedItems.length && selectedVariable == null; i++) {
                    TreeItem item = selectedItems[i];
                    if (item.getData() instanceof Variable) {
                        selectedVariable = (Variable) item.getData();
                    }
                }
                for (int i = 0; i < selectedItems.length && selectedVariable == null; i++) {
                    TreeItem item = selectedItems[i].getParentItem();
                    while (item != null && selectedVariable == null) {
                        if (item.getData() instanceof Variable) {
                            selectedVariable = (Variable) item.getData();
                        }
                        item = item.getParentItem();
                    }
                }
            }
            return selectedVariable;
        }
    }

    /**
     * This class will be used for all of our "language changing" actions.
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
     */
    private class ChangeLanguageAction extends Action {
        /** Language to which this action will change the interpreter when executed. */
        private final LanguageInterpreterDescriptor language;

        /**
         * Instantiates our action given the language to which it will change.
         * 
         * @param language
         *            The language to which this action will change the interpreter.
         */
        public ChangeLanguageAction(LanguageInterpreterDescriptor language) {
            super(language.getLabel(), IAction.AS_RADIO_BUTTON);
            this.language = language;
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.jface.action.Action#run()
         */
        @Override
        public void run() {
            selectLanguage(language);
        }
    }

    /**
     * This implementation of a Thread will be used to wrap a compilation task as returned by the
     * LanguageInterpreter, then asynchronously update the form with all error messages (if any) that were
     * raised by this compilation task. Afterwards, this Thread will update the interpreter context with the
     * compilation result.
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
     */
    private class CompilationThread extends Thread {
        /**
         * We will set this flag on {@link #interrupt()} in order to determine whether the thread was
         * cancelled (which could happen <b>after</b> the thread has completed, which would make the
         * "interrupted" flag quiet.
         */
        private boolean cancelled;

        /** The compilation thread which result we are to wait for. */
        private Future<CompilationResult> compilationTask;

        /**
         * Instantiates a compilation thread given the compilation task of which we are to check the result.
         * 
         * @param compilationTask
         *            Thread which result we are to wait for.
         */
        public CompilationThread(Future<CompilationResult> compilationTask) {
            super("InterpreterCompilationThread"); //$NON-NLS-1$
            this.compilationTask = compilationTask;
        }

        /**
         * {@inheritDoc}
         * 
         * @see java.lang.Thread#interrupt()
         */
        @Override
        public void interrupt() {
            cancelled = true;
            compilationTask.cancel(true);
            super.interrupt();
        }

        /**
         * {@inheritDoc}
         * 
         * @see java.lang.Thread#run()
         */
        @Override
        public void run() {
            try {
                final CompilationResult result = compilationTask.get();
                checkCancelled();

                if (result != null && result.getStatus() != null) {
                    Display.getDefault().asyncExec(new Runnable() {
                        /**
                         * {@inheritDoc}
                         * 
                         * @see java.lang.Runnable#run()
                         */
                        public void run() {
                            clearCompilationMessages();
                            checkCancelled();
                            addStatusMessages(result.getStatus(), COMPILATION_MESSAGE_PREFIX);
                        }
                    });
                }

                // Whether there were problems or not, update the context with this result.
                setCompilationResult(result);
            } catch (InterruptedException e) {
                // Thread is expected to be cancelled if another is started
            } catch (CancellationException e) {
                // Thread is expected to be cancelled if another is started
            } catch (ExecutionException e) {
                checkCancelled();
                String message = e.getMessage();
                if (e.getCause() != null) {
                    message = e.getCause().getMessage();
                }
                final IStatus status = new Status(IStatus.ERROR, InterpreterPlugin.PLUGIN_ID, message);
                final CompilationResult result = new CompilationResult(status);

                Display.getDefault().asyncExec(new Runnable() {
                    /**
                     * {@inheritDoc}
                     * 
                     * @see java.lang.Runnable#run()
                     */
                    public void run() {
                        clearCompilationMessages();
                        checkCancelled();
                        addStatusMessages(status, COMPILATION_MESSAGE_PREFIX);
                    }
                });

                setCompilationResult(result);
            }
        }

        /**
         * Throws a new {@link CancellationException} if the current thread has been cancelled.
         */
        protected void checkCancelled() {
            if (cancelled) {
                throw new CancellationException();
            }
        }
    }

    /**
     * This will be installed on the workbench page on which this view is displayed in order to listen to
     * selection events corresponding to EObjects.
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
     */
    private class EObjectSelectionListener implements ISelectionListener {
        /**
         * Increases visibility of the default constructor.
         */
        public EObjectSelectionListener() {
            // Increases visibility
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.ui.ISelectionListener#selectionChanged(org.eclipse.ui.IWorkbenchPart,
         *      org.eclipse.jface.viewers.ISelection)
         */
        @SuppressWarnings("unchecked")
        public void selectionChanged(IWorkbenchPart part, ISelection selection) {
            if (!selection.isEmpty() && selection instanceof IStructuredSelection) {
                boolean cleared = false;
                final Iterator<Object> selectionIterator = ((IStructuredSelection) selection).iterator();
                while (selectionIterator.hasNext()) {
                    final Object next = selectionIterator.next();
                    final EObject nextEObject;
                    if (next instanceof EObject) {
                        nextEObject = (EObject) next;
                    } else if (next instanceof IAdaptable) {
                        nextEObject = (EObject) ((IAdaptable) next).getAdapter(EObject.class);
                    } else {
                        nextEObject = (EObject) Platform.getAdapterManager().getAdapter(next, EObject.class);
                    }
                    if (nextEObject != null) {
                        // At least one of the selected objects is an EObject, clear current selection
                        if (!cleared) {
                            clearSelection();
                            cleared = true;
                        }
                        addToSelection(nextEObject);
                    }
                }
                // If the selection changed somehow, relaunch the real-time evaluation
                if (cleared && realTimeThread != null) {
                    realTimeThread.setDirty();
                }
            }
        }
    }

    /**
     * This implementation of a Thread will be used to wrap an evaluation task as returned by the
     * LanguageInterpreter, then asynchronously update the form with all error messages (if any) that were
     * raised by this compilation task. Afterwards, this Thread will update the result view of the interpreter
     * form.
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
     */
    private class EvaluationThread extends Thread {
        /**
         * We will set this flag on {@link #interrupt()} in order to determine whether the thread was
         * cancelled (which could happen <b>after</b> the thread has completed, which would make the
         * "interrupted" flag quiet.
         */
        private boolean cancelled;

        /** The evaluation thread which result we are to wait for. */
        private Future<EvaluationResult> evaluationTask;

        /** Context of the interpreter as it was when this Thread has been created. */
        private final InterpreterContext interpreterContext;

        /**
         * Instantiates our Thread given the initial interpreter context.
         * 
         * @param interpreterContext
         *            The initial interpreter context.
         */
        public EvaluationThread(InterpreterContext interpreterContext) {
            super("InterpreterEvaluationThread"); //$NON-NLS-1$
            this.interpreterContext = interpreterContext;
        }

        /**
         * {@inheritDoc}
         * 
         * @see java.lang.Thread#interrupt()
         */
        @Override
        public void interrupt() {
            cancelled = true;
            if (evaluationTask != null) {
                evaluationTask.cancel(true);
            }
            super.interrupt();
        }

        /**
         * {@inheritDoc}
         * 
         * @see java.lang.Thread#run()
         */
        @Override
        public void run() {
            try {
                checkCancelled();
                Display.getDefault().syncExec(new Runnable() {
                    public void run() {
                        getForm().setBusy(true);
                    }
                });
                // Cannot do anything before the current compilation thread stops.
                if (compilationThread != null) {
                    compilationThread.join();
                }
                checkCancelled();

                Callable<EvaluationResult> evaluationCallable = getCurrentLanguageInterpreter()
                        .getEvaluationTask(new EvaluationContext(interpreterContext, compilationResult));
                evaluationTask = evaluationPool.submit(evaluationCallable);

                final EvaluationResult result = evaluationTask.get();
                checkCancelled();

                if (result != null) {
                    Display.getDefault().asyncExec(new Runnable() {
                        /**
                         * {@inheritDoc}
                         * 
                         * @see java.lang.Runnable#run()
                         */
                        public void run() {
                            clearEvaluationMessages();
                            if (result.getStatus() != null) {
                                addStatusMessages(result.getStatus(), EVALUATION_MESSAGE_PREFIX);
                            }
                            // whether there were problems or not, try and update the result viewer.
                            setEvaluationResult(result);
                        }
                    });
                }
            } catch (InterruptedException e) {
                // Thread is expected to be cancelled if another is started
            } catch (CancellationException e) {
                // Thread is expected to be cancelled if another is started
            } catch (ExecutionException e) {
                String message = e.getMessage();
                if (e.getCause() != null) {
                    message = e.getCause().getMessage();
                }
                message = e.getClass().getName() + ' ' + message;
                final IStatus status = new Status(IStatus.ERROR, InterpreterPlugin.PLUGIN_ID, message);
                final EvaluationResult result = new EvaluationResult(status);

                Display.getDefault().asyncExec(new Runnable() {
                    /**
                     * {@inheritDoc}
                     * 
                     * @see java.lang.Runnable#run()
                     */
                    public void run() {
                        clearEvaluationMessages();
                        addStatusMessages(status, EVALUATION_MESSAGE_PREFIX);
                        setEvaluationResult(result);
                    }
                });
            } finally {
                Display.getDefault().syncExec(new Runnable() {
                    public void run() {
                        getForm().setBusy(false);
                    }
                });
            }
        }

        /**
         * Throws a new {@link CancellationException} if the current thread has been cancelled.
         */
        protected void checkCancelled() {
            if (cancelled) {
                throw new CancellationException();
            }
        }
    }

    /**
     * This daemon thread will be launched whenever the "real-time" toggle is activated, and will only be
     * stopped when the view is disposed or the "real-time" toggle is disabled.
     * <p>
     * This Thread will be constantly reset on modifications of the expression viewer, and will only really
     * start its work if the expression is left untouched for a given count of seconds.
     * </p>
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
     */
    private class RealTimeThread extends Thread {
        /** Time to wait before launching the evaluation (0.5 second by default). */
        private static final int DELAY = 500;

        /** This will be set to <code>true</code> whenever we need to recompile the expression. */
        private boolean dirty;

        /** The lock we'll acquire for this thread's work. */
        private final Object lock = new Object();

        /** This will be set to <code>true</code> whenever we should reset this thread's timer. */
        private boolean reset;

        /**
         * Instantiates the real-time evaluation thread.
         */
        public RealTimeThread() {
            super("InterpreterRealTimeThread"); //$NON-NLS-1$
            setPriority(Thread.MIN_PRIORITY);
            setDaemon(true);
        }

        /**
         * Resets this thread's timer.
         */
        public void reset() {
            synchronized (this) {
                reset = true;
            }
        }

        /**
         * {@inheritDoc}
         * 
         * @see java.lang.Thread#run()
         */
        @Override
        public void run() {
            while (!Thread.interrupted()) {
                synchronized (lock) {
                    try {
                        lock.wait(DELAY);
                    } catch (InterruptedException e) {
                        // This is expected
                    }
                }

                synchronized (this) {
                    if (reset) {
                        reset = false;
                        // If a reset has been asked for, stop this iteration
                        continue;
                    }
                    if (dirty) {
                        dirty = false;
                    } else {
                        // The expression does not need to be recompiled
                        continue;
                    }
                }

                Display.getDefault().asyncExec(new Runnable() {
                    /**
                     * {@inheritDoc}
                     * 
                     * @see java.lang.Runnable#run()
                     */
                    public void run() {
                        compileExpression();
                        evaluate();
                    }
                });
            }
        }

        /**
         * Sets the "dirty" state of this thread to indicate the expression needs to be recompiled.
         */
        public void setDirty() {
            synchronized (this) {
                dirty = true;
            }
        }
    }
}