/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.rubypeople.rdt.internal.debug.ui.actions;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.Iterator;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.ISourceLocator;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.IDebugModelPresentation;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.debug.ui.IDebugView;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorActionDelegate;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IViewActionDelegate;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.texteditor.ITextEditor;
import org.rubypeople.rdt.debug.core.RdtDebugCorePlugin;
import org.rubypeople.rdt.debug.core.model.IEvaluationResult;
import org.rubypeople.rdt.debug.core.model.IRubyStackFrame;
import org.rubypeople.rdt.debug.core.model.IRubyValue;
import org.rubypeople.rdt.debug.core.model.IRubyVariable;
import org.rubypeople.rdt.debug.ui.RdtDebugUiConstants;
import org.rubypeople.rdt.internal.debug.ui.RdtDebugUiPlugin;
import org.rubypeople.rdt.internal.debug.ui.display.IDataDisplay;
import org.rubypeople.rdt.internal.debug.ui.display.RubyInspectExpression;
import org.rubypeople.rdt.internal.ui.text.RubyWordFinder;
/**
* Action to do simple code evaluation. The evaluation is done in the UI thread and the expression and result are
* displayed using the IDataDisplay.
*/
public abstract class EvaluateAction implements IWorkbenchWindowActionDelegate, IObjectActionDelegate,
IEditorActionDelegate, IPartListener, IViewActionDelegate
{
private IAction fAction;
private IWorkbenchPart fTargetPart;
private IWorkbenchWindow fWindow;
private Object fSelection;
private IRegion fRegion;
/**
* Is the action waiting for an evaluation.
*/
private boolean fEvaluating;
/**
* The new target part to use with the evaluation completes.
*/
private IWorkbenchPart fNewTargetPart = null;
/**
* Used to resolve editor input for selected stack frame
*/
private IDebugModelPresentation fPresentation;
public EvaluateAction()
{
super();
}
/**
* Returns the 'object' context for this evaluation, or <code>null</code> if none. If the evaluation is being
* performed in the context of the variables view/inspector. Then perform the evaluation in the context of the
* selected value.
*
* @return Ruby object or <code>null</code>
*/
protected IRubyValue getObjectContext()
{
IWorkbenchPage page = RdtDebugUiPlugin.getActivePage();
if (page == null)
return null;
IWorkbenchPart activePart = page.getActivePart();
if (activePart == null)
return null;
IDebugView a = (IDebugView) activePart.getAdapter(IDebugView.class);
if (a == null || a.getViewer() == null)
return null;
ISelection s = a.getViewer().getSelection();
if (!(s instanceof IStructuredSelection))
return null;
IStructuredSelection structuredSelection = (IStructuredSelection) s;
if (structuredSelection.size() != 1)
return null;
Object selection = structuredSelection.getFirstElement();
if (selection instanceof IRubyVariable)
{
IRubyVariable var = (IRubyVariable) selection;
// if 'this' is selected, use stack frame context
try
{
if (!var.getName().equals("this")) { //$NON-NLS-1$
IValue value = var.getValue();
if (value instanceof IRubyValue)
{
return (IRubyValue) value;
}
}
}
catch (DebugException e)
{
RdtDebugUiPlugin.log(e);
}
}
else if (selection instanceof RubyInspectExpression)
{
IValue value = ((RubyInspectExpression) selection).getValue();
if (value instanceof IRubyValue)
{
return (IRubyValue) value;
}
}
return null;
}
/**
* Finds the currently selected stack frame in the UI. Stack frames from a scrapbook launch are ignored.
*/
protected IRubyStackFrame getStackFrameContext()
{
try
{
IWorkbenchPart part = getTargetPart();
if (part == null)
{
return RdtDebugUiPlugin.getEvaluationContextManager().getEvaluationContext(getWindow());
}
return RdtDebugUiPlugin.getEvaluationContextManager().getEvaluationContext(part);
}
catch (Throwable e)
{
RdtDebugUiPlugin.log(e);
return null;
}
}
/**
* @see IEvaluationListener#evaluationComplete(IEvaluationResult)
*/
public void evaluationComplete(final IEvaluationResult result)
{
// if plug-in has shutdown, ignore - see bug# 8693
if (RdtDebugUiPlugin.getDefault() == null)
{
return;
}
final IValue value = result.getValue();
if (result.hasErrors() || value != null)
{
final Display display = RdtDebugUiPlugin.getStandardDisplay();
if (display.isDisposed())
{
return;
}
displayResult(result);
}
}
protected void evaluationCleanup()
{
setEvaluating(false);
setTargetPart(fNewTargetPart);
}
/**
* Display the given evaluation result.
*/
abstract protected void displayResult(IEvaluationResult result);
protected void run()
{
// eval in context of object or stack frame
final IRubyValue object = getObjectContext();
final IRubyStackFrame stackFrame = getStackFrameContext();
if (stackFrame == null)
{
reportError(ActionMessages.Evaluate_error_message_stack_frame_context);
return;
}
// check for nested evaluation
IThread thread = (IThread) stackFrame.getThread();
// if (thread.isPerformingEvaluation()) {
// reportError(ActionMessages.EvaluateAction_Cannot_perform_nested_evaluations__1);
// return;
// }
setNewTargetPart(getTargetPart());
IRunnableWithProgress runnable = new IRunnableWithProgress()
{
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException
{
if (stackFrame.isSuspended())
{
Object selection = getSelectedObject();
if (!(selection instanceof String))
{
return;
}
String expression = (String) selection;
setEvaluating(true);
IEvaluationResult result = stackFrame.evaluate(expression);
evaluationComplete(result);
return;
}
// thread not suspended
throw new InvocationTargetException(null,
ActionMessages.EvaluateAction_Thread_not_suspended___unable_to_perform_evaluation__1);
}
};
IWorkbench workbench = RdtDebugUiPlugin.getDefault().getWorkbench();
try
{
workbench.getProgressService().busyCursorWhile(runnable);
}
catch (InvocationTargetException e)
{
evaluationCleanup();
String message = e.getMessage();
if (message == null)
{
message = e.getClass().getName();
if (e.getCause() != null)
{
message = e.getCause().getClass().getName();
if (e.getCause().getMessage() != null)
{
message = e.getCause().getMessage();
}
}
}
reportError(message);
}
catch (InterruptedException e)
{
}
}
/**
* Updates the enabled state of the action that this is a delegate for.
*/
protected void update()
{
IAction action = getAction();
if (action != null)
{
resolveSelectedObject();
}
}
/**
* Resolves the selected object in the target part, or <code>null</code> if there is no selection.
*/
protected void resolveSelectedObject()
{
Object selectedObject = null;
fRegion = null;
ISelection selection = getTargetSelection();
if (selection instanceof ITextSelection)
{
ITextSelection ts = (ITextSelection) selection;
String text = ts.getText();
if (textHasContent(text))
{
selectedObject = text;
fRegion = new Region(ts.getOffset(), ts.getLength());
}
else if (getTargetPart() instanceof IEditorPart)
{
IEditorPart editor = (IEditorPart) getTargetPart();
if (editor instanceof ITextEditor)
{
selectedObject = resolveSelectedObjectUsingToken(selectedObject, ts, editor);
}
}
}
else if (selection instanceof IStructuredSelection)
{
if (!selection.isEmpty())
{
if (getTargetPart().getSite().getId().equals(IDebugUIConstants.ID_DEBUG_VIEW))
{
// work on the editor selection
IEditorPart editor = getTargetPart().getSite().getPage().getActiveEditor();
setTargetPart(editor);
selection = getTargetSelection();
if (selection instanceof ITextSelection)
{
ITextSelection ts = (ITextSelection) selection;
String text = ts.getText();
if (textHasContent(text))
{
selectedObject = text;
}
else if (editor instanceof ITextEditor)
{
selectedObject = resolveSelectedObjectUsingToken(selectedObject, ts, editor);
}
}
}
else
{
IStructuredSelection ss = (IStructuredSelection) selection;
Iterator elements = ss.iterator();
while (elements.hasNext())
{
if (!(elements.next() instanceof IRubyVariable))
{
setSelectedObject(null);
return;
}
}
selectedObject = ss;
}
}
}
setSelectedObject(selectedObject);
}
private Object resolveSelectedObjectUsingToken(Object selectedObject, ITextSelection ts, IEditorPart editor)
{
ITextEditor textEditor = (ITextEditor) editor;
IDocument doc = textEditor.getDocumentProvider().getDocument(editor.getEditorInput());
fRegion = RubyWordFinder.findWord(doc, ts.getOffset());
if (fRegion != null)
{
try
{
selectedObject = doc.get(fRegion.getOffset(), fRegion.getLength());
}
catch (BadLocationException e)
{
}
}
return selectedObject;
}
protected ISelection getTargetSelection()
{
IWorkbenchPart part = getTargetPart();
if (part != null)
{
ISelectionProvider provider = part.getSite().getSelectionProvider();
if (provider != null)
{
return provider.getSelection();
}
}
return null;
}
/**
* Resolve an editor input from the source element of the stack frame argument, and return whether it's equal to the
* editor input for the editor that owns this action.
*/
protected boolean compareToEditorInput(IStackFrame stackFrame)
{
ILaunch launch = stackFrame.getLaunch();
if (launch == null)
{
return false;
}
ISourceLocator locator = launch.getSourceLocator();
if (locator == null)
{
return false;
}
Object sourceElement = locator.getSourceElement(stackFrame);
if (sourceElement == null)
{
return false;
}
IEditorInput sfEditorInput = getDebugModelPresentation().getEditorInput(sourceElement);
if (getTargetPart() instanceof IEditorPart)
{
return ((IEditorPart) getTargetPart()).getEditorInput().equals(sfEditorInput);
}
return false;
}
protected Shell getShell()
{
if (getTargetPart() != null)
{
return getTargetPart().getSite().getShell();
}
return RdtDebugUiPlugin.getActiveWorkbenchShell();
}
protected IDataDisplay getDataDisplay()
{
IDataDisplay display = getDirectDataDisplay();
if (display != null)
{
return display;
}
IWorkbenchPage page = RdtDebugUiPlugin.getActivePage();
if (page != null)
{
IWorkbenchPart activePart = page.getActivePart();
if (activePart != null)
{
IViewPart view = page.findView(RdtDebugUiConstants.ID_DISPLAY_VIEW);
if (view == null)
{
try
{
view = page.showView(RdtDebugUiConstants.ID_DISPLAY_VIEW);
}
catch (PartInitException e)
{
RdtDebugUiPlugin.errorDialog(ActionMessages.EvaluateAction_Cannot_open_Display_view, e);
}
finally
{
page.activate(activePart);
}
}
if (view != null)
{
page.bringToTop(view);
return (IDataDisplay) view.getAdapter(IDataDisplay.class);
}
}
}
return null;
}
protected IDataDisplay getDirectDataDisplay()
{
IWorkbenchPart part = getTargetPart();
if (part != null)
{
IDataDisplay display = (IDataDisplay) part.getAdapter(IDataDisplay.class);
if (display != null)
{
IWorkbenchPage page = RdtDebugUiPlugin.getActivePage();
if (page != null)
{
IWorkbenchPart activePart = page.getActivePart();
if (activePart != null)
{
if (activePart != part)
{
page.activate(part);
}
}
}
return display;
}
}
IWorkbenchPage page = RdtDebugUiPlugin.getActivePage();
if (page != null)
{
IWorkbenchPart activePart = page.getActivePart();
if (activePart != null)
{
IDataDisplay display = (IDataDisplay) activePart.getAdapter(IDataDisplay.class);
if (display != null)
{
return display;
}
}
}
return null;
}
protected boolean textHasContent(String text)
{
if (text != null)
{
int length = text.length();
if (length > 0)
{
for (int i = 0; i < length; i++)
{
if (Character.isLetterOrDigit(text.charAt(i)))
{
return true;
}
}
}
}
return false;
}
/**
* Displays a failed evaluation message in the data display.
*/
protected void reportErrors(IEvaluationResult result)
{
String message = getErrorMessage(result);
reportError(message);
}
protected void reportError(String message)
{
IDataDisplay dataDisplay = getDirectDataDisplay();
if (dataDisplay != null)
{
if (message.length() != 0)
{
dataDisplay.displayExpressionValue(MessageFormat.format(
ActionMessages.EvaluateAction__evaluation_failed__Reason, new String[] { format(message) }));
}
else
{
dataDisplay.displayExpressionValue(ActionMessages.EvaluateAction__evaluation_failed__1);
}
}
else
{
Status status = new Status(IStatus.ERROR, RdtDebugUiPlugin.getUniqueIdentifier(), IStatus.ERROR, message,
null);
ErrorDialog.openError(getShell(), ActionMessages.Evaluate_error_title_eval_problems, null, status);
}
}
private String format(String message)
{
StringBuffer result = new StringBuffer();
int index = 0, pos;
while ((pos = message.indexOf('\n', index)) != -1)
{
result.append("\t\t").append(message.substring(index, index = pos + 1)); //$NON-NLS-1$
}
if (index < message.length())
{
result.append("\t\t").append(message.substring(index)); //$NON-NLS-1$
}
return result.toString();
}
public static String getExceptionMessage(Throwable exception)
{
if (exception instanceof CoreException)
{
CoreException ce = (CoreException) exception;
Throwable throwable = ce.getStatus().getException();
if (throwable instanceof CoreException)
{
// Traverse nested CoreExceptions
return getExceptionMessage(throwable);
}
return ce.getStatus().getMessage();
}
String message = MessageFormat.format(ActionMessages.Evaluate_error_message_direct_exception,
new Object[] { exception.getClass() });
if (exception.getMessage() != null)
{
message = MessageFormat.format(ActionMessages.Evaluate_error_message_exception_pattern, new Object[] {
message, exception.getMessage() });
}
return message;
}
protected String getErrorMessage(IEvaluationResult result)
{
String[] errors = result.getErrorMessages();
if (errors.length == 0)
{
return getExceptionMessage(result.getException());
}
return getErrorMessage(errors);
}
protected String getErrorMessage(String[] errors)
{
String message = ""; //$NON-NLS-1$
for (int i = 0; i < errors.length; i++)
{
String msg = errors[i];
if (i == 0)
{
message = msg;
}
else
{
message = MessageFormat.format(ActionMessages.Evaluate_error_problem_append_pattern, new Object[] {
message, msg });
}
}
return message;
}
/*
* (non-Javadoc)
* @see org.eclipse.ui.IActionDelegate#run(IAction)
*/
public void run(IAction action)
{
update();
run();
}
/*
* (non-Javadoc)
* @see org.eclipse.ui.IActionDelegate#selectionChanged(IAction, ISelection)
*/
public void selectionChanged(IAction action, ISelection selection)
{
setAction(action);
}
/**
* @see IWorkbenchWindowActionDelegate#dispose()
*/
public void dispose()
{
disposeDebugModelPresentation();
IWorkbenchWindow win = getWindow();
if (win != null)
{
win.getPartService().removePartListener(this);
}
}
/**
* @see IWorkbenchWindowActionDelegate#init(IWorkbenchWindow)
*/
public void init(IWorkbenchWindow window)
{
setWindow(window);
IWorkbenchPage page = window.getActivePage();
if (page != null)
{
setTargetPart(page.getActivePart());
}
window.getPartService().addPartListener(this);
update();
}
protected IAction getAction()
{
return fAction;
}
protected void setAction(IAction action)
{
fAction = action;
}
/**
* Returns a debug model presentation (creating one if necessary).
*
* @return debug model presentation
*/
protected IDebugModelPresentation getDebugModelPresentation()
{
if (fPresentation == null)
{
fPresentation = DebugUITools.newDebugModelPresentation(RdtDebugCorePlugin.getPluginIdentifier());
}
return fPresentation;
}
/**
* Disposes this action's debug model presentation, if one was created.
*/
protected void disposeDebugModelPresentation()
{
if (fPresentation != null)
{
fPresentation.dispose();
}
}
/**
* @see IEditorActionDelegate#setActiveEditor(IAction, IEditorPart)
*/
public void setActiveEditor(IAction action, IEditorPart targetEditor)
{
setAction(action);
setTargetPart(targetEditor);
}
/**
* @see IPartListener#partActivated(IWorkbenchPart)
*/
public void partActivated(IWorkbenchPart part)
{
setTargetPart(part);
}
/**
* @see IPartListener#partBroughtToTop(IWorkbenchPart)
*/
public void partBroughtToTop(IWorkbenchPart part)
{
}
/**
* @see IPartListener#partClosed(IWorkbenchPart)
*/
public void partClosed(IWorkbenchPart part)
{
if (part == getTargetPart())
{
setTargetPart(null);
}
if (part == getNewTargetPart())
{
setNewTargetPart(null);
}
}
/**
* @see IPartListener#partDeactivated(IWorkbenchPart)
*/
public void partDeactivated(IWorkbenchPart part)
{
}
/**
* @see IPartListener#partOpened(IWorkbenchPart)
*/
public void partOpened(IWorkbenchPart part)
{
}
/**
* @see IViewActionDelegate#init(IViewPart)
*/
public void init(IViewPart view)
{
setTargetPart(view);
}
protected IWorkbenchPart getTargetPart()
{
return fTargetPart;
}
protected void setTargetPart(IWorkbenchPart part)
{
if (isEvaluating())
{
// do not want to change the target part while evaluating
// see bug 8334
setNewTargetPart(part);
}
else
{
fTargetPart = part;
}
}
protected IWorkbenchWindow getWindow()
{
return fWindow;
}
protected void setWindow(IWorkbenchWindow window)
{
fWindow = window;
}
/**
* @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
*/
public void setActivePart(IAction action, IWorkbenchPart targetPart)
{
setAction(action);
setTargetPart(targetPart);
update();
}
protected Object getSelectedObject()
{
return fSelection;
}
protected void setSelectedObject(Object selection)
{
fSelection = selection;
}
protected IWorkbenchPart getNewTargetPart()
{
return fNewTargetPart;
}
protected void setNewTargetPart(IWorkbenchPart newTargetPart)
{
fNewTargetPart = newTargetPart;
}
protected boolean isEvaluating()
{
return fEvaluating;
}
protected void setEvaluating(boolean evaluating)
{
fEvaluating = evaluating;
}
/**
* Returns the selected text region, or <code>null</code> if none.
*
* @return
*/
protected IRegion getRegion()
{
return fRegion;
}
/**
* Computes an anchor point for a popup dialog on top of a text viewer.
*
* @param viewer
* @return desired anchor point
*/
public static Point getPopupAnchor(ITextViewer viewer)
{
StyledText textWidget = viewer.getTextWidget();
Point docRange = textWidget.getSelectionRange();
int midOffset = docRange.x + (docRange.y / 2);
Point point = textWidget.getLocationAtOffset(midOffset);
point = textWidget.toDisplay(point);
GC gc = new GC(textWidget);
gc.setFont(textWidget.getFont());
int height = gc.getFontMetrics().getHeight();
gc.dispose();
point.y += height;
return point;
}
}