| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.jetbrains.python.console; |
| |
| import com.google.common.base.CharMatcher; |
| import com.google.common.base.Function; |
| import com.google.common.base.Joiner; |
| import com.google.common.collect.Collections2; |
| import com.google.common.collect.Maps; |
| import com.intellij.execution.ExecutionException; |
| import com.intellij.execution.ExecutionHelper; |
| import com.intellij.execution.Executor; |
| import com.intellij.execution.configurations.EncodingEnvironmentUtil; |
| import com.intellij.execution.configurations.GeneralCommandLine; |
| import com.intellij.execution.console.ConsoleHistoryController; |
| import com.intellij.execution.console.LanguageConsoleView; |
| import com.intellij.execution.console.ProcessBackedConsoleExecuteActionHandler; |
| import com.intellij.execution.process.CommandLineArgumentsProvider; |
| import com.intellij.execution.process.ProcessAdapter; |
| import com.intellij.execution.process.ProcessEvent; |
| import com.intellij.execution.process.ProcessOutputTypes; |
| import com.intellij.execution.runners.AbstractConsoleRunnerWithHistory; |
| import com.intellij.execution.ui.RunContentDescriptor; |
| import com.intellij.icons.AllIcons; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.Result; |
| import com.intellij.openapi.command.WriteCommandAction; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Caret; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.actionSystem.EditorAction; |
| import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; |
| import com.intellij.openapi.editor.actions.SplitLineAction; |
| import com.intellij.openapi.editor.ex.EditorEx; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleManager; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.progress.Task; |
| import com.intellij.openapi.project.DumbAware; |
| import com.intellij.openapi.project.DumbAwareAction; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.projectRoots.Sdk; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.Couple; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.io.StreamUtil; |
| import com.intellij.openapi.vfs.CharsetToolkit; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.vfs.encoding.EncodingManager; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.impl.source.tree.FileElement; |
| import com.intellij.remote.RemoteSshProcess; |
| import com.intellij.testFramework.LightVirtualFile; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.IJSwingUtilities; |
| import com.intellij.util.PathMappingSettings; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.net.NetUtils; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.xdebugger.XDebugProcess; |
| import com.intellij.xdebugger.XDebugProcessStarter; |
| import com.intellij.xdebugger.XDebugSession; |
| import com.intellij.xdebugger.XDebuggerManager; |
| import com.jetbrains.python.PythonHelpersLocator; |
| import com.jetbrains.python.console.completion.PydevConsoleElement; |
| import com.jetbrains.python.console.parsing.PythonConsoleData; |
| import com.jetbrains.python.console.pydev.ConsoleCommunication; |
| import com.jetbrains.python.debugger.PyDebugRunner; |
| import com.jetbrains.python.debugger.PySourcePosition; |
| import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase; |
| import com.jetbrains.python.remote.PyRemoteSdkCredentials; |
| import com.jetbrains.python.remote.PythonRemoteInterpreterManager; |
| import com.jetbrains.python.run.ProcessRunner; |
| import com.jetbrains.python.run.PythonCommandLineState; |
| import com.jetbrains.python.run.PythonTracebackFilter; |
| import com.jetbrains.python.sdk.PySdkUtil; |
| import com.jetbrains.python.sdk.PythonSdkType; |
| import com.jetbrains.python.sdk.flavors.PythonSdkFlavor; |
| import icons.PythonIcons; |
| import org.apache.xmlrpc.XmlRpcException; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.awt.event.InputEvent; |
| import java.awt.event.KeyEvent; |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.ServerSocket; |
| import java.nio.charset.Charset; |
| import java.util.*; |
| |
| import static com.jetbrains.python.sdk.PythonEnvUtil.setPythonIOEncoding; |
| import static com.jetbrains.python.sdk.PythonEnvUtil.setPythonUnbuffered; |
| |
| /** |
| * @author oleg |
| */ |
| public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonConsoleView> { |
| public static final String WORKING_DIR_ENV = "WORKING_DIR_AND_PYTHON_PATHS"; |
| public static final String CONSOLE_START_COMMAND = "import sys; print('Python %s on %s' % (sys.version, sys.platform))\n" + |
| "sys.path.extend([" + WORKING_DIR_ENV + "])\n"; |
| private static final Logger LOG = Logger.getInstance(PydevConsoleRunner.class.getName()); |
| @SuppressWarnings("SpellCheckingInspection") |
| public static final String PYDEV_PYDEVCONSOLE_PY = "pydev/pydevconsole.py"; |
| public static final int PORTS_WAITING_TIMEOUT = 20000; |
| |
| private Sdk mySdk; |
| @NotNull private CommandLineArgumentsProvider myCommandLineArgumentsProvider; |
| protected int[] myPorts; |
| private PydevConsoleCommunication myPydevConsoleCommunication; |
| private PyConsoleProcessHandler myProcessHandler; |
| protected PydevConsoleExecuteActionHandler myConsoleExecuteActionHandler; |
| private List<ConsoleListener> myConsoleListeners = ContainerUtil.createLockFreeCopyOnWriteList(); |
| private final PyConsoleType myConsoleType; |
| private Map<String, String> myEnvironmentVariables; |
| private String myCommandLine; |
| private String[] myStatementsToExecute = ArrayUtil.EMPTY_STRING_ARRAY; |
| private ConsoleHistoryController myHistoryController; |
| |
| public static Key<ConsoleCommunication> CONSOLE_KEY = new Key<ConsoleCommunication>("PYDEV_CONSOLE_KEY"); |
| |
| public static Key<Sdk> CONSOLE_SDK = new Key<Sdk>("PYDEV_CONSOLE_SDK_KEY"); |
| |
| private static final long APPROPRIATE_TO_WAIT = 60000; |
| private PyRemoteSdkCredentials myRemoteCredentials; |
| |
| private String myConsoleTitle = null; |
| |
| public PydevConsoleRunner(@NotNull final Project project, |
| @NotNull Sdk sdk, @NotNull final PyConsoleType consoleType, |
| @Nullable final String workingDir, |
| Map<String, String> environmentVariables, String... statementsToExecute) { |
| super(project, consoleType.getTitle(), workingDir); |
| mySdk = sdk; |
| myConsoleType = consoleType; |
| myEnvironmentVariables = environmentVariables; |
| myStatementsToExecute = statementsToExecute; |
| } |
| |
| public static PathMappingSettings getMappings(Project project, Sdk sdk) { |
| PathMappingSettings mappingSettings = null; |
| if (PySdkUtil.isRemote(sdk)) { |
| PythonRemoteInterpreterManager instance = PythonRemoteInterpreterManager.getInstance(); |
| if (instance != null) { |
| //noinspection ConstantConditions |
| mappingSettings = |
| instance.setupMappings(project, (PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData(), null); |
| } |
| } |
| return mappingSettings; |
| } |
| |
| @NotNull |
| public static Pair<Sdk, Module> findPythonSdkAndModule(@NotNull Project project, @Nullable Module contextModule) { |
| Sdk sdk = null; |
| Module module = null; |
| PyConsoleOptions.PyConsoleSettings settings = PyConsoleOptions.getInstance(project).getPythonConsoleSettings(); |
| String sdkHome = settings.getSdkHome(); |
| if (sdkHome != null) { |
| sdk = PythonSdkType.findSdkByPath(sdkHome); |
| if (settings.getModuleName() != null) { |
| module = ModuleManager.getInstance(project).findModuleByName(settings.getModuleName()); |
| } |
| else { |
| module = contextModule; |
| if (module == null && ModuleManager.getInstance(project).getModules().length > 0) { |
| module = ModuleManager.getInstance(project).getModules()[0]; |
| } |
| } |
| } |
| if (sdk == null && settings.isUseModuleSdk()) { |
| if (contextModule != null) { |
| module = contextModule; |
| } |
| else if (settings.getModuleName() != null) { |
| module = ModuleManager.getInstance(project).findModuleByName(settings.getModuleName()); |
| } |
| if (module != null) { |
| if (PythonSdkType.findPythonSdk(module) != null) { |
| sdk = PythonSdkType.findPythonSdk(module); |
| } |
| } |
| } |
| else if (contextModule != null) { |
| if (module == null) { |
| module = contextModule; |
| } |
| if (sdk == null) { |
| sdk = PythonSdkType.findPythonSdk(module); |
| } |
| } |
| |
| if (sdk == null) { |
| for (Module m : ModuleManager.getInstance(project).getModules()) { |
| if (PythonSdkType.findPythonSdk(m) != null) { |
| sdk = PythonSdkType.findPythonSdk(m); |
| module = m; |
| break; |
| } |
| } |
| } |
| if (sdk == null) { |
| if (PythonSdkType.getAllSdks().size() > 0) { |
| //noinspection UnusedAssignment |
| sdk = PythonSdkType.getAllSdks().get(0); //take any python sdk |
| } |
| } |
| return Pair.create(sdk, module); |
| } |
| |
| public static String constructPythonPathCommand(Collection<String> pythonPath, String command) { |
| final String path = Joiner.on(", ").join(Collections2.transform(pythonPath, new Function<String, String>() { |
| @Override |
| public String apply(String input) { |
| return "'" + input.replace("\\", "\\\\").replace("'", "\\'") + "'"; |
| } |
| })); |
| |
| return command.replace(WORKING_DIR_ENV, path); |
| } |
| |
| public void setStatementsToExecute(String... statementsToExecute) { |
| myStatementsToExecute = statementsToExecute; |
| } |
| |
| public static Map<String, String> addDefaultEnvironments(Sdk sdk, Map<String, String> envs) { |
| Charset defaultCharset = EncodingManager.getInstance().getDefaultCharset(); |
| |
| final String encoding = defaultCharset != null ? defaultCharset.name() : "utf-8"; |
| setPythonIOEncoding(setPythonUnbuffered(envs), encoding); |
| |
| PythonSdkFlavor.initPythonPath(envs, true, PythonCommandLineState.getAddedPaths(sdk)); |
| return envs; |
| } |
| |
| |
| @Override |
| protected List<AnAction> fillToolBarActions(final DefaultActionGroup toolbarActions, |
| final Executor defaultExecutor, |
| final RunContentDescriptor contentDescriptor) { |
| AnAction backspaceHandlingAction = createBackspaceHandlingAction(); |
| //toolbarActions.add(backspaceHandlingAction); |
| AnAction interruptAction = createInterruptAction(); |
| |
| AnAction rerunAction = createRerunAction(); |
| toolbarActions.add(rerunAction); |
| |
| List<AnAction> actions = super.fillToolBarActions(toolbarActions, defaultExecutor, contentDescriptor); |
| |
| actions.add(0, rerunAction); |
| |
| actions.add(backspaceHandlingAction); |
| actions.add(interruptAction); |
| |
| actions.add(createSplitLineAction()); |
| |
| AnAction showVarsAction = new ShowVarsAction(); |
| toolbarActions.add(showVarsAction); |
| toolbarActions.add(myHistoryController.getBrowseHistory()); |
| |
| toolbarActions.add(new ConnectDebuggerAction()); |
| return actions; |
| } |
| |
| public void runSync() { |
| myPorts = findAvailablePorts(getProject(), myConsoleType); |
| |
| assert myPorts != null; |
| |
| myCommandLineArgumentsProvider = createCommandLineArgumentsProvider(mySdk, myEnvironmentVariables, myPorts); |
| |
| try { |
| super.initAndRun(); |
| } |
| catch (ExecutionException e) { |
| LOG.warn("Error running console", e); |
| ExecutionHelper.showErrors(getProject(), Arrays.<Exception>asList(e), "Python Console", null); |
| } |
| |
| ProgressManager.getInstance().run(new Task.Backgroundable(getProject(), "Connecting to console", false) { |
| @Override |
| public void run(@NotNull final ProgressIndicator indicator) { |
| indicator.setText("Connecting to console..."); |
| connect(myStatementsToExecute); |
| } |
| }); |
| } |
| |
| public void run() { |
| UIUtil.invokeAndWaitIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| FileDocumentManager.getInstance().saveAllDocuments(); |
| } |
| }); |
| |
| myPorts = findAvailablePorts(getProject(), myConsoleType); |
| |
| assert myPorts != null; |
| |
| myCommandLineArgumentsProvider = createCommandLineArgumentsProvider(mySdk, myEnvironmentVariables, myPorts); |
| |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| ProgressManager.getInstance().run(new Task.Backgroundable(getProject(), "Connecting to console", false) { |
| @Override |
| public void run(@NotNull final ProgressIndicator indicator) { |
| indicator.setText("Connecting to console..."); |
| try { |
| initAndRun(myStatementsToExecute); |
| } |
| catch (ExecutionException e) { |
| LOG.warn("Error running console", e); |
| assert myProject != null; |
| ExecutionHelper.showErrors(myProject, Arrays.<Exception>asList(e), getTitle(), null); |
| } |
| } |
| }); |
| } |
| }); |
| } |
| |
| public static PydevConsoleRunner create(Project project, |
| Sdk sdk, |
| PyConsoleType consoleType, |
| String workingDirectory) { |
| return new PydevConsoleRunner(project, sdk, consoleType, workingDirectory, Maps.<String, String>newHashMap(), new String[]{}); |
| } |
| |
| private static int[] findAvailablePorts(Project project, PyConsoleType consoleType) { |
| final int[] ports; |
| try { |
| // File "pydev/console/pydevconsole.py", line 223, in <module> |
| // port, client_port = sys.argv[1:3] |
| ports = NetUtils.findAvailableSocketPorts(2); |
| } |
| catch (IOException e) { |
| ExecutionHelper.showErrors(project, Arrays.<Exception>asList(e), consoleType.getTitle(), null); |
| return null; |
| } |
| return ports; |
| } |
| |
| protected CommandLineArgumentsProvider createCommandLineArgumentsProvider(final Sdk sdk, |
| final Map<String, String> environmentVariables, |
| int[] ports) { |
| final ArrayList<String> args = new ArrayList<String>(); |
| args.add(sdk.getHomePath()); |
| final String versionString = sdk.getVersionString(); |
| if (versionString == null || !versionString.toLowerCase().contains("jython")) { |
| args.add("-u"); |
| } |
| args.add(FileUtil.toSystemDependentName(PythonHelpersLocator.getHelperPath(PYDEV_PYDEVCONSOLE_PY))); |
| for (int port : ports) { |
| args.add(String.valueOf(port)); |
| } |
| return new CommandLineArgumentsProvider() { |
| @Override |
| public String[] getArguments() { |
| return ArrayUtil.toStringArray(args); |
| } |
| |
| @Override |
| public boolean passParentEnvs() { |
| return false; |
| } |
| |
| @Override |
| public Map<String, String> getAdditionalEnvs() { |
| return addDefaultEnvironments(sdk, environmentVariables); |
| } |
| }; |
| } |
| |
| @Override |
| protected PythonConsoleView createConsoleView() { |
| PythonConsoleView consoleView = new PythonConsoleView(getProject(), getConsoleTitle(), mySdk); |
| myPydevConsoleCommunication.setConsoleFile(consoleView.getConsoleVirtualFile()); |
| consoleView.addMessageFilter(new PythonTracebackFilter(getProject())); |
| return consoleView; |
| } |
| |
| @Override |
| protected Process createProcess() throws ExecutionException { |
| if (PySdkUtil.isRemote(mySdk)) { |
| PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance(); |
| if (manager != null) { |
| return createRemoteConsoleProcess(manager, myCommandLineArgumentsProvider.getArguments(), |
| myCommandLineArgumentsProvider.getAdditionalEnvs()); |
| } |
| throw new PythonRemoteInterpreterManager.PyRemoteInterpreterExecutionException(); |
| } |
| else { |
| myCommandLine = myCommandLineArgumentsProvider.getCommandLineString(); |
| Map<String, String> envs = myCommandLineArgumentsProvider.getAdditionalEnvs(); |
| if (envs != null) { |
| EncodingEnvironmentUtil.fixDefaultEncodingIfMac(envs, getProject()); |
| } |
| final Process server = ProcessRunner |
| .createProcess(getWorkingDir(), envs, myCommandLineArgumentsProvider.getArguments()); |
| try { |
| myPydevConsoleCommunication = new PydevConsoleCommunication(getProject(), myPorts[0], server, myPorts[1]); |
| } |
| catch (Exception e) { |
| throw new ExecutionException(e.getMessage()); |
| } |
| return server; |
| } |
| } |
| |
| private Process createRemoteConsoleProcess(PythonRemoteInterpreterManager manager, String[] command, Map<String, String> env) |
| throws ExecutionException { |
| PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData(); |
| assert data != null; |
| |
| GeneralCommandLine commandLine = new GeneralCommandLine(command); |
| |
| |
| commandLine.getEnvironment().putAll(env); |
| |
| commandLine.getParametersList().set(1, PythonRemoteInterpreterManager.toSystemDependent(new File(data.getHelpersPath(), |
| PYDEV_PYDEVCONSOLE_PY) |
| .getPath(), |
| PySourcePosition.isWindowsPath( |
| data.getInterpreterPath()) |
| )); |
| commandLine.getParametersList().set(2, "0"); |
| commandLine.getParametersList().set(3, "0"); |
| |
| myCommandLine = commandLine.getCommandLineString(); |
| |
| try { |
| myRemoteCredentials = data.getRemoteSdkCredentials(true); |
| PathMappingSettings mappings = manager.setupMappings(getProject(), data, null); |
| |
| RemoteSshProcess remoteProcess = |
| manager.createRemoteProcess(getProject(), myRemoteCredentials, mappings, commandLine, true); |
| |
| |
| Couple<Integer> remotePorts = getRemotePortsFromProcess(remoteProcess); |
| |
| remoteProcess.addLocalTunnel(myPorts[0], myRemoteCredentials.getHost(), remotePorts.first); |
| remoteProcess.addRemoteTunnel(remotePorts.second, "localhost", myPorts[1]); |
| |
| |
| myPydevConsoleCommunication = new PydevRemoteConsoleCommunication(getProject(), myPorts[0], remoteProcess, myPorts[1]); |
| return remoteProcess; |
| } |
| catch (Exception e) { |
| throw new ExecutionException(e.getMessage()); |
| } |
| } |
| |
| private static Couple<Integer> getRemotePortsFromProcess(RemoteSshProcess process) throws ExecutionException { |
| Scanner s = new Scanner(process.getInputStream()); |
| |
| return Couple.of(readInt(s, process), readInt(s, process)); |
| } |
| |
| private static int readInt(Scanner s, Process process) throws ExecutionException { |
| long started = System.currentTimeMillis(); |
| |
| while (System.currentTimeMillis() - started < PORTS_WAITING_TIMEOUT) { |
| if (s.hasNextLine()) { |
| String line = s.nextLine(); |
| try { |
| return Integer.parseInt(line); |
| } |
| catch (NumberFormatException ignored) { |
| continue; |
| } |
| } |
| |
| try { |
| |
| Thread.sleep(200); |
| } |
| catch (InterruptedException ignored) { |
| } |
| |
| if (process.exitValue() != 0) { |
| String error; |
| try { |
| error = "Console process terminated with error:\n" + StreamUtil.readText(process.getErrorStream()); |
| } |
| catch (Exception ignored) { |
| error = "Console process terminated with exit code " + process.exitValue(); |
| } |
| throw new ExecutionException(error); |
| } |
| else { |
| break; |
| } |
| } |
| |
| throw new ExecutionException("Couldn't read integer value from stream"); |
| } |
| |
| @Override |
| protected PyConsoleProcessHandler createProcessHandler(final Process process) { |
| if (PySdkUtil.isRemote(mySdk)) { |
| PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance(); |
| if (manager != null) { |
| PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData(); |
| assert data != null; |
| myProcessHandler = |
| manager.createConsoleProcessHandler(process, myRemoteCredentials, getConsoleView(), myPydevConsoleCommunication, |
| myCommandLine, CharsetToolkit.UTF8_CHARSET, |
| manager.setupMappings(getProject(), data, null)); |
| } |
| else { |
| LOG.error("Can't create remote console process handler"); |
| } |
| } |
| else { |
| myProcessHandler = new PyConsoleProcessHandler(process, getConsoleView(), myPydevConsoleCommunication, myCommandLine, |
| CharsetToolkit.UTF8_CHARSET); |
| } |
| return myProcessHandler; |
| } |
| |
| public void initAndRun(final String... statements2execute) throws ExecutionException { |
| super.initAndRun(); |
| |
| connect(statements2execute); |
| } |
| |
| public void connect(final String[] statements2execute) { |
| if (handshake()) { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| |
| @Override |
| public void run() { |
| // Propagate console communication to language console |
| final PythonConsoleView consoleView = getConsoleView(); |
| |
| consoleView.setConsoleCommunication(myPydevConsoleCommunication); |
| consoleView.setSdk(mySdk); |
| consoleView.setExecutionHandler(myConsoleExecuteActionHandler); |
| myProcessHandler.addProcessListener(new ProcessAdapter() { |
| @Override |
| public void onTextAvailable(ProcessEvent event, Key outputType) { |
| consoleView.print(event.getText(), outputType); |
| } |
| }); |
| |
| enableConsoleExecuteAction(); |
| |
| for (String statement : statements2execute) { |
| consoleView.executeStatement(statement + "\n", ProcessOutputTypes.SYSTEM); |
| } |
| |
| fireConsoleInitializedEvent(consoleView); |
| } |
| }); |
| } |
| else { |
| getConsoleView().print("Couldn't connect to console process.", ProcessOutputTypes.STDERR); |
| myProcessHandler.destroyProcess(); |
| finishConsole(); |
| } |
| } |
| |
| @Override |
| protected String constructConsoleTitle(@NotNull String consoleTitle) { |
| if (myConsoleTitle == null) { |
| myConsoleTitle = super.constructConsoleTitle(consoleTitle); |
| } |
| return myConsoleTitle; |
| } |
| |
| protected AnAction createRerunAction() { |
| return new RestartAction(this); |
| } |
| |
| private AnAction createInterruptAction() { |
| AnAction anAction = new AnAction() { |
| @Override |
| public void actionPerformed(final AnActionEvent e) { |
| if (myPydevConsoleCommunication.isExecuting()) { |
| getConsoleView().print("^C", ProcessOutputTypes.SYSTEM); |
| } |
| myPydevConsoleCommunication.interrupt(); |
| } |
| |
| @Override |
| public void update(final AnActionEvent e) { |
| EditorEx consoleEditor = getConsoleView().getConsole().getConsoleEditor(); |
| boolean enabled = IJSwingUtilities.hasFocus(consoleEditor.getComponent()) && !consoleEditor.getSelectionModel().hasSelection(); |
| e.getPresentation().setEnabled(enabled); |
| } |
| }; |
| anAction |
| .registerCustomShortcutSet(KeyEvent.VK_C, InputEvent.CTRL_MASK, getConsoleView().getConsole().getConsoleEditor().getComponent()); |
| anAction.getTemplatePresentation().setVisible(false); |
| return anAction; |
| } |
| |
| |
| private AnAction createBackspaceHandlingAction() { |
| final AnAction upAction = new AnAction() { |
| @Override |
| public void actionPerformed(final AnActionEvent e) { |
| new WriteCommandAction(getLanguageConsole().getProject(), getLanguageConsole().getFile()) { |
| @Override |
| protected void run(@NotNull final Result result) throws Throwable { |
| String text = getLanguageConsole().getEditorDocument().getText(); |
| String newText = text.substring(0, text.length() - myConsoleExecuteActionHandler.getPythonIndent()); |
| getLanguageConsole().getEditorDocument().setText(newText); |
| getLanguageConsole().getConsoleEditor().getCaretModel().moveToOffset(newText.length()); |
| } |
| }.execute(); |
| } |
| |
| @Override |
| public void update(final AnActionEvent e) { |
| e.getPresentation() |
| .setEnabled(myConsoleExecuteActionHandler.getCurrentIndentSize() >= myConsoleExecuteActionHandler.getPythonIndent() && |
| isIndentSubstring(getLanguageConsole().getEditorDocument().getText())); |
| } |
| }; |
| upAction.registerCustomShortcutSet(KeyEvent.VK_BACK_SPACE, 0, null); |
| upAction.getTemplatePresentation().setVisible(false); |
| return upAction; |
| } |
| |
| private boolean isIndentSubstring(String text) { |
| int indentSize = myConsoleExecuteActionHandler.getPythonIndent(); |
| return text.length() >= indentSize && CharMatcher.WHITESPACE.matchesAllOf(text.substring(text.length() - indentSize)); |
| } |
| |
| private void enableConsoleExecuteAction() { |
| myConsoleExecuteActionHandler.setEnabled(true); |
| } |
| |
| private boolean handshake() { |
| boolean res; |
| long started = System.currentTimeMillis(); |
| do { |
| try { |
| res = myPydevConsoleCommunication.handshake(); |
| } |
| catch (XmlRpcException ignored) { |
| res = false; |
| } |
| if (res) { |
| break; |
| } |
| else { |
| long now = System.currentTimeMillis(); |
| if (now - started > APPROPRIATE_TO_WAIT) { |
| break; |
| } |
| else { |
| try { |
| Thread.sleep(100); |
| } |
| catch (InterruptedException ignored) { |
| } |
| } |
| } |
| } |
| while (true); |
| return res; |
| } |
| |
| @Override |
| protected AnAction createStopAction() { |
| final AnAction generalStopAction = super.createStopAction(); |
| return createConsoleStoppingAction(generalStopAction); |
| } |
| |
| @Override |
| protected AnAction createCloseAction(Executor defaultExecutor, final RunContentDescriptor descriptor) { |
| final AnAction generalCloseAction = super.createCloseAction(defaultExecutor, descriptor); |
| |
| final AnAction stopAction = new DumbAwareAction() { |
| @Override |
| public void update(AnActionEvent e) { |
| generalCloseAction.update(e); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| e = stopConsole(e); |
| |
| clearContent(descriptor); |
| |
| generalCloseAction.actionPerformed(e); |
| } |
| }; |
| stopAction.copyFrom(generalCloseAction); |
| return stopAction; |
| } |
| |
| protected void clearContent(RunContentDescriptor descriptor) { |
| } |
| |
| private AnAction createConsoleStoppingAction(final AnAction generalStopAction) { |
| final AnAction stopAction = new DumbAwareAction() { |
| @Override |
| public void update(AnActionEvent e) { |
| generalStopAction.update(e); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| e = stopConsole(e); |
| |
| generalStopAction.actionPerformed(e); |
| } |
| }; |
| stopAction.copyFrom(generalStopAction); |
| return stopAction; |
| } |
| |
| private AnActionEvent stopConsole(AnActionEvent e) { |
| if (myPydevConsoleCommunication != null) { |
| e = new AnActionEvent(e.getInputEvent(), e.getDataContext(), e.getPlace(), |
| e.getPresentation(), e.getActionManager(), e.getModifiers()); |
| try { |
| closeCommunication(); |
| // waiting for REPL communication before destroying process handler |
| Thread.sleep(300); |
| } |
| catch (Exception ignored) { |
| // Ignore |
| } |
| } |
| return e; |
| } |
| |
| protected AnAction createSplitLineAction() { |
| |
| class ConsoleSplitLineAction extends EditorAction { |
| |
| private static final String CONSOLE_SPLIT_LINE_ACTION_ID = "Console.SplitLine"; |
| |
| public ConsoleSplitLineAction() { |
| super(new EditorWriteActionHandler() { |
| |
| private final SplitLineAction mySplitLineAction = new SplitLineAction(); |
| |
| @Override |
| public boolean isEnabled(Editor editor, DataContext dataContext) { |
| return mySplitLineAction.getHandler().isEnabled(editor, dataContext); |
| } |
| |
| @Override |
| public void executeWriteAction(Editor editor, @Nullable Caret caret, DataContext dataContext) { |
| ((EditorWriteActionHandler)mySplitLineAction.getHandler()).executeWriteAction(editor, caret, dataContext); |
| editor.getCaretModel().getCurrentCaret().moveCaretRelatively(0, 1, false, true); |
| } |
| }); |
| } |
| |
| public void setup() { |
| EmptyAction.setupAction(this, CONSOLE_SPLIT_LINE_ACTION_ID, null); |
| } |
| } |
| |
| ConsoleSplitLineAction action = new ConsoleSplitLineAction(); |
| action.setup(); |
| return action; |
| } |
| |
| private void closeCommunication() { |
| if (!myProcessHandler.isProcessTerminated()) { |
| myPydevConsoleCommunication.close(); |
| } |
| } |
| |
| @NotNull |
| @Override |
| protected ProcessBackedConsoleExecuteActionHandler createExecuteActionHandler() { |
| myConsoleExecuteActionHandler = |
| new PydevConsoleExecuteActionHandler(getConsoleView(), getProcessHandler(), myPydevConsoleCommunication); |
| myConsoleExecuteActionHandler.setEnabled(false); |
| myHistoryController = new ConsoleHistoryController(myConsoleType.getTypeId(), "", getLanguageConsole(), |
| myConsoleExecuteActionHandler.getConsoleHistoryModel()); |
| myHistoryController.install(); |
| return myConsoleExecuteActionHandler; |
| } |
| |
| public PydevConsoleCommunication getPydevConsoleCommunication() { |
| return myPydevConsoleCommunication; |
| } |
| |
| public static boolean isInPydevConsole(final PsiElement element) { |
| return element instanceof PydevConsoleElement || getConsoleCommunication(element) != null; |
| } |
| |
| public static boolean isInPydevConsole(final VirtualFile file) { |
| return file.getName().contains("Python Console"); |
| } |
| |
| public static boolean isPythonConsole(@Nullable final FileElement element) { |
| return getPythonConsoleData(element) != null; |
| } |
| |
| @Nullable |
| public static PythonConsoleData getPythonConsoleData(@Nullable FileElement element) { |
| if (element == null || element.getPsi() == null || element.getPsi().getContainingFile() == null) { |
| return null; |
| } |
| |
| VirtualFile file = getConsoleFile(element.getPsi().getContainingFile()); |
| |
| if (file == null) { |
| return null; |
| } |
| return file.getUserData(PyConsoleUtil.PYTHON_CONSOLE_DATA); |
| } |
| |
| private static VirtualFile getConsoleFile(PsiFile psiFile) { |
| VirtualFile file = psiFile.getViewProvider().getVirtualFile(); |
| if (file instanceof LightVirtualFile) { |
| file = ((LightVirtualFile)file).getOriginalFile(); |
| } |
| return file; |
| } |
| |
| @Nullable |
| public static ConsoleCommunication getConsoleCommunication(final PsiElement element) { |
| final PsiFile containingFile = element.getContainingFile(); |
| return containingFile != null ? containingFile.getCopyableUserData(CONSOLE_KEY) : null; |
| } |
| |
| @Nullable |
| public static Sdk getConsoleSdk(final PsiElement element) { |
| final PsiFile containingFile = element.getContainingFile(); |
| return containingFile != null ? containingFile.getCopyableUserData(CONSOLE_SDK) : null; |
| } |
| |
| @Override |
| protected boolean shouldAddNumberToTitle() { |
| return true; |
| } |
| |
| public void addConsoleListener(ConsoleListener consoleListener) { |
| myConsoleListeners.add(consoleListener); |
| } |
| |
| public void removeConsoleListener(ConsoleListener consoleListener) { |
| myConsoleListeners.remove(consoleListener); |
| } |
| |
| private void fireConsoleInitializedEvent(LanguageConsoleView consoleView) { |
| for (ConsoleListener listener : myConsoleListeners) { |
| listener.handleConsoleInitialized(consoleView); |
| } |
| } |
| |
| |
| public interface ConsoleListener { |
| void handleConsoleInitialized(LanguageConsoleView consoleView); |
| } |
| |
| |
| private static class RestartAction extends AnAction { |
| private PydevConsoleRunner myConsoleRunner; |
| |
| |
| private RestartAction(PydevConsoleRunner runner) { |
| copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_RERUN)); |
| getTemplatePresentation().setIcon(AllIcons.Actions.Restart); |
| myConsoleRunner = runner; |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| myConsoleRunner.rerun(); |
| } |
| } |
| |
| private void rerun() { |
| new Task.Backgroundable(getProject(), "Restarting console", true) { |
| @Override |
| public void run(@NotNull ProgressIndicator indicator) { |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| closeCommunication(); |
| } |
| }); |
| |
| myProcessHandler.waitFor(); |
| |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| PydevConsoleRunner.this.run(); |
| } |
| }); |
| } |
| }.queue(); |
| } |
| |
| private class ShowVarsAction extends ToggleAction implements DumbAware { |
| private boolean mySelected = false; |
| |
| public ShowVarsAction() { |
| super("Show Variables", "Shows active console variables", AllIcons.Debugger.Watches); |
| } |
| |
| @Override |
| public boolean isSelected(AnActionEvent e) { |
| return mySelected; |
| } |
| |
| @Override |
| public void setSelected(AnActionEvent e, boolean state) { |
| mySelected = state; |
| |
| if (mySelected) { |
| getConsoleView().showVariables(myPydevConsoleCommunication); |
| } |
| else { |
| getConsoleView().restoreWindow(); |
| } |
| } |
| } |
| |
| |
| private class ConnectDebuggerAction extends ToggleAction implements DumbAware { |
| private boolean mySelected = false; |
| private XDebugSession mySession = null; |
| |
| public ConnectDebuggerAction() { |
| super("Attach Debugger", "Enables tracing of code executed in console", AllIcons.Actions.StartDebugger); |
| } |
| |
| @Override |
| public boolean isSelected(AnActionEvent e) { |
| return mySelected; |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| if (mySession != null) { |
| e.getPresentation().setEnabled(false); |
| } |
| else { |
| e.getPresentation().setEnabled(true); |
| } |
| } |
| |
| @Override |
| public void setSelected(AnActionEvent e, boolean state) { |
| mySelected = state; |
| |
| if (mySelected) { |
| try { |
| mySession = connectToDebugger(); |
| } |
| catch (Exception e1) { |
| LOG.error(e1); |
| Messages.showErrorDialog("Can't connect to debugger", "Error Connecting Debugger"); |
| } |
| } |
| else { |
| //TODO: disable debugging |
| } |
| } |
| } |
| |
| private XDebugSession connectToDebugger() throws ExecutionException { |
| final ServerSocket serverSocket = PythonCommandLineState.createServerSocket(); |
| |
| final XDebugSession session = XDebuggerManager.getInstance(getProject()). |
| startSessionAndShowTab("Python Console Debugger", PythonIcons.Python.Python, null, true, new XDebugProcessStarter() { |
| @NotNull |
| public XDebugProcess start(@NotNull final XDebugSession session) { |
| PythonDebugLanguageConsoleView debugConsoleView = new PythonDebugLanguageConsoleView(getProject(), mySdk); |
| |
| PyConsoleDebugProcessHandler consoleDebugProcessHandler = |
| new PyConsoleDebugProcessHandler(myProcessHandler); |
| |
| PyConsoleDebugProcess consoleDebugProcess = |
| new PyConsoleDebugProcess(session, serverSocket, debugConsoleView, |
| consoleDebugProcessHandler); |
| |
| PythonDebugConsoleCommunication communication = |
| PyDebugRunner.initDebugConsoleView(getProject(), consoleDebugProcess, debugConsoleView, consoleDebugProcessHandler); |
| |
| myPydevConsoleCommunication.setDebugCommunication(communication); |
| debugConsoleView.attachToProcess(consoleDebugProcessHandler); |
| |
| consoleDebugProcess.waitForNextConnection(); |
| |
| try { |
| consoleDebugProcess.connect(myPydevConsoleCommunication); |
| } |
| catch (Exception e) { |
| LOG.error(e); //TODO |
| } |
| |
| myProcessHandler.notifyTextAvailable("\nDebugger connected.\n", ProcessOutputTypes.STDERR); |
| |
| return consoleDebugProcess; |
| } |
| }); |
| |
| return session; |
| } |
| |
| public static PythonConsoleRunnerFactory factory() { |
| return new PydevConsoleRunnerFactory(); |
| } |
| } |