/****************************************************************************** * 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. } }