/******************************************************************************
* Copyright (C) 2012-2013 Hussain Bohra 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:
* Hussain Bohra <hussain.bohra@tavant.com> - initial API and implementation
* Fabio Zadrozny <fabiofz@gmail.com> - ongoing maintenance
******************************************************************************/
package org.python.pydev.debug.newconsole;
import java.util.ArrayList;
import java.util.List;
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.core.runtime.jobs.Job;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.python.pydev.core.log.Log;
import org.python.pydev.debug.model.PyStackFrame;
import org.python.pydev.debug.model.XMLUtils;
import org.python.pydev.shared_core.callbacks.ICallback;
import org.python.pydev.shared_core.string.StringUtils;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_interactive_console.console.IScriptConsoleCommunication;
import org.python.pydev.shared_interactive_console.console.InterpreterResponse;
/**
* This class allows console to communicate with python backend by using the existing
* debug connection.
*
* @author hussain.bohra
* @author Fabio Zadrozny
*/
public class PydevDebugConsoleCommunication implements IScriptConsoleCommunication {
private int TIMEOUT = PydevConsoleConstants.CONSOLE_TIMEOUT;
String EMPTY = StringUtils.EMPTY;
/**
* Signals that the next command added should be sent as an input to the server.
*/
private volatile boolean waitingForInput;
/**
* Input that should be sent to the server (waiting for raw_input)
*/
private volatile String inputReceived;
/**
* Helper to keep on busy loop.
*/
private volatile Object lock = new Object();
/**
* Response that should be sent back to the shell.
*/
private volatile InterpreterResponse nextResponse;
private final IPyStackFrameProvider consoleFrameProvider;
private ICallback<Object, Tuple<String, String>> onContentsReceived;
private boolean bufferedOutput;
public void setBufferedOutput(boolean bufferedOutput) {
this.bufferedOutput = bufferedOutput;
}
public boolean getBufferedOutput() {
return this.bufferedOutput;
}
public PydevDebugConsoleCommunication(boolean bufferedOutput, IPyStackFrameProvider consoleFrameProvider) {
this.consoleFrameProvider = consoleFrameProvider;
this.bufferedOutput = bufferedOutput;
}
@Override
public boolean isConnected() {
return this.consoleFrameProvider.getLastSelectedFrame() != null;
}
@Override
public void setOnContentsReceivedCallback(ICallback<Object, Tuple<String, String>> onContentsReceived) {
this.onContentsReceived = onContentsReceived;
}
@Override
public void interrupt() {
//can't interrupt in the debug console for now...
}
@Override
public void execInterpreter(final String command, final ICallback<Object, InterpreterResponse> onResponseReceived) {
nextResponse = null;
if (waitingForInput) {
inputReceived = command;
waitingForInput = false;
// the thread that we started in the last exec is still alive if we were waiting for an input.
} else {
// create a thread that'll keep locked until an answer is received from the server.
Job job = new Job("PyDev Debug Console Communication") {
@Override
protected IStatus run(IProgressMonitor monitor) {
PyStackFrame frame = consoleFrameProvider.getLastSelectedFrame();
if (frame == null) {
if (onContentsReceived != null) {
onContentsReceived.call(new Tuple<String, String>(EMPTY,
"[Invalid Frame]: Please select frame to connect the console.\n"));
}
nextResponse = new InterpreterResponse(false, false);
return Status.CANCEL_STATUS;
}
final EvaluateDebugConsoleExpression evaluateDebugConsoleExpression = new EvaluateDebugConsoleExpression(
frame);
evaluateDebugConsoleExpression.executeCommand(command, bufferedOutput);
String result = evaluateDebugConsoleExpression.waitForCommand();
try {
if (result.length() == 0) {
//timed out
if (onContentsReceived != null) {
onContentsReceived.call(new Tuple<String, String>(result, EMPTY));
}
nextResponse = new InterpreterResponse(false, false);
return Status.CANCEL_STATUS;
} else {
EvaluateDebugConsoleExpression.PydevDebugConsoleMessage consoleMessage = XMLUtils
.getConsoleMessage(result);
if (onContentsReceived != null) {
onContentsReceived.call(new Tuple<String, String>(
consoleMessage.getOutputMessage().toString(),
consoleMessage.getErrorMessage().toString()));
}
nextResponse = new InterpreterResponse(consoleMessage.isMore(), false);
}
} catch (CoreException e) {
Log.log(e);
if (onContentsReceived != null) {
onContentsReceived.call(new Tuple<String, String>(result, EMPTY));
}
nextResponse = new InterpreterResponse(false, false);
return Status.CANCEL_STATUS;
}
return Status.OK_STATUS;
}
};
job.schedule();
}
int timeOut = TIMEOUT; //only get contents each 500 millis...
// busy loop until we have a response
while (nextResponse == null) {
synchronized (lock) {
try {
lock.wait(20);
} catch (InterruptedException e) {
}
}
timeOut -= 20;
if (timeOut <= 0 && nextResponse == null) {
timeOut = TIMEOUT / 2; // after the first, get it each 250 millis
}
}
onResponseReceived.call(nextResponse);
}
@Override
public ICompletionProposal[] getCompletions(String text, String actTok, int offset, boolean showForTabCompletion)
throws Exception {
ICompletionProposal[] receivedCompletions = {};
if (waitingForInput) {
return new ICompletionProposal[0];
}
PyStackFrame frame = consoleFrameProvider.getLastSelectedFrame();
if (frame == null) {
return new ICompletionProposal[0];
}
final EvaluateDebugConsoleExpression evaluateDebugConsoleExpression = new EvaluateDebugConsoleExpression(frame);
String result = evaluateDebugConsoleExpression.getCompletions(actTok, offset);
if (result.length() > 0) {
List<Object[]> fromServer = XMLUtils.convertXMLcompletionsFromConsole(result);
List<ICompletionProposal> ret = new ArrayList<ICompletionProposal>();
PydevConsoleCommunication.convertConsoleCompletionsToICompletions(text, actTok, offset, fromServer, ret, false);
receivedCompletions = ret.toArray(new ICompletionProposal[ret.size()]);
}
return receivedCompletions;
}
@Override
public String getDescription(String text) throws Exception {
return null;
}
/**
* Enable/Disable linking of the debug console with the suspended frame.
*/
@Override
public void linkWithDebugSelection(boolean isLinkedWithDebug) {
consoleFrameProvider.linkWithDebugSelection(isLinkedWithDebug);
}
@Override
public void close() throws Exception {
//Do nothing on console close.
}
}