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.intellij.codeInsight.lookup.LookupManager;
20 import com.intellij.execution.ExecutionException;
21 import com.intellij.execution.ExecutionHelper;
22 import com.intellij.execution.ExecutionManager;
23 import com.intellij.execution.Executor;
24 import com.intellij.execution.configurations.EncodingEnvironmentUtil;
25 import com.intellij.execution.configurations.GeneralCommandLine;
26 import com.intellij.execution.configurations.ParamsGroup;
27 import com.intellij.execution.configurations.PtyCommandLine;
28 import com.intellij.execution.console.ConsoleExecuteAction;
29 import com.intellij.execution.console.ConsoleHistoryController;
30 import com.intellij.execution.console.LanguageConsoleView;
31 import com.intellij.execution.executors.DefaultRunExecutor;
32 import com.intellij.execution.process.ProcessAdapter;
33 import com.intellij.execution.process.ProcessEvent;
34 import com.intellij.execution.process.ProcessOutputTypes;
35 import com.intellij.execution.process.ProcessTerminatedListener;
36 import com.intellij.execution.runners.ConsoleTitleGen;
37 import com.intellij.execution.ui.RunContentDescriptor;
38 import com.intellij.execution.ui.actions.CloseAction;
39 import com.intellij.icons.AllIcons;
40 import com.intellij.ide.CommonActionsManager;
41 import com.intellij.ide.errorTreeView.NewErrorTreeViewPanel;
42 import com.intellij.internal.statistic.UsageTrigger;
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.Result;
47 import com.intellij.openapi.command.WriteCommandAction;
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.openapi.vfs.encoding.EncodingProjectManager;
72 import com.intellij.psi.PsiFile;
73 import com.intellij.remote.RemoteProcess;
74 import com.intellij.remote.Tunnelable;
75 import com.intellij.testFramework.LightVirtualFile;
76 import com.intellij.ui.JBColor;
77 import com.intellij.ui.SideBorder;
78 import com.intellij.util.ArrayUtil;
79 import com.intellij.util.IJSwingUtilities;
80 import com.intellij.util.PathMappingSettings;
81 import com.intellij.util.TimeoutUtil;
82 import com.intellij.util.containers.ContainerUtil;
83 import com.intellij.util.net.NetUtils;
84 import com.intellij.util.ui.MessageCategory;
85 import com.intellij.util.ui.UIUtil;
86 import com.intellij.xdebugger.XDebugProcess;
87 import com.intellij.xdebugger.XDebugProcessStarter;
88 import com.intellij.xdebugger.XDebugSession;
89 import com.intellij.xdebugger.XDebuggerManager;
90 import com.jetbrains.python.PythonHelper;
91 import com.jetbrains.python.console.pydev.ConsoleCommunicationListener;
92 import com.jetbrains.python.debugger.PyDebugRunner;
93 import com.jetbrains.python.debugger.PySourcePosition;
94 import com.jetbrains.python.remote.PyRemotePathMapper;
95 import com.jetbrains.python.remote.PyRemoteProcessHandlerBase;
96 import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase;
97 import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
98 import com.jetbrains.python.run.*;
99 import com.jetbrains.python.sdk.PySdkUtil;
100 import icons.PythonIcons;
101 import org.apache.xmlrpc.XmlRpcException;
102 import org.jetbrains.annotations.NotNull;
103 import org.jetbrains.annotations.Nullable;
105 import javax.swing.*;
107 import java.awt.event.InputEvent;
108 import java.awt.event.KeyEvent;
110 import java.io.IOException;
111 import java.net.ServerSocket;
112 import java.nio.charset.Charset;
114 import java.util.List;
116 import static com.intellij.execution.runners.AbstractConsoleRunnerWithHistory.registerActionShortcuts;
121 public class PydevConsoleRunnerImpl implements PydevConsoleRunner {
122 public static final String WORKING_DIR_ENV = "WORKING_DIR_AND_PYTHON_PATHS";
123 public static final String CONSOLE_START_COMMAND = "import sys; print('Python %s on %s' % (sys.version, sys.platform))\n" +
124 "sys.path.extend([" + WORKING_DIR_ENV + "])\n";
125 private static final Logger LOG = Logger.getInstance(PydevConsoleRunnerImpl.class.getName());
126 @SuppressWarnings("SpellCheckingInspection")
127 public static final String PYDEV_PYDEVCONSOLE_PY = "pydev/pydevconsole.py";
128 public static final int PORTS_WAITING_TIMEOUT = 20000;
129 private static final String CONSOLE_FEATURE = "python.console";
130 private final Project myProject;
131 private final String myTitle;
132 private final String myWorkingDir;
133 private final Executor myExecutor;
137 private GeneralCommandLine myGeneralCommandLine;
138 protected int[] myPorts;
139 private PydevConsoleCommunication myPydevConsoleCommunication;
140 private PyConsoleProcessHandler myProcessHandler;
141 protected PydevConsoleExecuteActionHandler myConsoleExecuteActionHandler;
142 private List<ConsoleListener> myConsoleListeners = ContainerUtil.createLockFreeCopyOnWriteList();
143 private final PyConsoleType myConsoleType;
144 private Map<String, String> myEnvironmentVariables;
145 private String myCommandLine;
146 @NotNull private final PyConsoleOptions.PyConsoleSettings myConsoleSettings;
147 private String[] myStatementsToExecute = ArrayUtil.EMPTY_STRING_ARRAY;
150 private static final long APPROPRIATE_TO_WAIT = 60000;
152 private PyRemoteProcessHandlerBase myRemoteProcessHandlerBase;
154 private String myConsoleTitle = null;
155 private PythonConsoleView myConsoleView;
157 public PydevConsoleRunnerImpl(@NotNull final Project project,
159 @NotNull final PyConsoleType consoleType,
160 @Nullable final String workingDir,
161 Map<String, String> environmentVariables,
163 PyConsoleOptions.PyConsoleSettings settingsProvider,
164 String... statementsToExecute) {
167 myTitle = consoleType.getTitle();
168 myWorkingDir = workingDir;
169 myConsoleType = consoleType;
170 myEnvironmentVariables = environmentVariables;
171 myConsoleSettings = settingsProvider;
172 myStatementsToExecute = statementsToExecute;
173 PyConsoleToolWindowExecutor toolWindowExecutor = PyConsoleToolWindowExecutor.findInstance();
174 myExecutor = toolWindowExecutor != null ? toolWindowExecutor : DefaultRunExecutor.getRunExecutorInstance();
178 private List<AnAction> fillToolBarActions(final DefaultActionGroup toolbarActions,
179 final RunContentDescriptor contentDescriptor) {
180 //toolbarActions.add(backspaceHandlingAction);
182 toolbarActions.add(createRerunAction());
184 List<AnAction> actions = ContainerUtil.newArrayList();
187 actions.add(createStopAction());
190 actions.add(createCloseAction(contentDescriptor));
194 new ConsoleExecuteAction(myConsoleView, myConsoleExecuteActionHandler, myConsoleExecuteActionHandler.getEmptyExecuteAction(),
195 myConsoleExecuteActionHandler));
198 actions.add(CommonActionsManager.getInstance().createHelpAction("interactive_console"));
200 toolbarActions.addAll(actions);
203 actions.add(0, createRerunAction());
205 actions.add(createBackspaceHandlingAction());
206 actions.add(createInterruptAction());
207 actions.add(createTabCompletionAction());
209 actions.add(createSplitLineAction());
211 toolbarActions.add(new ShowVarsAction());
212 toolbarActions.add(ConsoleHistoryController.getController(myConsoleView).getBrowseHistory());
214 toolbarActions.add(new ConnectDebuggerAction());
216 toolbarActions.add(new NewConsoleAction());
222 public void runSync() {
223 myPorts = findAvailablePorts(myProject, myConsoleType);
225 assert myPorts != null;
227 myGeneralCommandLine = createCommandLine(mySdk, myEnvironmentVariables, myWorkingDir, myPorts);
228 myCommandLine = myGeneralCommandLine.getCommandLineString();
233 catch (ExecutionException e) {
234 LOG.warn("Error running console", e);
235 ExecutionHelper.showErrors(myProject, Collections.<Exception>singletonList(e), "Python Console", null);
238 ProgressManager.getInstance().run(new Task.Backgroundable(myProject, "Connecting to Console", false) {
240 public void run(@NotNull final ProgressIndicator indicator) {
241 indicator.setText("Connecting to console...");
242 connect(myStatementsToExecute);
250 ApplicationManager.getApplication().invokeAndWait(() -> FileDocumentManager.getInstance().saveAllDocuments());
252 myPorts = findAvailablePorts(myProject, myConsoleType);
254 assert myPorts != null;
256 myGeneralCommandLine = createCommandLine(mySdk, myEnvironmentVariables, myWorkingDir, myPorts);
257 myCommandLine = myGeneralCommandLine.getCommandLineString();
260 .invokeLaterIfNeeded(() -> ProgressManager.getInstance().run(new Task.Backgroundable(myProject, "Connecting to Console", false) {
262 public void run(@NotNull final ProgressIndicator indicator) {
263 indicator.setText("Connecting to console...");
266 connect(myStatementsToExecute);
268 catch (final Exception e) {
269 LOG.warn("Error running console", e);
270 UIUtil.invokeAndWaitIfNeeded(new Runnable() {
273 showErrorsInConsole(e);
281 private void showErrorsInConsole(Exception e) {
283 DefaultActionGroup actionGroup = new DefaultActionGroup(createRerunAction());
285 final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN,
289 final JPanel panel = new JPanel(new BorderLayout());
290 panel.add(actionToolbar.getComponent(), BorderLayout.WEST);
292 NewErrorTreeViewPanel errorViewPanel = new NewErrorTreeViewPanel(myProject, null, false, false, null);
294 String[] messages = StringUtil.isNotEmpty(e.getMessage()) ? StringUtil.splitByLines(e.getMessage()) : ArrayUtil.EMPTY_STRING_ARRAY;
295 if (messages.length == 0) {
296 messages = new String[]{"Unknown error"};
299 errorViewPanel.addMessage(MessageCategory.ERROR, messages, null, -1, -1, null);
300 panel.add(errorViewPanel, BorderLayout.CENTER);
303 final RunContentDescriptor contentDescriptor =
304 new RunContentDescriptor(null, myProcessHandler, panel, "Error running console");
306 actionGroup.add(createCloseAction(contentDescriptor));
308 ExecutionManager.getInstance(myProject).getContentManager().showRunContent(myExecutor, contentDescriptor);
311 private static int[] findAvailablePorts(Project project, PyConsoleType consoleType) {
314 // File "pydev/console/pydevconsole.py", line 223, in <module>
315 // port, client_port = sys.argv[1:3]
316 ports = NetUtils.findAvailableSocketPorts(2);
318 catch (IOException e) {
319 ExecutionHelper.showErrors(project, Collections.<Exception>singletonList(e), consoleType.getTitle(), null);
325 protected GeneralCommandLine createCommandLine(@NotNull final Sdk sdk,
326 @NotNull final Map<String, String> environmentVariables,
327 String workingDir, int[] ports) {
328 return doCreateConsoleCmdLine(sdk, environmentVariables, workingDir, ports, PythonHelper.CONSOLE);
332 protected GeneralCommandLine doCreateConsoleCmdLine(Sdk sdk,
333 Map<String, String> environmentVariables,
336 PythonHelper helper) {
337 GeneralCommandLine cmd =
338 PythonCommandLineState.createPythonCommandLine(myProject, new PythonConsoleRunParams(myConsoleSettings, workingDir, sdk,
339 environmentVariables), false,
340 PtyCommandLine.isEnabled() && !SystemInfo.isWindows);
341 cmd.withWorkDirectory(myWorkingDir);
343 ParamsGroup group = cmd.getParametersList().getParamsGroup(PythonCommandLineState.GROUP_SCRIPT);
344 helper.addToGroup(group, cmd);
346 for (int port : ports) {
347 group.addParameter(String.valueOf(port));
353 private PythonConsoleView createConsoleView() {
354 PythonConsoleView consoleView = new PythonConsoleView(myProject, myTitle, mySdk);
355 myPydevConsoleCommunication.setConsoleFile(consoleView.getVirtualFile());
356 consoleView.addMessageFilter(new PythonTracebackFilter(myProject));
360 private Process createProcess() throws ExecutionException {
361 if (PySdkUtil.isRemote(mySdk)) {
362 PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
363 if (manager != null) {
364 UsageTrigger.trigger(CONSOLE_FEATURE + ".remote");
365 return createRemoteConsoleProcess(manager, myGeneralCommandLine.getParametersList().getArray(),
366 myGeneralCommandLine.getEnvironment(), myGeneralCommandLine.getWorkDirectory());
368 throw new PythonRemoteInterpreterManager.PyRemoteInterpreterExecutionException();
371 myCommandLine = myGeneralCommandLine.getCommandLineString();
372 Map<String, String> envs = myGeneralCommandLine.getEnvironment();
373 EncodingEnvironmentUtil.setLocaleEnvironmentIfMac(envs, myGeneralCommandLine.getCharset());
375 UsageTrigger.trigger(CONSOLE_FEATURE + ".local");
376 final Process server = myGeneralCommandLine.createProcess();
379 myPydevConsoleCommunication = new PydevConsoleCommunication(myProject, myPorts[0], server, myPorts[1]);
381 catch (Exception e) {
382 throw new ExecutionException(e.getMessage());
388 private RemoteProcess createRemoteConsoleProcess(PythonRemoteInterpreterManager manager,
390 Map<String, String> env,
392 throws ExecutionException {
393 PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData();
396 GeneralCommandLine commandLine = new GeneralCommandLine();
398 commandLine.setWorkDirectory(workDirectory);
400 commandLine.withParameters(command);
402 commandLine.getEnvironment().putAll(env);
404 commandLine.getParametersList().set(0, PythonRemoteInterpreterManager.toSystemDependent(new File(data.getHelpersPath(),
405 PYDEV_PYDEVCONSOLE_PY)
407 PySourcePosition.isWindowsPath(
408 data.getInterpreterPath())
410 commandLine.getParametersList().set(1, "0");
411 commandLine.getParametersList().set(2, "0");
414 PyRemotePathMapper pathMapper = PydevConsoleRunner.getPathMapper(myProject, mySdk, myConsoleSettings);
416 assert pathMapper != null;
418 commandLine.putUserData(PyRemoteProcessStarter.OPEN_FOR_INCOMING_CONNECTION, true);
420 myRemoteProcessHandlerBase = PyRemoteProcessStarterManagerUtil
421 .getManager(data).startRemoteProcess(myProject, commandLine, manager, data,
424 myCommandLine = myRemoteProcessHandlerBase.getCommandLine();
426 RemoteProcess remoteProcess = myRemoteProcessHandlerBase.getProcess();
428 Couple<Integer> remotePorts = getRemotePortsFromProcess(remoteProcess);
430 if (remoteProcess instanceof Tunnelable) {
431 Tunnelable tunnelableProcess = (Tunnelable)remoteProcess;
432 tunnelableProcess.addLocalTunnel(myPorts[0], remotePorts.first);
433 tunnelableProcess.addRemoteTunnel(remotePorts.second, "localhost", myPorts[1]);
435 if (LOG.isDebugEnabled()) {
436 LOG.debug(String.format("Using tunneled communication for Python console: port %d (=> %d) on IDE side, " +
437 "port %d (=> %d) on pydevconsole.py side", myPorts[1], remotePorts.second, myPorts[0],
441 myPydevConsoleCommunication = new PydevRemoteConsoleCommunication(myProject, myPorts[0], remoteProcess, myPorts[1]);
444 if (LOG.isDebugEnabled()) {
445 LOG.debug(String.format("Using direct communication for Python console: port %d on IDE side, port %d on pydevconsole.py side",
446 remotePorts.second, remotePorts.first));
449 myPydevConsoleCommunication =
450 new PydevRemoteConsoleCommunication(myProject, remotePorts.first, remoteProcess, remotePorts.second);
453 return remoteProcess;
455 catch (Exception e) {
456 throw new ExecutionException(e.getMessage(), e);
460 private static Couple<Integer> getRemotePortsFromProcess(RemoteProcess process) throws ExecutionException {
461 Scanner s = new Scanner(process.getInputStream());
463 return Couple.of(readInt(s, process), readInt(s, process));
466 private static int readInt(Scanner s, Process process) throws ExecutionException {
467 long started = System.currentTimeMillis();
469 StringBuilder sb = new StringBuilder();
470 boolean flag = false;
472 while (System.currentTimeMillis() - started < PORTS_WAITING_TIMEOUT) {
473 if (s.hasNextLine()) {
474 String line = s.nextLine();
475 sb.append(line).append("\n");
477 int i = Integer.parseInt(line);
479 LOG.warn("Unexpected strings in output:\n" + sb.toString());
483 catch (NumberFormatException ignored) {
489 TimeoutUtil.sleep(200);
491 if (process.exitValue() != 0) {
494 error = "Console process terminated with error:\n" + StreamUtil.readText(process.getErrorStream()) + sb.toString();
496 catch (Exception ignored) {
497 error = "Console process terminated with exit code " + process.exitValue() + ", output:" + sb.toString();
499 throw new ExecutionException(error);
506 throw new ExecutionException("Couldn't read integer value from stream");
509 private PyConsoleProcessHandler createProcessHandler(final Process process) {
510 if (PySdkUtil.isRemote(mySdk)) {
511 PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
512 if (manager != null) {
513 PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData();
516 manager.createConsoleProcessHandler((RemoteProcess)process, myConsoleView, myPydevConsoleCommunication,
517 myCommandLine, CharsetToolkit.UTF8_CHARSET,
518 manager.setupMappings(myProject, data, null),
519 myRemoteProcessHandlerBase.getRemoteSocketToLocalHostProvider());
522 LOG.error("Can't create remote console process handler");
526 myProcessHandler = new PyConsoleProcessHandler(process, myConsoleView, myPydevConsoleCommunication, myCommandLine,
527 CharsetToolkit.UTF8_CHARSET);
529 return myProcessHandler;
533 private void initAndRun() throws ExecutionException {
534 // Create Server process
535 final Process process = createProcess();
536 UIUtil.invokeLaterIfNeeded(() -> {
538 myConsoleView = createConsoleView();
539 if (myConsoleView != null) {
540 ((JComponent)myConsoleView).setBorder(new SideBorder(JBColor.border(), SideBorder.LEFT));
542 myProcessHandler = createProcessHandler(process);
544 myConsoleExecuteActionHandler = createExecuteActionHandler();
546 ProcessTerminatedListener.attach(myProcessHandler);
548 PythonConsoleView consoleView = myConsoleView;
549 myProcessHandler.addProcessListener(new ProcessAdapter() {
551 public void processTerminated(ProcessEvent event) {
552 consoleView.setEditable(false);
557 myConsoleView.attachToProcess(myProcessHandler);
558 createContentDescriptorAndActions();
562 myProcessHandler.startNotify();
566 protected void createContentDescriptorAndActions() {
568 final DefaultActionGroup toolbarActions = new DefaultActionGroup();
569 final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarActions, false);
572 final JPanel panel = new JPanel(new BorderLayout());
573 panel.add(actionToolbar.getComponent(), BorderLayout.WEST);
574 panel.add(myConsoleView.getComponent(), BorderLayout.CENTER);
576 actionToolbar.setTargetComponent(panel);
578 final RunContentDescriptor contentDescriptor =
579 new RunContentDescriptor(myConsoleView, myProcessHandler, panel, new ConsoleTitleGen(myProject, myTitle).makeTitle(), null);
581 contentDescriptor.setFocusComputable(() -> myConsoleView.getConsoleEditor().getContentComponent());
582 contentDescriptor.setAutoFocusContent(true);
586 final List<AnAction> actions = fillToolBarActions(toolbarActions, contentDescriptor);
587 registerActionShortcuts(actions, myConsoleView.getConsoleEditor().getComponent());
588 registerActionShortcuts(actions, panel);
590 ExecutionManager.getInstance(myProject).getContentManager().showRunContent(myExecutor, contentDescriptor);
593 private void connect(final String[] statements2execute) {
595 ApplicationManager.getApplication().invokeLater(() -> {
596 // Propagate console communication to language console
597 final PythonConsoleView consoleView = myConsoleView;
599 consoleView.setConsoleCommunication(myPydevConsoleCommunication);
600 consoleView.setSdk(mySdk);
601 consoleView.setExecutionHandler(myConsoleExecuteActionHandler);
602 myProcessHandler.addProcessListener(new ProcessAdapter() {
604 public void onTextAvailable(ProcessEvent event, Key outputType) {
605 consoleView.print(event.getText(), outputType);
609 enableConsoleExecuteAction();
611 for (String statement : statements2execute) {
612 consoleView.executeStatement(statement + "\n", ProcessOutputTypes.SYSTEM);
615 fireConsoleInitializedEvent(consoleView);
619 myConsoleView.print("Couldn't connect to console process.", ProcessOutputTypes.STDERR);
620 myProcessHandler.destroyProcess();
621 myConsoleView.setEditable(false);
626 protected AnAction createRerunAction() {
627 return new RestartAction(this);
630 private AnAction createInterruptAction() {
631 AnAction anAction = new AnAction() {
633 public void actionPerformed(final AnActionEvent e) {
634 if (myPydevConsoleCommunication.isExecuting()) {
635 myConsoleView.print("^C", ProcessOutputTypes.SYSTEM);
637 myPydevConsoleCommunication.interrupt();
641 public void update(final AnActionEvent e) {
642 EditorEx consoleEditor = myConsoleView.getConsoleEditor();
643 boolean enabled = IJSwingUtilities.hasFocus(consoleEditor.getComponent()) && !consoleEditor.getSelectionModel().hasSelection();
644 e.getPresentation().setEnabled(enabled);
648 .registerCustomShortcutSet(KeyEvent.VK_C, InputEvent.CTRL_MASK, myConsoleView.getConsoleEditor().getComponent());
649 anAction.getTemplatePresentation().setVisible(false);
653 private AnAction createTabCompletionAction() {
654 final AnAction runCompletions = new AnAction() {
656 public void actionPerformed(AnActionEvent e) {
658 Editor editor = myConsoleView.getConsoleEditor();
659 if (LookupManager.getActiveLookup(editor) != null) {
660 AnAction replace = ActionManager.getInstance().getAction("EditorChooseLookupItemReplace");
661 ActionUtil.performActionDumbAware(replace, e);
664 AnAction completionAction = ActionManager.getInstance().getAction("CodeCompletion");
665 if (completionAction == null) {
668 ActionUtil.performActionDumbAware(completionAction, e);
672 public void update(AnActionEvent e) {
673 Editor editor = myConsoleView.getConsoleEditor();
674 if (LookupManager.getActiveLookup(editor) != null) {
675 e.getPresentation().setEnabled(false);
677 int offset = editor.getCaretModel().getOffset();
678 Document document = editor.getDocument();
679 int lineStart = document.getLineStartOffset(document.getLineNumber(offset));
680 String textToCursor = document.getText(new TextRange(lineStart, offset));
681 e.getPresentation().setEnabled(!CharMatcher.WHITESPACE.matchesAllOf(textToCursor));
686 .registerCustomShortcutSet(KeyEvent.VK_TAB, 0, myConsoleView.getConsoleEditor().getComponent());
687 runCompletions.getTemplatePresentation().setVisible(false);
688 return runCompletions;
692 private AnAction createBackspaceHandlingAction() {
693 final AnAction upAction = new AnAction() {
695 public void actionPerformed(final AnActionEvent e) {
696 new WriteCommandAction(myProject, myConsoleView.getFile()) {
698 protected void run(@NotNull final Result result) throws Throwable {
699 String text = myConsoleView.getEditorDocument().getText();
700 String newText = text.substring(0, text.length() - myConsoleExecuteActionHandler.getPythonIndent());
701 myConsoleView.getEditorDocument().setText(newText);
702 myConsoleView.getConsoleEditor().getCaretModel().moveToOffset(newText.length());
708 public void update(final AnActionEvent e) {
710 .setEnabled(myConsoleExecuteActionHandler.getCurrentIndentSize() >= myConsoleExecuteActionHandler.getPythonIndent() &&
711 isIndentSubstring(myConsoleView.getEditorDocument().getText()));
714 upAction.registerCustomShortcutSet(KeyEvent.VK_BACK_SPACE, 0, null);
715 upAction.getTemplatePresentation().setVisible(false);
719 private boolean isIndentSubstring(String text) {
720 int indentSize = myConsoleExecuteActionHandler.getPythonIndent();
721 return text.length() >= indentSize && CharMatcher.WHITESPACE.matchesAllOf(text.substring(text.length() - indentSize));
724 private void enableConsoleExecuteAction() {
725 myConsoleExecuteActionHandler.setEnabled(true);
728 private boolean handshake() {
730 long started = System.currentTimeMillis();
733 res = myPydevConsoleCommunication.handshake();
735 catch (XmlRpcException ignored) {
742 long now = System.currentTimeMillis();
743 if (now - started > APPROPRIATE_TO_WAIT) {
747 TimeoutUtil.sleep(100);
756 private AnAction createStopAction() {
757 AnAction generalStopAction = ActionManager.getInstance().getAction(IdeActions.ACTION_STOP_PROGRAM);
758 final AnAction stopAction = new DumbAwareAction() {
760 public void update(AnActionEvent e) {
761 generalStopAction.update(e);
765 public void actionPerformed(AnActionEvent e) {
768 generalStopAction.actionPerformed(e);
771 stopAction.copyFrom(generalStopAction);
775 private AnAction createCloseAction(final RunContentDescriptor descriptor) {
776 final AnAction generalCloseAction = new CloseAction(myExecutor, descriptor, myProject);
778 final AnAction stopAction = new DumbAwareAction() {
780 public void update(AnActionEvent e) {
781 generalCloseAction.update(e);
785 public void actionPerformed(AnActionEvent e) {
788 clearContent(descriptor);
790 generalCloseAction.actionPerformed(e);
793 stopAction.copyFrom(generalCloseAction);
797 protected void clearContent(RunContentDescriptor descriptor) {
800 private AnActionEvent stopConsole(AnActionEvent e) {
801 if (myPydevConsoleCommunication != null) {
802 e = new AnActionEvent(e.getInputEvent(), e.getDataContext(), e.getPlace(),
803 e.getPresentation(), e.getActionManager(), e.getModifiers());
805 closeCommunication();
806 // waiting for REPL communication before destroying process handler
809 catch (Exception ignored) {
816 protected AnAction createSplitLineAction() {
818 class ConsoleSplitLineAction extends EditorAction {
820 private static final String CONSOLE_SPLIT_LINE_ACTION_ID = "Console.SplitLine";
822 public ConsoleSplitLineAction() {
823 super(new EditorWriteActionHandler() {
825 private final SplitLineAction mySplitLineAction = new SplitLineAction();
828 public boolean isEnabled(Editor editor, DataContext dataContext) {
829 return mySplitLineAction.getHandler().isEnabled(editor, dataContext);
833 public void executeWriteAction(Editor editor, @Nullable Caret caret, DataContext dataContext) {
834 ((EditorWriteActionHandler)mySplitLineAction.getHandler()).executeWriteAction(editor, caret, dataContext);
835 editor.getCaretModel().getCurrentCaret().moveCaretRelatively(0, 1, false, true);
840 public void setup() {
841 EmptyAction.setupAction(this, CONSOLE_SPLIT_LINE_ACTION_ID, null);
845 ConsoleSplitLineAction action = new ConsoleSplitLineAction();
850 private void closeCommunication() {
851 if (!myProcessHandler.isProcessTerminated()) {
852 myPydevConsoleCommunication.close();
857 protected PydevConsoleExecuteActionHandler createExecuteActionHandler() {
858 myConsoleExecuteActionHandler =
859 new PydevConsoleExecuteActionHandler(myConsoleView, myProcessHandler, myPydevConsoleCommunication);
860 myConsoleExecuteActionHandler.setEnabled(false);
861 new ConsoleHistoryController(myConsoleType.getTypeId(), "", myConsoleView).install();
862 return myConsoleExecuteActionHandler;
866 public PydevConsoleCommunication getPydevConsoleCommunication() {
867 return myPydevConsoleCommunication;
870 static VirtualFile getConsoleFile(PsiFile psiFile) {
871 VirtualFile file = psiFile.getViewProvider().getVirtualFile();
872 if (file instanceof LightVirtualFile) {
873 file = ((LightVirtualFile)file).getOriginalFile();
879 public void addConsoleListener(ConsoleListener consoleListener) {
880 myConsoleListeners.add(consoleListener);
883 private void fireConsoleInitializedEvent(LanguageConsoleView consoleView) {
884 for (ConsoleListener listener : myConsoleListeners) {
885 listener.handleConsoleInitialized(consoleView);
887 myConsoleListeners.clear();
891 public PydevConsoleExecuteActionHandler getConsoleExecuteActionHandler() {
892 return myConsoleExecuteActionHandler;
896 private static class RestartAction extends AnAction {
897 private PydevConsoleRunnerImpl myConsoleRunner;
900 private RestartAction(PydevConsoleRunnerImpl runner) {
901 copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_RERUN));
902 getTemplatePresentation().setIcon(AllIcons.Actions.Restart);
903 myConsoleRunner = runner;
907 public void actionPerformed(AnActionEvent e) {
908 myConsoleRunner.rerun();
912 private void rerun() {
913 new Task.Backgroundable(myProject, "Restarting Console", true) {
915 public void run(@NotNull ProgressIndicator indicator) {
916 if (myProcessHandler != null) {
917 UIUtil.invokeLaterIfNeeded(() -> closeCommunication());
919 myProcessHandler.waitFor();
922 UIUtil.invokeLaterIfNeeded(() -> PydevConsoleRunnerImpl.this.run());
927 private class ShowVarsAction extends ToggleAction implements DumbAware {
928 private boolean mySelected = false;
930 public ShowVarsAction() {
931 super("Show Variables", "Shows active console variables", AllIcons.Debugger.Watches);
935 public boolean isSelected(AnActionEvent e) {
940 public void setSelected(AnActionEvent e, boolean state) {
944 myConsoleView.showVariables(myPydevConsoleCommunication);
947 myConsoleView.restoreWindow();
953 private class ConnectDebuggerAction extends ToggleAction implements DumbAware {
954 private boolean mySelected = false;
955 private XDebugSession mySession = null;
957 public ConnectDebuggerAction() {
958 super("Attach Debugger", "Enables tracing of code executed in console", AllIcons.Actions.StartDebugger);
962 public boolean isSelected(AnActionEvent e) {
967 public void update(AnActionEvent e) {
968 if (mySession != null) {
969 e.getPresentation().setEnabled(false);
972 e.getPresentation().setEnabled(true);
977 public void setSelected(AnActionEvent e, boolean state) {
982 mySession = connectToDebugger();
984 catch (Exception e1) {
986 Messages.showErrorDialog("Can't connect to debugger", "Error Connecting Debugger");
990 //TODO: disable debugging
996 private static class NewConsoleAction extends AnAction implements DumbAware {
997 public NewConsoleAction() {
998 super("New Console", "Creates new python console", AllIcons.General.Add);
1002 public void update(AnActionEvent e) {
1003 e.getPresentation().setEnabled(true);
1007 public void actionPerformed(AnActionEvent e) {
1008 PydevConsoleRunner runner =
1009 PythonConsoleRunnerFactory.getInstance().createConsoleRunner(e.getData(CommonDataKeys.PROJECT), e.getData(LangDataKeys.MODULE));
1014 private XDebugSession connectToDebugger() throws ExecutionException {
1015 final ServerSocket serverSocket = PythonCommandLineState.createServerSocket();
1017 final XDebugSession session = XDebuggerManager.getInstance(myProject).
1018 startSessionAndShowTab("Python Console Debugger", PythonIcons.Python.Python, null, true, new XDebugProcessStarter() {
1020 public XDebugProcess start(@NotNull final XDebugSession session) {
1021 PythonDebugLanguageConsoleView debugConsoleView = new PythonDebugLanguageConsoleView(myProject, mySdk);
1023 PyConsoleDebugProcessHandler consoleDebugProcessHandler =
1024 new PyConsoleDebugProcessHandler(myProcessHandler);
1026 PyConsoleDebugProcess consoleDebugProcess =
1027 new PyConsoleDebugProcess(session, serverSocket, debugConsoleView,
1028 consoleDebugProcessHandler);
1030 PythonDebugConsoleCommunication communication =
1031 PyDebugRunner.initDebugConsoleView(myProject, consoleDebugProcess, debugConsoleView, consoleDebugProcessHandler, session);
1033 communication.addCommunicationListener(new ConsoleCommunicationListener() {
1035 public void commandExecuted(boolean more) {
1036 session.rebuildViews();
1040 public void inputRequested() {
1044 myPydevConsoleCommunication.setDebugCommunication(communication);
1045 debugConsoleView.attachToProcess(consoleDebugProcessHandler);
1047 consoleDebugProcess.waitForNextConnection();
1050 consoleDebugProcess.connect(myPydevConsoleCommunication);
1052 catch (Exception e) {
1053 LOG.error(e); //TODO
1056 myProcessHandler.notifyTextAvailable("\nDebugger connected.\n", ProcessOutputTypes.STDERR);
1058 return consoleDebugProcess;
1066 public PyConsoleProcessHandler getProcessHandler() {
1067 return myProcessHandler;
1071 public PythonConsoleView getConsoleView() {
1072 return myConsoleView;
1075 public static PythonConsoleRunnerFactory factory() {
1076 return new PydevConsoleRunnerFactory();
1079 private static class PythonConsoleRunParams implements PythonRunParams {
1080 private final PyConsoleOptions.PyConsoleSettings myConsoleSettings;
1081 private String myWorkingDir;
1083 private Map<String, String> myEnvironmentVariables;
1085 public PythonConsoleRunParams(@NotNull PyConsoleOptions.PyConsoleSettings consoleSettings,
1086 @NotNull String workingDir,
1088 @NotNull Map<String, String> envs) {
1089 myConsoleSettings = consoleSettings;
1090 myWorkingDir = workingDir;
1092 myEnvironmentVariables = envs;
1093 myEnvironmentVariables.putAll(consoleSettings.getEnvs());
1097 public String getInterpreterOptions() {
1098 return myConsoleSettings.getInterpreterOptions();
1102 public void setInterpreterOptions(String interpreterOptions) {
1103 throw new UnsupportedOperationException();
1107 public String getWorkingDirectory() {
1108 return myWorkingDir;
1112 public void setWorkingDirectory(String workingDirectory) {
1113 throw new UnsupportedOperationException();
1118 public String getSdkHome() {
1119 return mySdk.getHomePath();
1123 public void setSdkHome(String sdkHome) {
1124 throw new UnsupportedOperationException();
1128 public void setModule(Module module) {
1129 throw new UnsupportedOperationException();
1133 public String getModuleName() {
1134 return myConsoleSettings.getModuleName();
1138 public boolean isUseModuleSdk() {
1139 return myConsoleSettings.isUseModuleSdk();
1143 public void setUseModuleSdk(boolean useModuleSdk) {
1144 throw new UnsupportedOperationException();
1148 public boolean isPassParentEnvs() {
1149 return myConsoleSettings.isPassParentEnvs();
1153 public void setPassParentEnvs(boolean passParentEnvs) {
1154 throw new UnsupportedOperationException();
1158 public Map<String, String> getEnvs() {
1159 return myEnvironmentVariables;
1163 public void setEnvs(Map<String, String> envs) {
1164 throw new UnsupportedOperationException();
1169 public PathMappingSettings getMappingSettings() {
1170 throw new UnsupportedOperationException();
1174 public void setMappingSettings(@Nullable PathMappingSettings mappingSettings) {
1175 throw new UnsupportedOperationException();
1179 public boolean shouldAddContentRoots() {
1180 return myConsoleSettings.shouldAddContentRoots();
1184 public boolean shouldAddSourceRoots() {
1185 return myConsoleSettings.shouldAddSourceRoots();
1189 public void setAddContentRoots(boolean flag) {
1190 throw new UnsupportedOperationException();
1194 public void setAddSourceRoots(boolean flag) {
1195 throw new UnsupportedOperationException();