/** * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package org.python.pydev.debug.newconsole; import java.io.IOException; import java.net.Socket; import java.net.SocketTimeoutException; 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.SubProgressMonitor; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.model.IProcess; import org.eclipse.ui.console.IConsoleFactory; import org.python.pydev.core.IInterpreterInfo; import org.python.pydev.core.IPythonNature; import org.python.pydev.core.log.Log; import org.python.pydev.debug.core.PydevDebugPlugin; import org.python.pydev.debug.model.PyDebugTargetConsole; import org.python.pydev.debug.model.PyStackFrame; import org.python.pydev.debug.model.remote.ListenConnector; import org.python.pydev.debug.model.remote.RemoteDebuggerConsole; import org.python.pydev.debug.newconsole.env.PydevIProcessFactory; import org.python.pydev.debug.newconsole.env.PydevIProcessFactory.PydevConsoleLaunchInfo; import org.python.pydev.debug.newconsole.env.JythonEclipseProcess; import org.python.pydev.debug.newconsole.env.UserCanceledException; import org.python.pydev.debug.newconsole.prefs.InteractiveConsolePrefs; import org.python.pydev.editor.preferences.PydevEditorPrefs; import org.python.pydev.plugin.preferences.PydevPrefs; import com.aptana.interactive_console.InteractiveConsolePlugin; import com.aptana.interactive_console.console.ui.ScriptConsoleManager; /** * Could ask to configure the interpreter in the preferences * * PreferencesUtil.createPreferenceDialogOn(null, preferencePageId, null, null) * * This is the class responsible for creating the console (and setting up the communication * between the console server and the client). * * @author Fabio */ public class PydevConsoleFactory implements IConsoleFactory { /** * @see IConsoleFactory#openConsole() */ public void openConsole() { createConsole(null); } /** * @return a new PydevConsole or null if unable to create it (user cancels it) */ public void createConsole(String additionalInitialComands) { try { PydevConsoleInterpreter interpreter = createDefaultPydevInterpreter(); if (interpreter == null) { return; } if (interpreter.getFrame() == null) { createConsole(interpreter, additionalInitialComands); } else { createDebugConsole(interpreter.getFrame(), additionalInitialComands); } } catch (Exception e) { Log.log(e); } } public void createConsole(final PydevConsoleInterpreter interpreter, final String additionalInitialComands) { Job job = new Job("Create Interactive Console") { @Override protected IStatus run(IProgressMonitor monitor) { monitor.beginTask("Create Interactive Console", 10); IStatus returnStatus = Status.OK_STATUS; try { ScriptConsoleManager manager = ScriptConsoleManager.getInstance(); PydevConsole console; if (interpreter.getFrame() == null) { monitor.worked(1); console = new PydevConsole(interpreter, additionalInitialComands); monitor.worked(1); try { createDebugTarget(interpreter, console, new SubProgressMonitor(monitor, 8)); } catch (UserCanceledException uce) { return Status.CANCEL_STATUS; } catch (Exception e) { //Just set the return status, but keep on going to add the console to the manager (as the message says). returnStatus = PydevDebugPlugin .makeStatus( IStatus.ERROR, "Unable to connect debugger to Interactive Console\n" + "The interactive console will continue to operate without the additional debugger features", e); } manager.add(console, true); } } catch (Exception e) { Log.log(e); returnStatus = PydevDebugPlugin.makeStatus(IStatus.ERROR, "Error initializing console.", e); } finally { monitor.done(); } return returnStatus; } }; job.setUser(true); job.schedule(); } private void createDebugTarget(PydevConsoleInterpreter interpreter, PydevConsole console, IProgressMonitor monitor) throws IOException, CoreException, DebugException, UserCanceledException { monitor.beginTask("Connect Debug Target", 2); try { // Jython within Eclipse does not yet support these new features Process process = interpreter.getProcess(); if (InteractiveConsolePrefs.getConsoleConnectVariableView() && !(process instanceof JythonEclipseProcess)) { PydevConsoleCommunication consoleCommunication = (PydevConsoleCommunication) interpreter .getConsoleCommunication(); try { consoleCommunication.hello(new SubProgressMonitor(monitor, 1)); } catch (Exception ex) { try { if (ex instanceof UserCanceledException) { //Only close the console communication if the user actually cancelled (otherwise the user will expect it to still be working). consoleCommunication.close(); } } catch (Exception e) { // Don't hide important information from user Log.log(e); } if (ex instanceof UserCanceledException) { UserCanceledException userCancelled = (UserCanceledException) ex; throw userCancelled; } String message = "Unexpected error setting up the debugger connection. "; if (ex instanceof SocketTimeoutException) { message = "Timed out after " + InteractiveConsolePrefs.getMaximumAttempts() + " attempts to connect to the console."; } throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, message, ex)); } int acceptTimeout = PydevPrefs.getPreferences().getInt(PydevEditorPrefs.CONNECT_TIMEOUT); PyDebugTargetConsole pyDebugTargetConsole = null; IProcess eclipseProcess = interpreter.getLaunch().getProcesses()[0]; RemoteDebuggerConsole debugger = new RemoteDebuggerConsole(); ListenConnector connector = new ListenConnector(acceptTimeout); debugger.startConnect(connector); pyDebugTargetConsole = new PyDebugTargetConsole(consoleCommunication, interpreter.getLaunch(), eclipseProcess, debugger); Socket socket = null; try { consoleCommunication.connectToDebugger(connector.getLocalPort()); socket = debugger.waitForConnect(monitor, process, eclipseProcess); if (socket == null) { throw new UserCanceledException("Cancelled"); } } catch (Exception ex) { try { if (ex instanceof UserCanceledException) { //Only close the console communication if the user actually cancelled (otherwise the user will expect it to still be working). consoleCommunication.close(); debugger.dispose(); //Can't terminate the process either! } else { //But we still want to dispose of the connector. debugger.disposeConnector(); } } catch (Exception e) { // Don't hide important information from user Log.log(e); } if (ex instanceof UserCanceledException) { UserCanceledException userCancelled = (UserCanceledException) ex; throw userCancelled; } String message = "Unexpected error setting up the debugger"; if (ex instanceof SocketTimeoutException) { message = "Timed out after " + Float.toString(acceptTimeout / 1000) + " seconds while waiting for python script to connect."; } throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, message, ex)); } pyDebugTargetConsole.startTransmission(socket); // this starts reading/writing from sockets pyDebugTargetConsole.initialize(); consoleCommunication.setDebugTarget(pyDebugTargetConsole); interpreter.getLaunch().addDebugTarget(pyDebugTargetConsole); ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); launchManager.addLaunch(interpreter.getLaunch()); pyDebugTargetConsole.setConsole(console); console.setProcess(pyDebugTargetConsole.getProcess()); pyDebugTargetConsole.finishedInit = true; } } finally { monitor.done(); } } /** * Create a new Debug Console * * @param interpreter * @param additionalInitialComands */ public void createDebugConsole(PyStackFrame frame, String additionalInitialComands) throws Exception { PydevConsoleLaunchInfo launchAndProcess = new PydevConsoleLaunchInfo(null, null, 0, null, frame); PydevConsoleInterpreter interpreter = createPydevDebugInterpreter(launchAndProcess); ScriptConsoleManager manager = ScriptConsoleManager.getInstance(); PydevDebugConsole console = new PydevDebugConsole(interpreter, additionalInitialComands); manager.add(console, true); } /** * @return A PydevConsoleInterpreter with its communication configured. * * @throws CoreException * @throws IOException * @throws UserCanceledException */ public static PydevConsoleInterpreter createDefaultPydevInterpreter() throws Exception, UserCanceledException { // import sys; sys.ps1=''; sys.ps2='' // import sys;print >> sys.stderr, ' '.join([sys.executable, sys.platform, sys.version]) // print >> sys.stderr, 'PYTHONPATH:' // for p in sys.path: // print >> sys.stderr, p // // print >> sys.stderr, 'Ok, all set up... Enjoy' PydevIProcessFactory iprocessFactory = new PydevIProcessFactory(); PydevConsoleLaunchInfo launchAndProcess = iprocessFactory.createInteractiveLaunch(); if (launchAndProcess == null) { return null; } if (launchAndProcess.interpreter != null) { return createPydevInterpreter(launchAndProcess, iprocessFactory.getNaturesUsed()); } else { return createPydevDebugInterpreter(launchAndProcess); } } // Use IProcessFactory to get the required tuple public static PydevConsoleInterpreter createPydevInterpreter(PydevConsoleLaunchInfo info, List<IPythonNature> natures) throws Exception { final ILaunch launch = info.launch; Process process = info.process; Integer clientPort = info.clientPort; IInterpreterInfo interpreterInfo = info.interpreter; if (launch == null) { return null; } PydevConsoleInterpreter consoleInterpreter = new PydevConsoleInterpreter(); int port = Integer.parseInt(launch.getAttribute(PydevIProcessFactory.INTERACTIVE_LAUNCH_PORT)); consoleInterpreter.setConsoleCommunication(new PydevConsoleCommunication(port, process, clientPort)); consoleInterpreter.setNaturesUsed(natures); consoleInterpreter.setInterpreterInfo(interpreterInfo); consoleInterpreter.setLaunch(launch); consoleInterpreter.setProcess(process); InteractiveConsolePlugin.getDefault().addConsoleLaunch(launch); consoleInterpreter.addCloseOperation(new Runnable() { public void run() { InteractiveConsolePlugin.getDefault().removeConsoleLaunch(launch); } }); return consoleInterpreter; } /** * Initialize Console Interpreter and Console Communication for the Debug Console */ public static PydevConsoleInterpreter createPydevDebugInterpreter(PydevConsoleLaunchInfo info) throws Exception { PyStackFrame frame = info.frame; PydevConsoleInterpreter consoleInterpreter = new PydevConsoleInterpreter(); consoleInterpreter.setFrame(frame); // pydev console uses running debugger as a backend consoleInterpreter.setConsoleCommunication(new PydevDebugConsoleCommunication()); return consoleInterpreter; } }