/******************************************************************************
* Copyright (C) 2013 Jonah Graham
*
* 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:
* Jonah Graham <jonah@kichwacoders.com> - initial API and implementation
******************************************************************************/
package org.python.pydev.debug.model.remote;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IWatchExpression;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.python.pydev.core.log.Log;
import org.python.pydev.debug.handlers.PrettyPrintCommandHandler;
import org.python.pydev.debug.model.AbstractDebugTarget;
import org.python.pydev.debug.model.IVariableLocator;
import org.python.pydev.debug.model.PyStackFrame;
import org.python.pydev.debug.model.PyVariable;
import org.python.pydev.shared_core.structure.Tuple;
/**
* Run a custom bit of Python in the context of the specified debug target.
* <p>
* This command takes a variable or expression (expressed as an {@link IVariableLocator#getPyDBLocation()} style
* location) and passes it to the function provided in the constructor. The constructor also takes either a code
* snippet that should define the function, or a file to execfile that should define the function.
* <p>
* Once created, the command should be posted to the target with {@link AbstractDebugTarget#postCommand(AbstractDebuggerCommand)}.
* Optionally, the function run on the target can return a string for further processing. In this case the command's
* {@link #setCompletionListener(ICommandResponseListener)} should be set and on completion, {@link #getResponsePayload()}
* can be used to obtain the returned value.
* <p>
* For an example, see {@link PrettyPrintCommandHandler}
*/
public class RunCustomOperationCommand extends AbstractDebuggerCommand {
private String encodedCodeOrFile;
private String operationFnName;
private IVariableLocator locator;
private String style;
private String responsePayload;
/**
* Extracts a debug target and locator from the provided selection.
* <p>
* The Eclipse definition org.python.pydev.debug.model.RunCustomOperationCommand.ExtractableContext can
* be used to filter menu presentation on whether the selection may be extractable.
*
* @param selection
* @return Debug target and locator suitable for passing the the constructor,
* or <code>null</code> if no suitable selection is selected.
*/
public static Tuple<AbstractDebugTarget, IVariableLocator> extractContextFromSelection(ISelection selection) {
if (selection instanceof StructuredSelection) {
StructuredSelection structuredSelection = (StructuredSelection) selection;
Object elem = structuredSelection.getFirstElement();
if (elem instanceof PyVariable) {
PyVariable pyVar = (PyVariable) elem;
AbstractDebugTarget target = (AbstractDebugTarget) pyVar.getDebugTarget();
return new Tuple<AbstractDebugTarget, IVariableLocator>(target, pyVar);
} else if (elem instanceof IWatchExpression) {
IWatchExpression expression = (IWatchExpression) elem;
final String expressionText = expression.getExpressionText();
IDebugTarget debugTarget = expression.getDebugTarget();
if (debugTarget instanceof AbstractDebugTarget) {
AbstractDebugTarget target = (AbstractDebugTarget) debugTarget;
IAdaptable context = DebugUITools.getDebugContext();
final PyStackFrame stackFrame = (PyStackFrame) context.getAdapter(PyStackFrame.class);
if (stackFrame != null) {
return new Tuple<AbstractDebugTarget, IVariableLocator>(target, new IVariableLocator() {
@Override
public String getThreadId() {
return stackFrame.getThreadId();
}
@Override
public String getPyDBLocation() {
String locator = stackFrame.getExpressionLocator().getPyDBLocation();
return locator + "\t" + expressionText;
}
});
}
}
}
}
return null;
}
private RunCustomOperationCommand(AbstractDebugTarget target, IVariableLocator locator,
String style, String codeOrFile, String operationFnName) {
super(target);
this.locator = locator;
this.style = style;
this.encodedCodeOrFile = encode(codeOrFile);
this.operationFnName = operationFnName;
}
/**
* Create a new command to run with the function defined in a string.
*
* @param target Debug Target to run on
* @param locator Location of variable or expression.
* @param operationSource Definition of the function to be run (this code is "exec"ed by the target)
* @param operationFnName Function to call, must be defined by operationSource
*/
public RunCustomOperationCommand(AbstractDebugTarget target, IVariableLocator locator,
String operationSource, String operationFnName) {
this(target, locator, "EXEC", operationSource, operationFnName);
}
/**
* Create a new command to run with the function defined in a file.
*
* @param target Debug Target to run on
* @param locator Location of variable or expression.
* @param operationPyFile Definition of the function to be run (this file is "execfile"d by the target)
* @param operationFnName Function to call, must be defined by operationSource
*/
public RunCustomOperationCommand(AbstractDebugTarget target, IVariableLocator locator,
File operationPyFile, String operationFnName) {
this(target, locator, "EXECFILE", operationPyFile.toString(), operationFnName);
}
@Override
public String getOutgoing() {
String payload = locator.getPyDBLocation() + "||" + style + "\t" + encodedCodeOrFile + "\t" + operationFnName;
String cmd = makeCommand(CMD_RUN_CUSTOM_OPERATION, sequence, payload);
return cmd;
}
@Override
public boolean needResponse() {
return true;
}
@Override
public void processOKResponse(int cmdCode, String payload) {
if (cmdCode == CMD_RUN_CUSTOM_OPERATION) {
this.responsePayload = decode(payload);
}
}
/**
* Return the response received from the custom command
* @return the response or <code>null</code> if an error or no response has been received.
*/
public String getResponsePayload() {
return responsePayload;
}
private static String encode(String in) {
try {
return URLEncoder.encode(in, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.log("Unreachable? UTF-8 is always supported.", e);
return "";
}
}
private static String decode(String in) {
try {
return URLDecoder.decode(in, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.log("Unreachable? UTF-8 is always supported.", e);
return "";
}
}
}