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.collect.Lists;
20 import com.intellij.codeInsight.lookup.LookupManager;
21 import com.intellij.execution.ExecutionException;
22 import com.intellij.execution.ExecutionHelper;
23 import com.intellij.execution.ExecutionManager;
24 import com.intellij.execution.Executor;
25 import com.intellij.execution.configurations.EncodingEnvironmentUtil;
26 import com.intellij.execution.configurations.GeneralCommandLine;
27 import com.intellij.execution.configurations.ParamsGroup;
28 import com.intellij.execution.configurations.PtyCommandLine;
29 import com.intellij.execution.console.ConsoleExecuteAction;
30 import com.intellij.execution.console.ConsoleHistoryController;
31 import com.intellij.execution.console.LanguageConsoleView;
32 import com.intellij.execution.executors.DefaultRunExecutor;
33 import com.intellij.execution.process.ProcessAdapter;
34 import com.intellij.execution.process.ProcessEvent;
35 import com.intellij.execution.process.ProcessOutputTypes;
36 import com.intellij.execution.process.ProcessTerminatedListener;
37 import com.intellij.execution.runners.ConsoleTitleGen;
38 import com.intellij.execution.ui.RunContentDescriptor;
39 import com.intellij.execution.ui.actions.CloseAction;
40 import com.intellij.icons.AllIcons;
41 import com.intellij.ide.CommonActionsManager;
42 import com.intellij.ide.errorTreeView.NewErrorTreeViewPanel;
43 import com.intellij.internal.statistic.UsageTrigger;
44 import com.intellij.openapi.actionSystem.*;
45 import com.intellij.openapi.actionSystem.ex.ActionUtil;
46 import com.intellij.openapi.application.ApplicationManager;
47 import com.intellij.openapi.application.TransactionGuard;
48 import com.intellij.openapi.diagnostic.Logger;
49 import com.intellij.openapi.editor.Caret;
50 import com.intellij.openapi.editor.Document;
51 import com.intellij.openapi.editor.Editor;
52 import com.intellij.openapi.editor.actionSystem.EditorAction;
53 import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
54 import com.intellij.openapi.editor.actions.SplitLineAction;
55 import com.intellij.openapi.editor.ex.EditorEx;
56 import com.intellij.openapi.fileEditor.FileDocumentManager;
57 import com.intellij.openapi.module.Module;
58 import com.intellij.openapi.progress.ProgressIndicator;
59 import com.intellij.openapi.progress.ProgressManager;
60 import com.intellij.openapi.progress.Task;
61 import com.intellij.openapi.project.DumbAware;
62 import com.intellij.openapi.project.DumbAwareAction;
63 import com.intellij.openapi.project.Project;
64 import com.intellij.openapi.projectRoots.Sdk;
65 import com.intellij.openapi.ui.Messages;
66 import com.intellij.openapi.util.*;
67 import com.intellij.openapi.util.io.StreamUtil;
68 import com.intellij.openapi.util.text.StringUtil;
69 import com.intellij.openapi.vfs.CharsetToolkit;
70 import com.intellij.openapi.vfs.VirtualFile;
71 import com.intellij.psi.PsiFile;
72 import com.intellij.remote.RemoteProcess;
73 import com.intellij.remote.Tunnelable;
74 import com.intellij.testFramework.LightVirtualFile;
75 import com.intellij.ui.JBColor;
76 import com.intellij.ui.SideBorder;
77 import com.intellij.ui.content.Content;
78 import com.intellij.util.*;
79 import com.intellij.util.containers.ContainerUtil;
80 import com.intellij.util.net.NetUtils;
81 import com.intellij.util.ui.MessageCategory;
82 import com.intellij.util.ui.UIUtil;
83 import com.intellij.xdebugger.XDebugProcess;
84 import com.intellij.xdebugger.XDebugProcessStarter;
85 import com.intellij.xdebugger.XDebugSession;
86 import com.intellij.xdebugger.XDebuggerManager;
87 import com.jetbrains.python.PythonHelper;
88 import com.jetbrains.python.console.actions.ShowVarsAction;
89 import com.jetbrains.python.console.pydev.ConsoleCommunicationListener;
90 import com.jetbrains.python.debugger.PyDebugRunner;
91 import com.jetbrains.python.debugger.PySourcePosition;
92 import com.jetbrains.python.remote.PyRemotePathMapper;
93 import com.jetbrains.python.remote.PyRemoteProcessHandlerBase;
94 import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase;
95 import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
96 import com.jetbrains.python.run.*;
97 import com.jetbrains.python.sdk.PySdkUtil;
98 import icons.PythonIcons;
99 import org.apache.xmlrpc.XmlRpcException;
100 import org.jetbrains.annotations.NotNull;
101 import org.jetbrains.annotations.Nullable;
103 import javax.swing.*;
105 import java.awt.event.InputEvent;
106 import java.awt.event.KeyEvent;
108 import java.io.IOException;
109 import java.net.ServerSocket;
111 import java.util.List;
112 import java.util.stream.Collectors;
114 import static com.intellij.execution.runners.AbstractConsoleRunnerWithHistory.registerActionShortcuts;
117 * @author traff, oleg
119 public class PydevConsoleRunnerImpl implements PydevConsoleRunner {
120 public static final String WORKING_DIR_ENV = "WORKING_DIR_AND_PYTHON_PATHS";
121 public static final String CONSOLE_START_COMMAND = "import sys; print('Python %s on %s' % (sys.version, sys.platform))\n" +
122 "sys.path.extend([" + WORKING_DIR_ENV + "])\n";
123 private static final Logger LOG = Logger.getInstance(PydevConsoleRunnerImpl.class.getName());
124 @SuppressWarnings("SpellCheckingInspection")
125 public static final String PYDEV_PYDEVCONSOLE_PY = "pydev/pydevconsole.py";
126 public static final int PORTS_WAITING_TIMEOUT = 20000;
127 private static final String CONSOLE_FEATURE = "python.console";
128 private final Project myProject;
129 private final String myTitle;
130 private final String myWorkingDir;
131 private final Consumer<String> myRerunAction;
134 private GeneralCommandLine myGeneralCommandLine;
135 protected int[] myPorts;
136 private PydevConsoleCommunication myPydevConsoleCommunication;
137 private PyConsoleProcessHandler myProcessHandler;
138 protected PydevConsoleExecuteActionHandler myConsoleExecuteActionHandler;
139 private List<ConsoleListener> myConsoleListeners = ContainerUtil.createLockFreeCopyOnWriteList();
140 private final PyConsoleType myConsoleType;
141 private Map<String, String> myEnvironmentVariables;
142 private String myCommandLine;
143 @NotNull private final PyConsoleOptions.PyConsoleSettings myConsoleSettings;
144 private String[] myStatementsToExecute = ArrayUtil.EMPTY_STRING_ARRAY;
147 private static final long APPROPRIATE_TO_WAIT = 60000;
149 private PyRemoteProcessHandlerBase myRemoteProcessHandlerBase;
151 private String myConsoleTitle = null;
152 private PythonConsoleView myConsoleView;
154 public PydevConsoleRunnerImpl(@NotNull final Project project,
156 @NotNull final PyConsoleType consoleType,
157 @Nullable final String workingDir,
158 Map<String, String> environmentVariables,
159 @NotNull PyConsoleOptions.PyConsoleSettings settingsProvider,
160 @NotNull Consumer<String> rerunAction, String... statementsToExecute) {
163 myTitle = consoleType.getTitle();
164 myWorkingDir = workingDir;
165 myConsoleType = consoleType;
166 myEnvironmentVariables = environmentVariables;
167 myConsoleSettings = settingsProvider;
168 myStatementsToExecute = statementsToExecute;
169 myRerunAction = rerunAction;
172 public void setConsoleTitle(String consoleTitle) {
173 myConsoleTitle = consoleTitle;
176 private List<AnAction> fillToolBarActions(final DefaultActionGroup toolbarActions,
177 final RunContentDescriptor contentDescriptor) {
178 //toolbarActions.add(backspaceHandlingAction);
180 toolbarActions.add(createRerunAction());
182 List<AnAction> actions = ContainerUtil.newArrayList();
185 actions.add(createStopAction());
188 actions.add(createCloseAction(contentDescriptor));
192 new ConsoleExecuteAction(myConsoleView, myConsoleExecuteActionHandler, myConsoleExecuteActionHandler.getEmptyExecuteAction(),
193 myConsoleExecuteActionHandler));
196 actions.add(CommonActionsManager.getInstance().createHelpAction("interactive_console"));
198 toolbarActions.addAll(actions);
201 actions.add(0, createRerunAction());
203 actions.add(createInterruptAction());
204 actions.add(createTabCompletionAction());
206 actions.add(createSplitLineAction());
208 toolbarActions.add(new ShowVarsAction(myConsoleView, myPydevConsoleCommunication));
209 toolbarActions.add(ConsoleHistoryController.getController(myConsoleView).getBrowseHistory());
211 toolbarActions.add(new ConnectDebuggerAction());
213 toolbarActions.add(new NewConsoleAction());
220 PythonConsoleToolWindow toolWindow = PythonConsoleToolWindow.getInstance(myProject);
221 if (toolWindow != null) {
222 toolWindow.getToolWindow().activate(() -> {}, true);
229 public void runSync() {
230 myPorts = findAvailablePorts(myProject, myConsoleType);
232 assert myPorts != null;
234 myGeneralCommandLine = createCommandLine(mySdk, myEnvironmentVariables, myWorkingDir, myPorts);
235 myCommandLine = myGeneralCommandLine.getCommandLineString();
240 catch (ExecutionException e) {
241 LOG.warn("Error running console", e);
242 ExecutionHelper.showErrors(myProject, Collections.<Exception>singletonList(e), "Python Console", null);
245 ProgressManager.getInstance().run(new Task.Backgroundable(myProject, "Connecting to Console", false) {
247 public void run(@NotNull final ProgressIndicator indicator) {
248 indicator.setText("Connecting to console...");
249 connect(myStatementsToExecute);
257 TransactionGuard.submitTransaction(myProject, () -> FileDocumentManager.getInstance().saveAllDocuments());
259 myPorts = findAvailablePorts(myProject, myConsoleType);
261 assert myPorts != null;
263 myGeneralCommandLine = createCommandLine(mySdk, myEnvironmentVariables, myWorkingDir, myPorts);
264 myCommandLine = myGeneralCommandLine.getCommandLineString();
267 .invokeLaterIfNeeded(() -> ProgressManager.getInstance().run(new Task.Backgroundable(myProject, "Connecting to Console", false) {
269 public void run(@NotNull final ProgressIndicator indicator) {
270 indicator.setText("Connecting to console...");
273 connect(myStatementsToExecute);
275 catch (final Exception e) {
276 LOG.warn("Error running console", e);
277 UIUtil.invokeAndWaitIfNeeded(new Runnable() {
280 showErrorsInConsole(e);
288 private void showErrorsInConsole(Exception e) {
290 DefaultActionGroup actionGroup = new DefaultActionGroup(createRerunAction());
292 final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN,
296 final JPanel panel = new JPanel(new BorderLayout());
297 panel.add(actionToolbar.getComponent(), BorderLayout.WEST);
299 NewErrorTreeViewPanel errorViewPanel = new NewErrorTreeViewPanel(myProject, null, false, false, null);
301 String[] messages = StringUtil.isNotEmpty(e.getMessage()) ? StringUtil.splitByLines(e.getMessage()) : ArrayUtil.EMPTY_STRING_ARRAY;
302 if (messages.length == 0) {
303 messages = new String[]{"Unknown error"};
306 errorViewPanel.addMessage(MessageCategory.ERROR, messages, null, -1, -1, null);
307 panel.add(errorViewPanel, BorderLayout.CENTER);
310 final RunContentDescriptor contentDescriptor =
311 new RunContentDescriptor(null, myProcessHandler, panel, "Error running console");
313 actionGroup.add(createCloseAction(contentDescriptor));
315 showContentDescriptor(contentDescriptor);
319 private void showContentDescriptor(RunContentDescriptor contentDescriptor) {
320 PythonConsoleToolWindow toolWindow = PythonConsoleToolWindow.getInstance(myProject);
321 if (toolWindow != null) {
323 .init(PythonConsoleToolWindow.getToolWindow(myProject), contentDescriptor);
327 .getInstance(myProject).getContentManager().showRunContent(getExecutor(), contentDescriptor);
331 private static Executor getExecutor() {
332 return DefaultRunExecutor.getRunExecutorInstance();
335 private static int[] findAvailablePorts(Project project, PyConsoleType consoleType) {
338 // File "pydev/console/pydevconsole.py", line 223, in <module>
339 // port, client_port = sys.argv[1:3]
340 ports = NetUtils.findAvailableSocketPorts(2);
342 catch (IOException e) {
343 ExecutionHelper.showErrors(project, Collections.<Exception>singletonList(e), consoleType.getTitle(), null);
349 protected GeneralCommandLine createCommandLine(@NotNull final Sdk sdk,
350 @NotNull final Map<String, String> environmentVariables,
351 String workingDir, int[] ports) {
352 return doCreateConsoleCmdLine(sdk, environmentVariables, workingDir, ports, PythonHelper.CONSOLE);
356 protected GeneralCommandLine doCreateConsoleCmdLine(Sdk sdk,
357 Map<String, String> environmentVariables,
360 PythonHelper helper) {
361 GeneralCommandLine cmd =
362 PythonCommandLineState.createPythonCommandLine(myProject, new PythonConsoleRunParams(myConsoleSettings, workingDir, sdk,
363 environmentVariables), false,
364 PtyCommandLine.isEnabled() && !SystemInfo.isWindows);
365 cmd.withWorkDirectory(myWorkingDir);
367 ParamsGroup group = cmd.getParametersList().getParamsGroup(PythonCommandLineState.GROUP_SCRIPT);
368 helper.addToGroup(group, cmd);
370 for (int port : ports) {
371 group.addParameter(String.valueOf(port));
377 private PythonConsoleView createConsoleView() {
378 PythonConsoleView consoleView = new PythonConsoleView(myProject, myTitle, mySdk);
379 myPydevConsoleCommunication.setConsoleFile(consoleView.getVirtualFile());
380 consoleView.addMessageFilter(new PythonTracebackFilter(myProject));
384 private Process createProcess() throws ExecutionException {
385 if (PySdkUtil.isRemote(mySdk)) {
386 PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
387 if (manager != null) {
388 UsageTrigger.trigger(CONSOLE_FEATURE + ".remote");
389 return createRemoteConsoleProcess(manager, myGeneralCommandLine.getParametersList().getArray(),
390 myGeneralCommandLine.getEnvironment(), myGeneralCommandLine.getWorkDirectory());
392 throw new PythonRemoteInterpreterManager.PyRemoteInterpreterExecutionException();
395 myCommandLine = myGeneralCommandLine.getCommandLineString();
396 Map<String, String> envs = myGeneralCommandLine.getEnvironment();
397 EncodingEnvironmentUtil.setLocaleEnvironmentIfMac(envs, myGeneralCommandLine.getCharset());
399 UsageTrigger.trigger(CONSOLE_FEATURE + ".local");
400 final Process server = myGeneralCommandLine.createProcess();
403 myPydevConsoleCommunication = new PydevConsoleCommunication(myProject, myPorts[0], server, myPorts[1]);
405 catch (Exception e) {
406 throw new ExecutionException(e.getMessage());
412 private RemoteProcess createRemoteConsoleProcess(PythonRemoteInterpreterManager manager,
414 Map<String, String> env,
416 throws ExecutionException {
417 PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData();
420 GeneralCommandLine commandLine = new GeneralCommandLine();
422 commandLine.setWorkDirectory(workDirectory);
424 commandLine.withParameters(command);
426 commandLine.getEnvironment().putAll(env);
428 commandLine.getParametersList().set(0, PythonRemoteInterpreterManager.toSystemDependent(new File(data.getHelpersPath(),
429 PYDEV_PYDEVCONSOLE_PY)
431 PySourcePosition.isWindowsPath(
432 data.getInterpreterPath())
434 commandLine.getParametersList().set(1, "0");
435 commandLine.getParametersList().set(2, "0");
438 PyRemotePathMapper pathMapper = PydevConsoleRunner.getPathMapper(myProject, mySdk, myConsoleSettings);
440 assert pathMapper != null;
442 commandLine.putUserData(PyRemoteProcessStarter.OPEN_FOR_INCOMING_CONNECTION, true);
444 myRemoteProcessHandlerBase = PyRemoteProcessStarterManagerUtil
445 .getManager(data).startRemoteProcess(myProject, commandLine, manager, data,
448 myCommandLine = myRemoteProcessHandlerBase.getCommandLine();
450 RemoteProcess remoteProcess = myRemoteProcessHandlerBase.getProcess();
452 Couple<Integer> remotePorts = getRemotePortsFromProcess(remoteProcess);
454 if (remoteProcess instanceof Tunnelable) {
455 Tunnelable tunnelableProcess = (Tunnelable)remoteProcess;
456 tunnelableProcess.addLocalTunnel(myPorts[0], remotePorts.first);
457 tunnelableProcess.addRemoteTunnel(remotePorts.second, "localhost", myPorts[1]);
459 if (LOG.isDebugEnabled()) {
460 LOG.debug(String.format("Using tunneled communication for Python console: port %d (=> %d) on IDE side, " +
461 "port %d (=> %d) on pydevconsole.py side", myPorts[1], remotePorts.second, myPorts[0],
465 myPydevConsoleCommunication = new PydevRemoteConsoleCommunication(myProject, myPorts[0], remoteProcess, myPorts[1]);
468 if (LOG.isDebugEnabled()) {
469 LOG.debug(String.format("Using direct communication for Python console: port %d on IDE side, port %d on pydevconsole.py side",
470 remotePorts.second, remotePorts.first));
473 myPydevConsoleCommunication =
474 new PydevRemoteConsoleCommunication(myProject, remotePorts.first, remoteProcess, remotePorts.second);
477 return remoteProcess;
479 catch (Exception e) {
480 throw new ExecutionException(e.getMessage(), e);
484 private static Couple<Integer> getRemotePortsFromProcess(RemoteProcess process) throws ExecutionException {
485 Scanner s = new Scanner(process.getInputStream());
487 return Couple.of(readInt(s, process), readInt(s, process));
490 private static int readInt(Scanner s, Process process) throws ExecutionException {
491 long started = System.currentTimeMillis();
493 StringBuilder sb = new StringBuilder();
494 boolean flag = false;
496 while (System.currentTimeMillis() - started < PORTS_WAITING_TIMEOUT) {
497 if (s.hasNextLine()) {
498 String line = s.nextLine();
499 sb.append(line).append("\n");
501 int i = Integer.parseInt(line);
503 LOG.warn("Unexpected strings in output:\n" + sb.toString());
507 catch (NumberFormatException ignored) {
513 TimeoutUtil.sleep(200);
515 if (process.exitValue() != 0) {
518 error = "Console process terminated with error:\n" + StreamUtil.readText(process.getErrorStream()) + sb.toString();
520 catch (Exception ignored) {
521 error = "Console process terminated with exit code " + process.exitValue() + ", output:" + sb.toString();
523 throw new ExecutionException(error);
530 throw new ExecutionException("Couldn't read integer value from stream");
533 private PyConsoleProcessHandler createProcessHandler(final Process process) {
534 if (PySdkUtil.isRemote(mySdk)) {
535 PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
536 if (manager != null) {
537 PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData();
540 manager.createConsoleProcessHandler((RemoteProcess)process, myConsoleView, myPydevConsoleCommunication,
541 myCommandLine, CharsetToolkit.UTF8_CHARSET,
542 manager.setupMappings(myProject, data, null),
543 myRemoteProcessHandlerBase.getRemoteSocketToLocalHostProvider());
546 LOG.error("Can't create remote console process handler");
550 myProcessHandler = new PyConsoleProcessHandler(process, myConsoleView, myPydevConsoleCommunication, myCommandLine,
551 CharsetToolkit.UTF8_CHARSET);
553 return myProcessHandler;
557 private void initAndRun() throws ExecutionException {
558 // Create Server process
559 final Process process = createProcess();
560 UIUtil.invokeLaterIfNeeded(() -> {
562 myConsoleView = createConsoleView();
563 if (myConsoleView != null) {
564 ((JComponent)myConsoleView).setBorder(new SideBorder(JBColor.border(), SideBorder.LEFT));
566 myProcessHandler = createProcessHandler(process);
568 myConsoleExecuteActionHandler = createExecuteActionHandler();
570 ProcessTerminatedListener.attach(myProcessHandler);
572 PythonConsoleView consoleView = myConsoleView;
573 myProcessHandler.addProcessListener(new ProcessAdapter() {
575 public void processTerminated(ProcessEvent event) {
576 consoleView.setEditable(false);
581 myConsoleView.attachToProcess(myProcessHandler);
582 createContentDescriptorAndActions();
586 myProcessHandler.startNotify();
590 protected void createContentDescriptorAndActions() {
592 final DefaultActionGroup toolbarActions = new DefaultActionGroup();
593 final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarActions, false);
596 final JPanel panel = new JPanel(new BorderLayout());
597 panel.add(actionToolbar.getComponent(), BorderLayout.WEST);
598 panel.add(myConsoleView.getComponent(), BorderLayout.CENTER);
600 actionToolbar.setTargetComponent(panel);
602 if (myConsoleTitle == null) {
603 myConsoleTitle = new ConsoleTitleGen(myProject, myTitle) {
606 protected List<String> getActiveConsoles(@NotNull String consoleTitle) {
607 PythonConsoleToolWindow toolWindow = PythonConsoleToolWindow.getInstance(myProject);
608 if (toolWindow != null && toolWindow.getToolWindow() != null) {
609 return Lists.newArrayList(toolWindow.getToolWindow().getContentManager().getContents()).stream().map(c -> c.getDisplayName())
611 Collectors.toList());
614 return super.getActiveConsoles(consoleTitle);
620 final RunContentDescriptor contentDescriptor =
621 new RunContentDescriptor(myConsoleView, myProcessHandler, panel, myConsoleTitle, null);
623 contentDescriptor.setFocusComputable(() -> myConsoleView.getConsoleEditor().getContentComponent());
624 contentDescriptor.setAutoFocusContent(true);
628 final List<AnAction> actions = fillToolBarActions(toolbarActions, contentDescriptor);
629 registerActionShortcuts(actions, myConsoleView.getConsoleEditor().getComponent());
630 registerActionShortcuts(actions, panel);
632 showContentDescriptor(contentDescriptor);
635 private void connect(final String[] statements2execute) {
637 ApplicationManager.getApplication().invokeLater(() -> {
638 // Propagate console communication to language console
639 final PythonConsoleView consoleView = myConsoleView;
641 consoleView.setConsoleCommunication(myPydevConsoleCommunication);
642 consoleView.setSdk(mySdk);
643 consoleView.setExecutionHandler(myConsoleExecuteActionHandler);
644 myProcessHandler.addProcessListener(new ProcessAdapter() {
646 public void onTextAvailable(ProcessEvent event, Key outputType) {
647 consoleView.print(event.getText(), outputType);
651 enableConsoleExecuteAction();
653 for (String statement : statements2execute) {
654 consoleView.executeStatement(statement + "\n", ProcessOutputTypes.SYSTEM);
657 fireConsoleInitializedEvent(consoleView);
658 consoleView.initialized();
662 myConsoleView.print("Couldn't connect to console process.", ProcessOutputTypes.STDERR);
663 myProcessHandler.destroyProcess();
664 myConsoleView.setEditable(false);
669 protected AnAction createRerunAction() {
670 return new RestartAction(this);
673 private AnAction createInterruptAction() {
674 AnAction anAction = new AnAction() {
676 public void actionPerformed(final AnActionEvent e) {
677 if (myPydevConsoleCommunication.isExecuting()) {
678 myConsoleView.print("^C", ProcessOutputTypes.SYSTEM);
680 myPydevConsoleCommunication.interrupt();
684 public void update(final AnActionEvent e) {
685 EditorEx consoleEditor = myConsoleView.getConsoleEditor();
686 boolean enabled = IJSwingUtilities.hasFocus(consoleEditor.getComponent()) && !consoleEditor.getSelectionModel().hasSelection();
687 e.getPresentation().setEnabled(enabled);
691 .registerCustomShortcutSet(KeyEvent.VK_C, InputEvent.CTRL_MASK, myConsoleView.getConsoleEditor().getComponent());
692 anAction.getTemplatePresentation().setVisible(false);
696 private AnAction createTabCompletionAction() {
697 final AnAction runCompletions = new AnAction() {
699 public void actionPerformed(AnActionEvent e) {
701 Editor editor = myConsoleView.getConsoleEditor();
702 if (LookupManager.getActiveLookup(editor) != null) {
703 AnAction replace = ActionManager.getInstance().getAction("EditorChooseLookupItemReplace");
704 ActionUtil.performActionDumbAware(replace, e);
707 AnAction completionAction = ActionManager.getInstance().getAction("CodeCompletion");
708 if (completionAction == null) {
711 ActionUtil.performActionDumbAware(completionAction, e);
715 public void update(AnActionEvent e) {
716 Editor editor = myConsoleView.getConsoleEditor();
717 if (LookupManager.getActiveLookup(editor) != null) {
718 e.getPresentation().setEnabled(false);
720 int offset = editor.getCaretModel().getOffset();
721 Document document = editor.getDocument();
722 int lineStart = document.getLineStartOffset(document.getLineNumber(offset));
723 String textToCursor = document.getText(new TextRange(lineStart, offset));
724 e.getPresentation().setEnabled(!CharMatcher.WHITESPACE.matchesAllOf(textToCursor));
729 .registerCustomShortcutSet(KeyEvent.VK_TAB, 0, myConsoleView.getConsoleEditor().getComponent());
730 runCompletions.getTemplatePresentation().setVisible(false);
731 return runCompletions;
735 private boolean isIndentSubstring(String text) {
736 int indentSize = myConsoleExecuteActionHandler.getPythonIndent();
737 return text.length() >= indentSize && CharMatcher.WHITESPACE.matchesAllOf(text.substring(text.length() - indentSize));
740 private void enableConsoleExecuteAction() {
741 myConsoleExecuteActionHandler.setEnabled(true);
744 private boolean handshake() {
746 long started = System.currentTimeMillis();
749 res = myPydevConsoleCommunication.handshake();
751 catch (XmlRpcException ignored) {
758 long now = System.currentTimeMillis();
759 if (now - started > APPROPRIATE_TO_WAIT) {
763 TimeoutUtil.sleep(100);
772 private AnAction createStopAction() {
773 AnAction generalStopAction = ActionManager.getInstance().getAction(IdeActions.ACTION_STOP_PROGRAM);
774 final AnAction stopAction = new DumbAwareAction() {
776 public void update(AnActionEvent e) {
777 generalStopAction.update(e);
781 public void actionPerformed(AnActionEvent e) {
784 generalStopAction.actionPerformed(e);
787 stopAction.copyFrom(generalStopAction);
791 private AnAction createCloseAction(final RunContentDescriptor descriptor) {
792 final AnAction generalCloseAction = new CloseAction(getExecutor(), descriptor, myProject);
794 final AnAction stopAction = new DumbAwareAction() {
796 public void update(AnActionEvent e) {
797 generalCloseAction.update(e);
801 public void actionPerformed(AnActionEvent e) {
804 clearContent(descriptor);
806 generalCloseAction.actionPerformed(e);
809 stopAction.copyFrom(generalCloseAction);
813 protected void clearContent(RunContentDescriptor descriptor) {
814 PythonConsoleToolWindow toolWindow = PythonConsoleToolWindow.getInstance(myProject);
815 if (toolWindow != null && toolWindow.getToolWindow() != null) {
816 Content content = toolWindow.getToolWindow().getContentManager().findContent(descriptor.getDisplayName());
817 assert content != null;
818 toolWindow.getToolWindow().getContentManager().removeContent(content, true);
822 private AnActionEvent stopConsole(AnActionEvent e) {
823 if (myPydevConsoleCommunication != null) {
824 e = new AnActionEvent(e.getInputEvent(), e.getDataContext(), e.getPlace(),
825 e.getPresentation(), e.getActionManager(), e.getModifiers());
827 closeCommunication();
828 // waiting for REPL communication before destroying process handler
831 catch (Exception ignored) {
838 protected AnAction createSplitLineAction() {
840 class ConsoleSplitLineAction extends EditorAction {
842 private static final String CONSOLE_SPLIT_LINE_ACTION_ID = "Console.SplitLine";
844 public ConsoleSplitLineAction() {
845 super(new EditorWriteActionHandler() {
847 private final SplitLineAction mySplitLineAction = new SplitLineAction();
850 public boolean isEnabled(Editor editor, DataContext dataContext) {
851 return mySplitLineAction.getHandler().isEnabled(editor, dataContext);
855 public void executeWriteAction(Editor editor, @Nullable Caret caret, DataContext dataContext) {
856 ((EditorWriteActionHandler)mySplitLineAction.getHandler()).executeWriteAction(editor, caret, dataContext);
857 editor.getCaretModel().getCurrentCaret().moveCaretRelatively(0, 1, false, true);
862 public void setup() {
863 EmptyAction.setupAction(this, CONSOLE_SPLIT_LINE_ACTION_ID, null);
867 ConsoleSplitLineAction action = new ConsoleSplitLineAction();
872 private void closeCommunication() {
873 if (!myProcessHandler.isProcessTerminated()) {
874 myPydevConsoleCommunication.close();
879 protected PydevConsoleExecuteActionHandler createExecuteActionHandler() {
880 myConsoleExecuteActionHandler =
881 new PydevConsoleExecuteActionHandler(myConsoleView, myProcessHandler, myPydevConsoleCommunication);
882 myConsoleExecuteActionHandler.setEnabled(false);
883 new ConsoleHistoryController(myConsoleType.getTypeId(), "", myConsoleView).install();
884 return myConsoleExecuteActionHandler;
888 public PydevConsoleCommunication getPydevConsoleCommunication() {
889 return myPydevConsoleCommunication;
892 static VirtualFile getConsoleFile(PsiFile psiFile) {
893 VirtualFile file = psiFile.getViewProvider().getVirtualFile();
894 if (file instanceof LightVirtualFile) {
895 file = ((LightVirtualFile)file).getOriginalFile();
901 public void addConsoleListener(ConsoleListener consoleListener) {
902 myConsoleListeners.add(consoleListener);
905 private void fireConsoleInitializedEvent(LanguageConsoleView consoleView) {
906 for (ConsoleListener listener : myConsoleListeners) {
907 listener.handleConsoleInitialized(consoleView);
909 myConsoleListeners.clear();
913 public PydevConsoleExecuteActionHandler getConsoleExecuteActionHandler() {
914 return myConsoleExecuteActionHandler;
918 private static class RestartAction extends AnAction {
919 private PydevConsoleRunnerImpl myConsoleRunner;
922 private RestartAction(PydevConsoleRunnerImpl runner) {
923 copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_RERUN));
924 getTemplatePresentation().setIcon(AllIcons.Actions.Restart);
925 myConsoleRunner = runner;
929 public void actionPerformed(AnActionEvent e) {
930 myConsoleRunner.rerun();
934 private void rerun() {
935 new Task.Backgroundable(myProject, "Restarting Console", true) {
937 public void run(@NotNull ProgressIndicator indicator) {
938 if (myProcessHandler != null) {
939 UIUtil.invokeAndWaitIfNeeded((Runnable)() -> closeCommunication());
941 boolean processStopped = myProcessHandler.waitFor(5000L);
942 if (!processStopped && myProcessHandler.canKillProcess()) {
943 myProcessHandler.killProcess();
945 myProcessHandler.waitFor();
948 UIUtil.invokeLaterIfNeeded(() -> {
949 myRerunAction.consume(myConsoleTitle);
956 private class ConnectDebuggerAction extends ToggleAction implements DumbAware {
957 private boolean mySelected = false;
958 private XDebugSession mySession = null;
960 public ConnectDebuggerAction() {
961 super("Attach Debugger", "Enables tracing of code executed in console", AllIcons.Actions.StartDebugger);
965 public boolean isSelected(AnActionEvent e) {
970 public void update(AnActionEvent e) {
971 if (mySession != null) {
972 e.getPresentation().setEnabled(false);
975 e.getPresentation().setEnabled(true);
980 public void setSelected(AnActionEvent e, boolean state) {
985 mySession = connectToDebugger();
987 catch (Exception e1) {
989 Messages.showErrorDialog("Can't connect to debugger", "Error Connecting Debugger");
993 //TODO: disable debugging
999 private static class NewConsoleAction extends AnAction implements DumbAware {
1000 public NewConsoleAction() {
1001 super("New Console", "Creates new python console", AllIcons.General.Add);
1005 public void update(AnActionEvent e) {
1006 e.getPresentation().setEnabled(true);
1010 public void actionPerformed(AnActionEvent e) {
1011 PydevConsoleRunner runner =
1012 PythonConsoleRunnerFactory.getInstance().createConsoleRunner(e.getData(CommonDataKeys.PROJECT), e.getData(LangDataKeys.MODULE));
1017 private XDebugSession connectToDebugger() throws ExecutionException {
1018 final ServerSocket serverSocket = PythonCommandLineState.createServerSocket();
1020 final XDebugSession session = XDebuggerManager.getInstance(myProject).
1021 startSessionAndShowTab("Python Console Debugger", PythonIcons.Python.Python, null, true, new XDebugProcessStarter() {
1023 public XDebugProcess start(@NotNull final XDebugSession session) {
1024 PythonDebugLanguageConsoleView debugConsoleView = new PythonDebugLanguageConsoleView(myProject, mySdk);
1026 PyConsoleDebugProcessHandler consoleDebugProcessHandler =
1027 new PyConsoleDebugProcessHandler(myProcessHandler);
1029 PyConsoleDebugProcess consoleDebugProcess =
1030 new PyConsoleDebugProcess(session, serverSocket, debugConsoleView,
1031 consoleDebugProcessHandler);
1033 PythonDebugConsoleCommunication communication =
1034 PyDebugRunner.initDebugConsoleView(myProject, consoleDebugProcess, debugConsoleView, consoleDebugProcessHandler, session);
1036 communication.addCommunicationListener(new ConsoleCommunicationListener() {
1038 public void commandExecuted(boolean more) {
1039 session.rebuildViews();
1043 public void inputRequested() {
1047 myPydevConsoleCommunication.setDebugCommunication(communication);
1048 debugConsoleView.attachToProcess(consoleDebugProcessHandler);
1050 consoleDebugProcess.waitForNextConnection();
1053 consoleDebugProcess.connect(myPydevConsoleCommunication);
1055 catch (Exception e) {
1056 LOG.error(e); //TODO
1059 myProcessHandler.notifyTextAvailable("\nDebugger connected.\n", ProcessOutputTypes.STDERR);
1061 return consoleDebugProcess;
1069 public PyConsoleProcessHandler getProcessHandler() {
1070 return myProcessHandler;
1074 public PythonConsoleView getConsoleView() {
1075 return myConsoleView;
1078 public static PythonConsoleRunnerFactory factory() {
1079 return new PydevConsoleRunnerFactory();
1082 private static class PythonConsoleRunParams implements PythonRunParams {
1083 private final PyConsoleOptions.PyConsoleSettings myConsoleSettings;
1084 private String myWorkingDir;
1086 private Map<String, String> myEnvironmentVariables;
1088 public PythonConsoleRunParams(@NotNull PyConsoleOptions.PyConsoleSettings consoleSettings,
1089 @NotNull String workingDir,
1091 @NotNull Map<String, String> envs) {
1092 myConsoleSettings = consoleSettings;
1093 myWorkingDir = workingDir;
1095 myEnvironmentVariables = envs;
1096 myEnvironmentVariables.putAll(consoleSettings.getEnvs());
1100 public String getInterpreterOptions() {
1101 return myConsoleSettings.getInterpreterOptions();
1105 public void setInterpreterOptions(String interpreterOptions) {
1106 throw new UnsupportedOperationException();
1110 public String getWorkingDirectory() {
1111 return myWorkingDir;
1115 public void setWorkingDirectory(String workingDirectory) {
1116 throw new UnsupportedOperationException();
1121 public String getSdkHome() {
1122 return mySdk.getHomePath();
1126 public void setSdkHome(String sdkHome) {
1127 throw new UnsupportedOperationException();
1131 public void setModule(Module module) {
1132 throw new UnsupportedOperationException();
1136 public String getModuleName() {
1137 return myConsoleSettings.getModuleName();
1141 public boolean isUseModuleSdk() {
1142 return myConsoleSettings.isUseModuleSdk();
1146 public void setUseModuleSdk(boolean useModuleSdk) {
1147 throw new UnsupportedOperationException();
1151 public boolean isPassParentEnvs() {
1152 return myConsoleSettings.isPassParentEnvs();
1156 public void setPassParentEnvs(boolean passParentEnvs) {
1157 throw new UnsupportedOperationException();
1161 public Map<String, String> getEnvs() {
1162 return myEnvironmentVariables;
1166 public void setEnvs(Map<String, String> envs) {
1167 throw new UnsupportedOperationException();
1172 public PathMappingSettings getMappingSettings() {
1173 throw new UnsupportedOperationException();
1177 public void setMappingSettings(@Nullable PathMappingSettings mappingSettings) {
1178 throw new UnsupportedOperationException();
1182 public boolean shouldAddContentRoots() {
1183 return myConsoleSettings.shouldAddContentRoots();
1187 public boolean shouldAddSourceRoots() {
1188 return myConsoleSettings.shouldAddSourceRoots();
1192 public void setAddContentRoots(boolean flag) {
1193 throw new UnsupportedOperationException();
1197 public void setAddSourceRoots(boolean flag) {
1198 throw new UnsupportedOperationException();