2 * Copyright 2000-2015 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.jetbrains.python.console;
18 import com.google.common.base.CharMatcher;
19 import com.google.common.base.Function;
20 import com.google.common.base.Joiner;
21 import com.google.common.collect.Collections2;
22 import com.intellij.codeInsight.lookup.LookupManager;
23 import com.intellij.execution.ExecutionException;
24 import com.intellij.execution.ExecutionHelper;
25 import com.intellij.execution.Executor;
26 import com.intellij.execution.configurations.EncodingEnvironmentUtil;
27 import com.intellij.execution.configurations.GeneralCommandLine;
28 import com.intellij.execution.configurations.ParamsGroup;
29 import com.intellij.execution.configurations.PtyCommandLine;
30 import com.intellij.execution.console.ConsoleHistoryController;
31 import com.intellij.execution.console.LanguageConsoleView;
32 import com.intellij.execution.console.ProcessBackedConsoleExecuteActionHandler;
33 import com.intellij.execution.executors.DefaultRunExecutor;
34 import com.intellij.execution.process.ProcessAdapter;
35 import com.intellij.execution.process.ProcessEvent;
36 import com.intellij.execution.process.ProcessOutputTypes;
37 import com.intellij.execution.runners.AbstractConsoleRunnerWithHistory;
38 import com.intellij.execution.ui.RunContentDescriptor;
39 import com.intellij.icons.AllIcons;
40 import com.intellij.ide.errorTreeView.NewErrorTreeViewPanel;
41 import com.intellij.internal.statistic.UsageTrigger;
42 import com.intellij.lang.ASTNode;
43 import com.intellij.openapi.actionSystem.*;
44 import com.intellij.openapi.actionSystem.ex.ActionUtil;
45 import com.intellij.openapi.application.ApplicationManager;
46 import com.intellij.openapi.application.ModalityState;
47 import com.intellij.openapi.application.Result;
48 import com.intellij.openapi.command.WriteCommandAction;
49 import com.intellij.openapi.diagnostic.Logger;
50 import com.intellij.openapi.editor.Caret;
51 import com.intellij.openapi.editor.Document;
52 import com.intellij.openapi.editor.Editor;
53 import com.intellij.openapi.editor.actionSystem.EditorAction;
54 import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
55 import com.intellij.openapi.editor.actions.SplitLineAction;
56 import com.intellij.openapi.editor.ex.EditorEx;
57 import com.intellij.openapi.fileEditor.FileDocumentManager;
58 import com.intellij.openapi.module.Module;
59 import com.intellij.openapi.module.ModuleManager;
60 import com.intellij.openapi.progress.ProgressIndicator;
61 import com.intellij.openapi.progress.ProgressManager;
62 import com.intellij.openapi.progress.Task;
63 import com.intellij.openapi.project.DumbAware;
64 import com.intellij.openapi.project.DumbAwareAction;
65 import com.intellij.openapi.project.Project;
66 import com.intellij.openapi.projectRoots.Sdk;
67 import com.intellij.openapi.ui.Messages;
68 import com.intellij.openapi.util.*;
69 import com.intellij.openapi.util.io.StreamUtil;
70 import com.intellij.openapi.util.text.StringUtil;
71 import com.intellij.openapi.vfs.CharsetToolkit;
72 import com.intellij.openapi.vfs.VirtualFile;
73 import com.intellij.openapi.vfs.encoding.EncodingProjectManager;
74 import com.intellij.psi.PsiElement;
75 import com.intellij.psi.PsiFile;
76 import com.intellij.remote.RemoteProcess;
77 import com.intellij.remote.Tunnelable;
78 import com.intellij.testFramework.LightVirtualFile;
79 import com.intellij.ui.GuiUtils;
80 import com.intellij.util.ArrayUtil;
81 import com.intellij.util.IJSwingUtilities;
82 import com.intellij.util.PathMappingSettings;
83 import com.intellij.util.TimeoutUtil;
84 import com.intellij.util.containers.ContainerUtil;
85 import com.intellij.util.net.NetUtils;
86 import com.intellij.util.ui.MessageCategory;
87 import com.intellij.util.ui.UIUtil;
88 import com.intellij.xdebugger.XDebugProcess;
89 import com.intellij.xdebugger.XDebugProcessStarter;
90 import com.intellij.xdebugger.XDebugSession;
91 import com.intellij.xdebugger.XDebuggerManager;
92 import com.jetbrains.python.PythonHelper;
93 import com.jetbrains.python.console.completion.PydevConsoleElement;
94 import com.jetbrains.python.console.parsing.PythonConsoleData;
95 import com.jetbrains.python.console.pydev.ConsoleCommunication;
96 import com.jetbrains.python.console.pydev.ConsoleCommunicationListener;
97 import com.jetbrains.python.debugger.PyDebugRunner;
98 import com.jetbrains.python.debugger.PySourcePosition;
99 import com.jetbrains.python.remote.PyRemotePathMapper;
100 import com.jetbrains.python.remote.PyRemoteProcessHandlerBase;
101 import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase;
102 import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
103 import com.jetbrains.python.run.*;
104 import com.jetbrains.python.sdk.PySdkUtil;
105 import com.jetbrains.python.sdk.PythonSdkType;
106 import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
107 import icons.PythonIcons;
108 import org.apache.xmlrpc.XmlRpcException;
109 import org.jetbrains.annotations.NotNull;
110 import org.jetbrains.annotations.Nullable;
112 import javax.swing.*;
114 import java.awt.event.InputEvent;
115 import java.awt.event.KeyEvent;
117 import java.io.IOException;
118 import java.net.ServerSocket;
119 import java.nio.charset.Charset;
121 import java.util.List;
123 import static com.jetbrains.python.sdk.PythonEnvUtil.setPythonIOEncoding;
124 import static com.jetbrains.python.sdk.PythonEnvUtil.setPythonUnbuffered;
129 public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonConsoleView> {
130 public static final String WORKING_DIR_ENV = "WORKING_DIR_AND_PYTHON_PATHS";
131 public static final String CONSOLE_START_COMMAND = "import sys; print('Python %s on %s' % (sys.version, sys.platform))\n" +
132 "sys.path.extend([" + WORKING_DIR_ENV + "])\n";
133 private static final Logger LOG = Logger.getInstance(PydevConsoleRunner.class.getName());
134 @SuppressWarnings("SpellCheckingInspection")
135 public static final String PYDEV_PYDEVCONSOLE_PY = "pydev/pydevconsole.py";
136 public static final int PORTS_WAITING_TIMEOUT = 20000;
137 private static final String CONSOLE_FEATURE = "python.console";
141 private GeneralCommandLine myGeneralCommandLine;
142 protected int[] myPorts;
143 private PydevConsoleCommunication myPydevConsoleCommunication;
144 private PyConsoleProcessHandler myProcessHandler;
145 protected PydevConsoleExecuteActionHandler myConsoleExecuteActionHandler;
146 private List<ConsoleListener> myConsoleListeners = ContainerUtil.createLockFreeCopyOnWriteList();
147 private final PyConsoleType myConsoleType;
148 private Map<String, String> myEnvironmentVariables;
149 private String myCommandLine;
150 @NotNull private final PyConsoleOptions.PyConsoleSettings myConsoleSettings;
151 private String[] myStatementsToExecute = ArrayUtil.EMPTY_STRING_ARRAY;
153 public static Key<ConsoleCommunication> CONSOLE_KEY = new Key<>("PYDEV_CONSOLE_KEY");
155 public static Key<Sdk> CONSOLE_SDK = new Key<>("PYDEV_CONSOLE_SDK_KEY");
157 private static final long APPROPRIATE_TO_WAIT = 60000;
159 private PyRemoteProcessHandlerBase myRemoteProcessHandlerBase;
161 private String myConsoleTitle = null;
163 public PydevConsoleRunner(@NotNull final Project project,
165 @NotNull final PyConsoleType consoleType,
166 @Nullable final String workingDir,
167 Map<String, String> environmentVariables,
169 PyConsoleOptions.PyConsoleSettings settingsProvider,
170 String... statementsToExecute) {
171 super(project, consoleType.getTitle(), workingDir);
173 myConsoleType = consoleType;
174 myEnvironmentVariables = environmentVariables;
175 myConsoleSettings = settingsProvider;
176 myStatementsToExecute = statementsToExecute;
180 public static PyRemotePathMapper getPathMapper(@NotNull Project project, Sdk sdk, PyConsoleOptions.PyConsoleSettings consoleSettings) {
181 if (PySdkUtil.isRemote(sdk)) {
182 PythonRemoteInterpreterManager instance = PythonRemoteInterpreterManager.getInstance();
183 if (instance != null) {
184 //noinspection ConstantConditions
185 PyRemotePathMapper remotePathMapper =
186 instance.setupMappings(project, (PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData(), null);
188 PathMappingSettings mappingSettings = consoleSettings.getMappingSettings();
190 remotePathMapper.addAll(mappingSettings.getPathMappings(), PyRemotePathMapper.PyPathMappingType.USER_DEFINED);
192 return remotePathMapper;
199 public static Pair<Sdk, Module> findPythonSdkAndModule(@NotNull Project project, @Nullable Module contextModule) {
201 Module module = null;
202 PyConsoleOptions.PyConsoleSettings settings = PyConsoleOptions.getInstance(project).getPythonConsoleSettings();
203 String sdkHome = settings.getSdkHome();
204 if (sdkHome != null) {
205 sdk = PythonSdkType.findSdkByPath(sdkHome);
206 if (settings.getModuleName() != null) {
207 module = ModuleManager.getInstance(project).findModuleByName(settings.getModuleName());
210 module = contextModule;
211 if (module == null && ModuleManager.getInstance(project).getModules().length > 0) {
212 module = ModuleManager.getInstance(project).getModules()[0];
216 if (sdk == null && settings.isUseModuleSdk()) {
217 if (contextModule != null) {
218 module = contextModule;
220 else if (settings.getModuleName() != null) {
221 module = ModuleManager.getInstance(project).findModuleByName(settings.getModuleName());
223 if (module != null) {
224 if (PythonSdkType.findPythonSdk(module) != null) {
225 sdk = PythonSdkType.findPythonSdk(module);
229 else if (contextModule != null) {
230 if (module == null) {
231 module = contextModule;
234 sdk = PythonSdkType.findPythonSdk(module);
239 for (Module m : ModuleManager.getInstance(project).getModules()) {
240 if (PythonSdkType.findPythonSdk(m) != null) {
241 sdk = PythonSdkType.findPythonSdk(m);
248 if (PythonSdkType.getAllSdks().size() > 0) {
249 //noinspection UnusedAssignment
250 sdk = PythonSdkType.getAllSdks().get(0); //take any python sdk
253 return Pair.create(sdk, module);
256 public static String constructPythonPathCommand(Collection<String> pythonPath, String command) {
257 final String path = Joiner.on(", ").join(Collections2.transform(pythonPath, new Function<String, String>() {
259 public String apply(String input) {
260 return "'" + input.replace("\\", "\\\\").replace("'", "\\'") + "'";
264 return command.replace(WORKING_DIR_ENV, path);
267 public static Map<String, String> addDefaultEnvironments(Sdk sdk, Map<String, String> envs, @NotNull Project project) {
268 setCorrectStdOutEncoding(envs, project);
270 PythonSdkFlavor.initPythonPath(envs, true, PythonCommandLineState.getAddedPaths(sdk));
275 * Add required ENV var to Python task to set its stdout charset to current project charset to allow it print correctly.
277 * @param envs map of envs to add variable
278 * @param project current project
280 public static void setCorrectStdOutEncoding(@NotNull final Map<String, String> envs, @NotNull final Project project) {
281 final Charset defaultCharset = getProjectDefaultCharset(project);
282 final String encoding = defaultCharset.name();
283 setPythonIOEncoding(setPythonUnbuffered(envs), encoding);
287 * Set command line charset as current project charset.
288 * Add required ENV var to Python task to set its stdout charset to current project charset to allow it print correctly.
290 * @param commandLine command line
291 * @param project current project
293 public static void setCorrectStdOutEncoding(@NotNull GeneralCommandLine commandLine, @NotNull final Project project) {
294 final Charset defaultCharset = getProjectDefaultCharset(project);
295 commandLine.setCharset(defaultCharset);
296 setPythonIOEncoding(commandLine.getEnvironment(), defaultCharset.name());
300 private static Charset getProjectDefaultCharset(@NotNull Project project) {
301 return EncodingProjectManager.getInstance(project).getDefaultCharset();
305 protected List<AnAction> fillToolBarActions(final DefaultActionGroup toolbarActions,
306 final Executor defaultExecutor,
307 final RunContentDescriptor contentDescriptor) {
308 AnAction backspaceHandlingAction = createBackspaceHandlingAction();
309 //toolbarActions.add(backspaceHandlingAction);
310 AnAction interruptAction = createInterruptAction();
312 AnAction rerunAction = createRerunAction();
313 toolbarActions.add(rerunAction);
315 List<AnAction> actions = super.fillToolBarActions(toolbarActions, defaultExecutor, contentDescriptor);
317 actions.add(0, rerunAction);
319 actions.add(backspaceHandlingAction);
320 actions.add(interruptAction);
321 actions.add(createTabCompletionAction());
323 actions.add(createSplitLineAction());
325 AnAction showVarsAction = new ShowVarsAction();
326 toolbarActions.add(showVarsAction);
327 toolbarActions.add(ConsoleHistoryController.getController(getConsoleView()).getBrowseHistory());
329 toolbarActions.add(new ConnectDebuggerAction());
331 toolbarActions.add(new NewConsoleAction());
336 public void runSync() {
337 myPorts = findAvailablePorts(getProject(), myConsoleType);
339 assert myPorts != null;
341 myGeneralCommandLine = createCommandLine(mySdk, myEnvironmentVariables, getWorkingDir(), myPorts);
342 myCommandLine = myGeneralCommandLine.getCommandLineString();
347 catch (ExecutionException e) {
348 LOG.warn("Error running console", e);
349 ExecutionHelper.showErrors(getProject(), Arrays.<Exception>asList(e), "Python Console", null);
352 ProgressManager.getInstance().run(new Task.Backgroundable(getProject(), "Connecting to console", false) {
354 public void run(@NotNull final ProgressIndicator indicator) {
355 indicator.setText("Connecting to console...");
356 connect(myStatementsToExecute);
370 * Creates new console tab
372 public void createNewConsole() {
377 ApplicationManager.getApplication().invokeAndWait(() -> FileDocumentManager.getInstance().saveAllDocuments());
379 myPorts = findAvailablePorts(getProject(), myConsoleType);
381 assert myPorts != null;
383 myGeneralCommandLine = createCommandLine(mySdk, myEnvironmentVariables, getWorkingDir(), myPorts);
384 myCommandLine = myGeneralCommandLine.getCommandLineString();
387 .invokeLaterIfNeeded(() -> ProgressManager.getInstance().run(new Task.Backgroundable(getProject(), "Connecting to Console", false) {
389 public void run(@NotNull final ProgressIndicator indicator) {
390 indicator.setText("Connecting to console...");
392 initAndRun(myStatementsToExecute);
394 catch (final Exception e) {
395 LOG.warn("Error running console", e);
396 UIUtil.invokeAndWaitIfNeeded(new Runnable() {
399 showErrorsInConsole(e);
407 private void showErrorsInConsole(Exception e) {
408 final Executor defaultExecutor = DefaultRunExecutor.getRunExecutorInstance();
410 DefaultActionGroup actionGroup = new DefaultActionGroup(createRerunAction());
412 final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN,
416 final JPanel panel = new JPanel(new BorderLayout());
417 panel.add(actionToolbar.getComponent(), BorderLayout.WEST);
419 NewErrorTreeViewPanel errorViewPanel = new NewErrorTreeViewPanel(getProject(), null, false, false, null);
421 String[] messages = StringUtil.isNotEmpty(e.getMessage()) ? StringUtil.splitByLines(e.getMessage()) : ArrayUtil.EMPTY_STRING_ARRAY;
422 if (messages.length == 0) {
423 messages = new String[]{"Unknown error"};
426 errorViewPanel.addMessage(MessageCategory.ERROR, messages, null, -1, -1, null);
427 panel.add(errorViewPanel, BorderLayout.CENTER);
430 final RunContentDescriptor contentDescriptor =
431 new RunContentDescriptor(null, myProcessHandler, panel, "Error running console");
433 actionGroup.add(createCloseAction(defaultExecutor, contentDescriptor));
435 showConsole(defaultExecutor, contentDescriptor);
438 private static int[] findAvailablePorts(Project project, PyConsoleType consoleType) {
441 // File "pydev/console/pydevconsole.py", line 223, in <module>
442 // port, client_port = sys.argv[1:3]
443 ports = NetUtils.findAvailableSocketPorts(2);
445 catch (IOException e) {
446 ExecutionHelper.showErrors(project, Arrays.<Exception>asList(e), consoleType.getTitle(), null);
452 protected GeneralCommandLine createCommandLine(@NotNull final Sdk sdk,
453 @NotNull final Map<String, String> environmentVariables,
454 String workingDir, int[] ports) {
455 return doCreateConsoleCmdLine(sdk, environmentVariables, workingDir, ports, PythonHelper.CONSOLE);
459 protected GeneralCommandLine doCreateConsoleCmdLine(Sdk sdk,
460 Map<String, String> environmentVariables,
463 PythonHelper helper) {
464 GeneralCommandLine cmd =
465 PythonCommandLineState.createPythonCommandLine(getProject(), new PythonConsoleRunParams(myConsoleSettings, workingDir, sdk,
466 environmentVariables), false,
467 PtyCommandLine.isEnabled() && !SystemInfo.isWindows);
468 cmd.withWorkDirectory(getWorkingDir());
470 ParamsGroup group = cmd.getParametersList().getParamsGroup(PythonCommandLineState.GROUP_SCRIPT);
471 helper.addToGroup(group, cmd);
473 for (int port : ports) {
474 group.addParameter(String.valueOf(port));
481 protected PythonConsoleView createConsoleView() {
482 PythonConsoleView consoleView = new PythonConsoleView(getProject(), getConsoleTitle(), mySdk);
483 myPydevConsoleCommunication.setConsoleFile(consoleView.getVirtualFile());
484 consoleView.addMessageFilter(new PythonTracebackFilter(getProject()));
489 protected Process createProcess() throws ExecutionException {
490 if (PySdkUtil.isRemote(mySdk)) {
491 PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
492 if (manager != null) {
493 UsageTrigger.trigger(CONSOLE_FEATURE + ".remote");
494 return createRemoteConsoleProcess(manager, myGeneralCommandLine.getParametersList().getArray(),
495 myGeneralCommandLine.getEnvironment(), myGeneralCommandLine.getWorkDirectory());
497 throw new PythonRemoteInterpreterManager.PyRemoteInterpreterExecutionException();
500 myCommandLine = myGeneralCommandLine.getCommandLineString();
501 Map<String, String> envs = myGeneralCommandLine.getEnvironment();
502 EncodingEnvironmentUtil.setLocaleEnvironmentIfMac(envs, myGeneralCommandLine.getCharset());
504 UsageTrigger.trigger(CONSOLE_FEATURE + ".local");
505 final Process server = myGeneralCommandLine.createProcess();
508 myPydevConsoleCommunication = new PydevConsoleCommunication(getProject(), myPorts[0], server, myPorts[1]);
510 catch (Exception e) {
511 throw new ExecutionException(e.getMessage());
517 private RemoteProcess createRemoteConsoleProcess(PythonRemoteInterpreterManager manager,
519 Map<String, String> env,
521 throws ExecutionException {
522 PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData();
525 GeneralCommandLine commandLine = new GeneralCommandLine();
527 commandLine.setWorkDirectory(workDirectory);
529 commandLine.withParameters(command);
531 commandLine.getEnvironment().putAll(env);
533 commandLine.getParametersList().set(0, PythonRemoteInterpreterManager.toSystemDependent(new File(data.getHelpersPath(),
534 PYDEV_PYDEVCONSOLE_PY)
536 PySourcePosition.isWindowsPath(
537 data.getInterpreterPath())
539 commandLine.getParametersList().set(1, "0");
540 commandLine.getParametersList().set(2, "0");
543 PyRemotePathMapper pathMapper = getPathMapper(getProject(), mySdk, myConsoleSettings);
545 assert pathMapper != null;
547 commandLine.putUserData(PyRemoteProcessStarter.OPEN_FOR_INCOMING_CONNECTION, true);
549 myRemoteProcessHandlerBase = PyRemoteProcessStarterManagerUtil
550 .getManager(data).startRemoteProcess(getProject(), commandLine, manager, data,
553 myCommandLine = myRemoteProcessHandlerBase.getCommandLine();
555 RemoteProcess remoteProcess = myRemoteProcessHandlerBase.getProcess();
557 Couple<Integer> remotePorts = getRemotePortsFromProcess(remoteProcess);
559 if (remoteProcess instanceof Tunnelable) {
560 Tunnelable tunnelableProcess = (Tunnelable)remoteProcess;
561 tunnelableProcess.addLocalTunnel(myPorts[0], remotePorts.first);
562 tunnelableProcess.addRemoteTunnel(remotePorts.second, "localhost", myPorts[1]);
564 if (LOG.isDebugEnabled()) {
565 LOG.debug(String.format("Using tunneled communication for Python console: port %d (=> %d) on IDE side, " +
566 "port %d (=> %d) on pydevconsole.py side", myPorts[1], remotePorts.second, myPorts[0],
570 myPydevConsoleCommunication = new PydevRemoteConsoleCommunication(getProject(), myPorts[0], remoteProcess, myPorts[1]);
573 if (LOG.isDebugEnabled()) {
574 LOG.debug(String.format("Using direct communication for Python console: port %d on IDE side, port %d on pydevconsole.py side",
575 remotePorts.second, remotePorts.first));
578 myPydevConsoleCommunication =
579 new PydevRemoteConsoleCommunication(getProject(), remotePorts.first, remoteProcess, remotePorts.second);
582 return remoteProcess;
584 catch (Exception e) {
585 throw new ExecutionException(e.getMessage(), e);
589 private static Couple<Integer> getRemotePortsFromProcess(RemoteProcess process) throws ExecutionException {
590 Scanner s = new Scanner(process.getInputStream());
592 return Couple.of(readInt(s, process), readInt(s, process));
595 private static int readInt(Scanner s, Process process) throws ExecutionException {
596 long started = System.currentTimeMillis();
598 StringBuilder sb = new StringBuilder();
599 boolean flag = false;
601 while (System.currentTimeMillis() - started < PORTS_WAITING_TIMEOUT) {
602 if (s.hasNextLine()) {
603 String line = s.nextLine();
604 sb.append(line).append("\n");
606 int i = Integer.parseInt(line);
608 LOG.warn("Unexpected strings in output:\n" + sb.toString());
612 catch (NumberFormatException ignored) {
618 TimeoutUtil.sleep(200);
620 if (process.exitValue() != 0) {
623 error = "Console process terminated with error:\n" + StreamUtil.readText(process.getErrorStream()) + sb.toString();
625 catch (Exception ignored) {
626 error = "Console process terminated with exit code " + process.exitValue() + ", output:" + sb.toString();
628 throw new ExecutionException(error);
635 throw new ExecutionException("Couldn't read integer value from stream");
639 protected PyConsoleProcessHandler createProcessHandler(final Process process) {
640 if (PySdkUtil.isRemote(mySdk)) {
641 PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
642 if (manager != null) {
643 PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData();
646 manager.createConsoleProcessHandler((RemoteProcess)process, getConsoleView(), myPydevConsoleCommunication,
647 myCommandLine, CharsetToolkit.UTF8_CHARSET,
648 manager.setupMappings(getProject(), data, null),
649 myRemoteProcessHandlerBase.getRemoteSocketToLocalHostProvider());
652 LOG.error("Can't create remote console process handler");
656 myProcessHandler = new PyConsoleProcessHandler(process, getConsoleView(), myPydevConsoleCommunication, myCommandLine,
657 CharsetToolkit.UTF8_CHARSET);
659 return myProcessHandler;
662 public void initAndRun(final String... statements2execute) throws ExecutionException {
665 connect(statements2execute);
668 public void connect(final String[] statements2execute) {
670 ApplicationManager.getApplication().invokeLater(() -> {
671 // Propagate console communication to language console
672 final PythonConsoleView consoleView = getConsoleView();
674 consoleView.setConsoleCommunication(myPydevConsoleCommunication);
675 consoleView.setSdk(mySdk);
676 consoleView.setExecutionHandler(myConsoleExecuteActionHandler);
677 myProcessHandler.addProcessListener(new ProcessAdapter() {
679 public void onTextAvailable(ProcessEvent event, Key outputType) {
680 consoleView.print(event.getText(), outputType);
684 enableConsoleExecuteAction();
686 for (String statement : statements2execute) {
687 consoleView.executeStatement(statement + "\n", ProcessOutputTypes.SYSTEM);
690 fireConsoleInitializedEvent(consoleView);
694 getConsoleView().print("Couldn't connect to console process.", ProcessOutputTypes.STDERR);
695 myProcessHandler.destroyProcess();
701 protected String constructConsoleTitle(@NotNull String consoleTitle) {
702 if (myConsoleTitle == null) {
703 myConsoleTitle = super.constructConsoleTitle(consoleTitle);
705 return myConsoleTitle;
708 protected AnAction createRerunAction() {
709 return new RestartAction(this);
712 private AnAction createInterruptAction() {
713 AnAction anAction = new AnAction() {
715 public void actionPerformed(final AnActionEvent e) {
716 if (myPydevConsoleCommunication.isExecuting()) {
717 getConsoleView().print("^C", ProcessOutputTypes.SYSTEM);
719 myPydevConsoleCommunication.interrupt();
723 public void update(final AnActionEvent e) {
724 EditorEx consoleEditor = getConsoleView().getConsoleEditor();
725 boolean enabled = IJSwingUtilities.hasFocus(consoleEditor.getComponent()) && !consoleEditor.getSelectionModel().hasSelection();
726 e.getPresentation().setEnabled(enabled);
730 .registerCustomShortcutSet(KeyEvent.VK_C, InputEvent.CTRL_MASK, getConsoleView().getConsoleEditor().getComponent());
731 anAction.getTemplatePresentation().setVisible(false);
735 private AnAction createTabCompletionAction() {
736 final AnAction runCompletions = new AnAction() {
738 public void actionPerformed(AnActionEvent e) {
740 Editor editor = getConsoleView().getConsoleEditor();
741 if (LookupManager.getActiveLookup(editor) != null) {
742 AnAction replace = ActionManager.getInstance().getAction("EditorChooseLookupItemReplace");
743 ActionUtil.performActionDumbAware(replace, e);
746 AnAction completionAction = ActionManager.getInstance().getAction("CodeCompletion");
747 if (completionAction == null) {
750 ActionUtil.performActionDumbAware(completionAction, e);
754 public void update(AnActionEvent e) {
755 Editor editor = getConsoleView().getConsoleEditor();
756 if (LookupManager.getActiveLookup(editor) != null) {
757 e.getPresentation().setEnabled(false);
759 int offset = editor.getCaretModel().getOffset();
760 Document document = editor.getDocument();
761 int lineStart = document.getLineStartOffset(document.getLineNumber(offset));
762 String textToCursor = document.getText(new TextRange(lineStart, offset));
763 e.getPresentation().setEnabled(!CharMatcher.WHITESPACE.matchesAllOf(textToCursor));
768 .registerCustomShortcutSet(KeyEvent.VK_TAB, 0, getConsoleView().getConsoleEditor().getComponent());
769 runCompletions.getTemplatePresentation().setVisible(false);
770 return runCompletions;
777 private AnAction createBackspaceHandlingAction() {
778 final AnAction upAction = new AnAction() {
780 public void actionPerformed(final AnActionEvent e) {
781 new WriteCommandAction(getConsoleView().getProject(), getConsoleView().getFile()) {
783 protected void run(@NotNull final Result result) throws Throwable {
784 String text = getConsoleView().getEditorDocument().getText();
785 String newText = text.substring(0, text.length() - myConsoleExecuteActionHandler.getPythonIndent());
786 getConsoleView().getEditorDocument().setText(newText);
787 getConsoleView().getConsoleEditor().getCaretModel().moveToOffset(newText.length());
793 public void update(final AnActionEvent e) {
795 .setEnabled(myConsoleExecuteActionHandler.getCurrentIndentSize() >= myConsoleExecuteActionHandler.getPythonIndent() &&
796 isIndentSubstring(getConsoleView().getEditorDocument().getText()));
799 upAction.registerCustomShortcutSet(KeyEvent.VK_BACK_SPACE, 0, null);
800 upAction.getTemplatePresentation().setVisible(false);
804 private boolean isIndentSubstring(String text) {
805 int indentSize = myConsoleExecuteActionHandler.getPythonIndent();
806 return text.length() >= indentSize && CharMatcher.WHITESPACE.matchesAllOf(text.substring(text.length() - indentSize));
809 private void enableConsoleExecuteAction() {
810 myConsoleExecuteActionHandler.setEnabled(true);
813 private boolean handshake() {
815 long started = System.currentTimeMillis();
818 res = myPydevConsoleCommunication.handshake();
820 catch (XmlRpcException ignored) {
827 long now = System.currentTimeMillis();
828 if (now - started > APPROPRIATE_TO_WAIT) {
832 TimeoutUtil.sleep(100);
841 protected AnAction createStopAction() {
842 final AnAction generalStopAction = super.createStopAction();
843 return createConsoleStoppingAction(generalStopAction);
847 protected AnAction createCloseAction(Executor defaultExecutor, final RunContentDescriptor descriptor) {
848 final AnAction generalCloseAction = super.createCloseAction(defaultExecutor, descriptor);
850 final AnAction stopAction = new DumbAwareAction() {
852 public void update(AnActionEvent e) {
853 generalCloseAction.update(e);
857 public void actionPerformed(AnActionEvent e) {
860 clearContent(descriptor);
862 generalCloseAction.actionPerformed(e);
865 stopAction.copyFrom(generalCloseAction);
869 protected void clearContent(RunContentDescriptor descriptor) {
872 private AnAction createConsoleStoppingAction(final AnAction generalStopAction) {
873 final AnAction stopAction = new DumbAwareAction() {
875 public void update(AnActionEvent e) {
876 generalStopAction.update(e);
880 public void actionPerformed(AnActionEvent e) {
883 generalStopAction.actionPerformed(e);
886 stopAction.copyFrom(generalStopAction);
890 private AnActionEvent stopConsole(AnActionEvent e) {
891 if (myPydevConsoleCommunication != null) {
892 e = new AnActionEvent(e.getInputEvent(), e.getDataContext(), e.getPlace(),
893 e.getPresentation(), e.getActionManager(), e.getModifiers());
895 closeCommunication();
896 // waiting for REPL communication before destroying process handler
899 catch (Exception ignored) {
906 protected AnAction createSplitLineAction() {
908 class ConsoleSplitLineAction extends EditorAction {
910 private static final String CONSOLE_SPLIT_LINE_ACTION_ID = "Console.SplitLine";
912 public ConsoleSplitLineAction() {
913 super(new EditorWriteActionHandler() {
915 private final SplitLineAction mySplitLineAction = new SplitLineAction();
918 public boolean isEnabled(Editor editor, DataContext dataContext) {
919 return mySplitLineAction.getHandler().isEnabled(editor, dataContext);
923 public void executeWriteAction(Editor editor, @Nullable Caret caret, DataContext dataContext) {
924 ((EditorWriteActionHandler)mySplitLineAction.getHandler()).executeWriteAction(editor, caret, dataContext);
925 editor.getCaretModel().getCurrentCaret().moveCaretRelatively(0, 1, false, true);
930 public void setup() {
931 EmptyAction.setupAction(this, CONSOLE_SPLIT_LINE_ACTION_ID, null);
935 ConsoleSplitLineAction action = new ConsoleSplitLineAction();
940 private void closeCommunication() {
941 if (!myProcessHandler.isProcessTerminated()) {
942 myPydevConsoleCommunication.close();
948 protected ProcessBackedConsoleExecuteActionHandler createExecuteActionHandler() {
949 myConsoleExecuteActionHandler =
950 new PydevConsoleExecuteActionHandler(getConsoleView(), getProcessHandler(), myPydevConsoleCommunication);
951 myConsoleExecuteActionHandler.setEnabled(false);
952 new ConsoleHistoryController(myConsoleType.getTypeId(), "", getConsoleView()).install();
953 return myConsoleExecuteActionHandler;
956 public PydevConsoleCommunication getPydevConsoleCommunication() {
957 return myPydevConsoleCommunication;
960 public static boolean isInPydevConsole(final PsiElement element) {
961 return element instanceof PydevConsoleElement || getConsoleCommunication(element) != null;
964 public static boolean isPythonConsole(@Nullable ASTNode element) {
965 return getPythonConsoleData(element) != null;
969 public static PythonConsoleData getPythonConsoleData(@Nullable ASTNode element) {
970 if (element == null || element.getPsi() == null || element.getPsi().getContainingFile() == null) {
974 VirtualFile file = getConsoleFile(element.getPsi().getContainingFile());
979 return file.getUserData(PyConsoleUtil.PYTHON_CONSOLE_DATA);
982 private static VirtualFile getConsoleFile(PsiFile psiFile) {
983 VirtualFile file = psiFile.getViewProvider().getVirtualFile();
984 if (file instanceof LightVirtualFile) {
985 file = ((LightVirtualFile)file).getOriginalFile();
991 public static ConsoleCommunication getConsoleCommunication(final PsiElement element) {
992 final PsiFile containingFile = element.getContainingFile();
993 return containingFile != null ? containingFile.getCopyableUserData(CONSOLE_KEY) : null;
997 public static Sdk getConsoleSdk(final PsiElement element) {
998 final PsiFile containingFile = element.getContainingFile();
999 return containingFile != null ? containingFile.getCopyableUserData(CONSOLE_SDK) : null;
1003 protected boolean shouldAddNumberToTitle() {
1007 public void addConsoleListener(ConsoleListener consoleListener) {
1008 myConsoleListeners.add(consoleListener);
1011 public void removeConsoleListener(ConsoleListener consoleListener) {
1012 myConsoleListeners.remove(consoleListener);
1015 private void fireConsoleInitializedEvent(LanguageConsoleView consoleView) {
1016 for (ConsoleListener listener : myConsoleListeners) {
1017 listener.handleConsoleInitialized(consoleView);
1022 public interface ConsoleListener {
1023 void handleConsoleInitialized(LanguageConsoleView consoleView);
1027 private static class RestartAction extends AnAction {
1028 private PydevConsoleRunner myConsoleRunner;
1031 private RestartAction(PydevConsoleRunner runner) {
1032 copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_RERUN));
1033 getTemplatePresentation().setIcon(AllIcons.Actions.Restart);
1034 myConsoleRunner = runner;
1038 public void actionPerformed(AnActionEvent e) {
1039 myConsoleRunner.rerun();
1043 private void rerun() {
1044 new Task.Backgroundable(getProject(), "Restarting Console", true) {
1046 public void run(@NotNull ProgressIndicator indicator) {
1047 if (myProcessHandler != null) {
1048 UIUtil.invokeLaterIfNeeded(() -> closeCommunication());
1050 myProcessHandler.waitFor();
1053 GuiUtils.invokeLaterIfNeeded(() -> PydevConsoleRunner.this.run(), ModalityState.defaultModalityState());
1058 private class ShowVarsAction extends ToggleAction implements DumbAware {
1059 private boolean mySelected = false;
1061 public ShowVarsAction() {
1062 super("Show Variables", "Shows active console variables", AllIcons.Debugger.Watches);
1066 public boolean isSelected(AnActionEvent e) {
1071 public void setSelected(AnActionEvent e, boolean state) {
1075 getConsoleView().showVariables(myPydevConsoleCommunication);
1078 getConsoleView().restoreWindow();
1084 private class ConnectDebuggerAction extends ToggleAction implements DumbAware {
1085 private boolean mySelected = false;
1086 private XDebugSession mySession = null;
1088 public ConnectDebuggerAction() {
1089 super("Attach Debugger", "Enables tracing of code executed in console", AllIcons.Actions.StartDebugger);
1093 public boolean isSelected(AnActionEvent e) {
1098 public void update(AnActionEvent e) {
1099 if (mySession != null) {
1100 e.getPresentation().setEnabled(false);
1103 e.getPresentation().setEnabled(true);
1108 public void setSelected(AnActionEvent e, boolean state) {
1113 mySession = connectToDebugger();
1115 catch (Exception e1) {
1117 Messages.showErrorDialog("Can't connect to debugger", "Error Connecting Debugger");
1121 //TODO: disable debugging
1127 private static class NewConsoleAction extends AnAction implements DumbAware {
1128 public NewConsoleAction() {
1129 super("New Console", "Creates new python console", AllIcons.General.Add);
1133 public void update(AnActionEvent e) {
1134 e.getPresentation().setEnabled(true);
1138 public void actionPerformed(AnActionEvent e) {
1139 PydevConsoleRunner runner =
1140 PythonConsoleRunnerFactory.getInstance().createConsoleRunner(e.getData(CommonDataKeys.PROJECT), e.getData(LangDataKeys.MODULE));
1141 runner.createNewConsole();
1145 private XDebugSession connectToDebugger() throws ExecutionException {
1146 final ServerSocket serverSocket = PythonCommandLineState.createServerSocket();
1148 final XDebugSession session = XDebuggerManager.getInstance(getProject()).
1149 startSessionAndShowTab("Python Console Debugger", PythonIcons.Python.Python, null, true, new XDebugProcessStarter() {
1151 public XDebugProcess start(@NotNull final XDebugSession session) {
1152 PythonDebugLanguageConsoleView debugConsoleView = new PythonDebugLanguageConsoleView(getProject(), mySdk);
1154 PyConsoleDebugProcessHandler consoleDebugProcessHandler =
1155 new PyConsoleDebugProcessHandler(myProcessHandler);
1157 PyConsoleDebugProcess consoleDebugProcess =
1158 new PyConsoleDebugProcess(session, serverSocket, debugConsoleView,
1159 consoleDebugProcessHandler);
1161 PythonDebugConsoleCommunication communication =
1162 PyDebugRunner.initDebugConsoleView(getProject(), consoleDebugProcess, debugConsoleView, consoleDebugProcessHandler, session);
1164 communication.addCommunicationListener(new ConsoleCommunicationListener() {
1166 public void commandExecuted(boolean more) {
1167 session.rebuildViews();
1171 public void inputRequested() {
1175 myPydevConsoleCommunication.setDebugCommunication(communication);
1176 debugConsoleView.attachToProcess(consoleDebugProcessHandler);
1178 consoleDebugProcess.waitForNextConnection();
1181 consoleDebugProcess.connect(myPydevConsoleCommunication);
1183 catch (Exception e) {
1184 LOG.error(e); //TODO
1187 myProcessHandler.notifyTextAvailable("\nDebugger connected.\n", ProcessOutputTypes.STDERR);
1189 return consoleDebugProcess;
1196 public static PythonConsoleRunnerFactory factory() {
1197 return new PydevConsoleRunnerFactory();
1200 private static class PythonConsoleRunParams implements PythonRunParams {
1201 private final PyConsoleOptions.PyConsoleSettings myConsoleSettings;
1202 private String myWorkingDir;
1204 private Map<String, String> myEnvironmentVariables;
1206 public PythonConsoleRunParams(@NotNull PyConsoleOptions.PyConsoleSettings consoleSettings,
1207 @NotNull String workingDir,
1209 @NotNull Map<String, String> envs) {
1210 myConsoleSettings = consoleSettings;
1211 myWorkingDir = workingDir;
1213 myEnvironmentVariables = envs;
1214 myEnvironmentVariables.putAll(consoleSettings.getEnvs());
1218 public String getInterpreterOptions() {
1219 return myConsoleSettings.getInterpreterOptions();
1223 public void setInterpreterOptions(String interpreterOptions) {
1224 throw new UnsupportedOperationException();
1228 public String getWorkingDirectory() {
1229 return myWorkingDir;
1233 public void setWorkingDirectory(String workingDirectory) {
1234 throw new UnsupportedOperationException();
1239 public String getSdkHome() {
1240 return mySdk.getHomePath();
1244 public void setSdkHome(String sdkHome) {
1245 throw new UnsupportedOperationException();
1249 public void setModule(Module module) {
1250 throw new UnsupportedOperationException();
1254 public String getModuleName() {
1255 return myConsoleSettings.getModuleName();
1259 public boolean isUseModuleSdk() {
1260 return myConsoleSettings.isUseModuleSdk();
1264 public void setUseModuleSdk(boolean useModuleSdk) {
1265 throw new UnsupportedOperationException();
1269 public boolean isPassParentEnvs() {
1270 return myConsoleSettings.isPassParentEnvs();
1274 public void setPassParentEnvs(boolean passParentEnvs) {
1275 throw new UnsupportedOperationException();
1279 public Map<String, String> getEnvs() {
1280 return myEnvironmentVariables;
1284 public void setEnvs(Map<String, String> envs) {
1285 throw new UnsupportedOperationException();
1290 public PathMappingSettings getMappingSettings() {
1291 throw new UnsupportedOperationException();
1295 public void setMappingSettings(@Nullable PathMappingSettings mappingSettings) {
1296 throw new UnsupportedOperationException();
1300 public boolean shouldAddContentRoots() {
1301 return myConsoleSettings.shouldAddContentRoots();
1305 public boolean shouldAddSourceRoots() {
1306 return myConsoleSettings.shouldAddSourceRoots();
1310 public void setAddContentRoots(boolean flag) {
1311 throw new UnsupportedOperationException();
1315 public void setAddSourceRoots(boolean flag) {
1316 throw new UnsupportedOperationException();