971f2d1bc909aefe25c80668f7743119392fa8b2
[idea/community.git] / python / src / com / jetbrains / python / console / PydevConsoleRunnerImpl.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.jetbrains.python.console;
17
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.diagnostic.Logger;
47 import com.intellij.openapi.editor.Caret;
48 import com.intellij.openapi.editor.Document;
49 import com.intellij.openapi.editor.Editor;
50 import com.intellij.openapi.editor.actionSystem.EditorAction;
51 import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
52 import com.intellij.openapi.editor.actions.SplitLineAction;
53 import com.intellij.openapi.editor.ex.EditorEx;
54 import com.intellij.openapi.fileEditor.FileDocumentManager;
55 import com.intellij.openapi.module.Module;
56 import com.intellij.openapi.progress.ProgressIndicator;
57 import com.intellij.openapi.progress.ProgressManager;
58 import com.intellij.openapi.progress.Task;
59 import com.intellij.openapi.project.DumbAware;
60 import com.intellij.openapi.project.DumbAwareAction;
61 import com.intellij.openapi.project.Project;
62 import com.intellij.openapi.projectRoots.Sdk;
63 import com.intellij.openapi.ui.Messages;
64 import com.intellij.openapi.util.*;
65 import com.intellij.openapi.util.io.StreamUtil;
66 import com.intellij.openapi.util.text.StringUtil;
67 import com.intellij.openapi.vfs.CharsetToolkit;
68 import com.intellij.openapi.vfs.VirtualFile;
69 import com.intellij.psi.PsiFile;
70 import com.intellij.remote.RemoteProcess;
71 import com.intellij.remote.Tunnelable;
72 import com.intellij.testFramework.LightVirtualFile;
73 import com.intellij.ui.JBColor;
74 import com.intellij.ui.SideBorder;
75 import com.intellij.util.ArrayUtil;
76 import com.intellij.util.IJSwingUtilities;
77 import com.intellij.util.PathMappingSettings;
78 import com.intellij.util.TimeoutUtil;
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;
102
103 import javax.swing.*;
104 import java.awt.*;
105 import java.awt.event.InputEvent;
106 import java.awt.event.KeyEvent;
107 import java.io.File;
108 import java.io.IOException;
109 import java.net.ServerSocket;
110 import java.util.*;
111 import java.util.List;
112
113 import static com.intellij.execution.runners.AbstractConsoleRunnerWithHistory.registerActionShortcuts;
114
115 /**
116  * @author oleg
117  */
118 public class PydevConsoleRunnerImpl implements PydevConsoleRunner {
119   public static final String WORKING_DIR_ENV = "WORKING_DIR_AND_PYTHON_PATHS";
120   public static final String CONSOLE_START_COMMAND = "import sys; print('Python %s on %s' % (sys.version, sys.platform))\n" +
121                                                      "sys.path.extend([" + WORKING_DIR_ENV + "])\n";
122   private static final Logger LOG = Logger.getInstance(PydevConsoleRunnerImpl.class.getName());
123   @SuppressWarnings("SpellCheckingInspection")
124   public static final String PYDEV_PYDEVCONSOLE_PY = "pydev/pydevconsole.py";
125   public static final int PORTS_WAITING_TIMEOUT = 20000;
126   private static final String CONSOLE_FEATURE = "python.console";
127   private final Project myProject;
128   private final String myTitle;
129   private final String myWorkingDir;
130   private final Executor myExecutor;
131   private final Runnable myRunRunAction;
132   @NotNull
133   private Sdk mySdk;
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;
145
146
147   private static final long APPROPRIATE_TO_WAIT = 60000;
148
149   private PyRemoteProcessHandlerBase myRemoteProcessHandlerBase;
150
151   private String myConsoleTitle = null;
152   private PythonConsoleView myConsoleView;
153
154   public PydevConsoleRunnerImpl(@NotNull final Project project,
155                                 @NotNull Sdk sdk,
156                                 @NotNull final PyConsoleType consoleType,
157                                 @Nullable final String workingDir,
158                                 Map<String, String> environmentVariables,
159                                 @NotNull PyConsoleOptions.PyConsoleSettings settingsProvider,
160                                 @NotNull Runnable rerunAction, String... statementsToExecute) {
161     myProject = project;
162     mySdk = sdk;
163     myTitle = consoleType.getTitle();
164     myWorkingDir = workingDir;
165     myConsoleType = consoleType;
166     myEnvironmentVariables = environmentVariables;
167     myConsoleSettings = settingsProvider;
168     myStatementsToExecute = statementsToExecute;
169     myRunRunAction = rerunAction;
170     PyConsoleToolWindowExecutor toolWindowExecutor = PyConsoleToolWindowExecutor.findInstance();
171     myExecutor = toolWindowExecutor != null ? toolWindowExecutor : DefaultRunExecutor.getRunExecutorInstance();
172   }
173
174
175   private List<AnAction> fillToolBarActions(final DefaultActionGroup toolbarActions,
176                                             final RunContentDescriptor contentDescriptor) {
177     //toolbarActions.add(backspaceHandlingAction);
178
179     toolbarActions.add(createRerunAction());
180
181     List<AnAction> actions = ContainerUtil.newArrayList();
182
183     //stop
184     actions.add(createStopAction());
185
186     //close
187     actions.add(createCloseAction(contentDescriptor));
188
189     // run action
190     actions.add(
191       new ConsoleExecuteAction(myConsoleView, myConsoleExecuteActionHandler, myConsoleExecuteActionHandler.getEmptyExecuteAction(),
192                                myConsoleExecuteActionHandler));
193
194     // Help
195     actions.add(CommonActionsManager.getInstance().createHelpAction("interactive_console"));
196
197     toolbarActions.addAll(actions);
198
199
200     actions.add(0, createRerunAction());
201
202     actions.add(createInterruptAction());
203     actions.add(createTabCompletionAction());
204
205     actions.add(createSplitLineAction());
206
207     toolbarActions.add(new ShowVarsAction(myConsoleView, myPydevConsoleCommunication));
208     toolbarActions.add(ConsoleHistoryController.getController(myConsoleView).getBrowseHistory());
209
210     toolbarActions.add(new ConnectDebuggerAction());
211
212     toolbarActions.add(new NewConsoleAction());
213
214     return actions;
215   }
216
217   @Override
218   public void runSync() {
219     myPorts = findAvailablePorts(myProject, myConsoleType);
220
221     assert myPorts != null;
222
223     myGeneralCommandLine = createCommandLine(mySdk, myEnvironmentVariables, myWorkingDir, myPorts);
224     myCommandLine = myGeneralCommandLine.getCommandLineString();
225
226     try {
227       initAndRun();
228     }
229     catch (ExecutionException e) {
230       LOG.warn("Error running console", e);
231       ExecutionHelper.showErrors(myProject, Collections.<Exception>singletonList(e), "Python Console", null);
232     }
233
234     ProgressManager.getInstance().run(new Task.Backgroundable(myProject, "Connecting to Console", false) {
235       @Override
236       public void run(@NotNull final ProgressIndicator indicator) {
237         indicator.setText("Connecting to console...");
238         connect(myStatementsToExecute);
239       }
240     });
241   }
242
243
244   @Override
245   public void run() {
246     ApplicationManager.getApplication().invokeAndWait(() -> FileDocumentManager.getInstance().saveAllDocuments());
247
248     myPorts = findAvailablePorts(myProject, myConsoleType);
249
250     assert myPorts != null;
251
252     myGeneralCommandLine = createCommandLine(mySdk, myEnvironmentVariables, myWorkingDir, myPorts);
253     myCommandLine = myGeneralCommandLine.getCommandLineString();
254
255     UIUtil
256       .invokeLaterIfNeeded(() -> ProgressManager.getInstance().run(new Task.Backgroundable(myProject, "Connecting to Console", false) {
257         @Override
258         public void run(@NotNull final ProgressIndicator indicator) {
259           indicator.setText("Connecting to console...");
260           try {
261             initAndRun();
262             connect(myStatementsToExecute);
263           }
264           catch (final Exception e) {
265             LOG.warn("Error running console", e);
266             UIUtil.invokeAndWaitIfNeeded(new Runnable() {
267               @Override
268               public void run() {
269                 showErrorsInConsole(e);
270               }
271             });
272           }
273         }
274       }));
275   }
276
277   private void showErrorsInConsole(Exception e) {
278
279     DefaultActionGroup actionGroup = new DefaultActionGroup(createRerunAction());
280
281     final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN,
282                                                                                         actionGroup, false);
283
284     // Runner creating
285     final JPanel panel = new JPanel(new BorderLayout());
286     panel.add(actionToolbar.getComponent(), BorderLayout.WEST);
287
288     NewErrorTreeViewPanel errorViewPanel = new NewErrorTreeViewPanel(myProject, null, false, false, null);
289
290     String[] messages = StringUtil.isNotEmpty(e.getMessage()) ? StringUtil.splitByLines(e.getMessage()) : ArrayUtil.EMPTY_STRING_ARRAY;
291     if (messages.length == 0) {
292       messages = new String[]{"Unknown error"};
293     }
294
295     errorViewPanel.addMessage(MessageCategory.ERROR, messages, null, -1, -1, null);
296     panel.add(errorViewPanel, BorderLayout.CENTER);
297
298
299     final RunContentDescriptor contentDescriptor =
300       new RunContentDescriptor(null, myProcessHandler, panel, "Error running console");
301
302     actionGroup.add(createCloseAction(contentDescriptor));
303
304     ExecutionManager.getInstance(myProject).getContentManager().showRunContent(myExecutor, contentDescriptor);
305
306   }
307
308   private static int[] findAvailablePorts(Project project, PyConsoleType consoleType) {
309     final int[] ports;
310     try {
311       // File "pydev/console/pydevconsole.py", line 223, in <module>
312       // port, client_port = sys.argv[1:3]
313       ports = NetUtils.findAvailableSocketPorts(2);
314     }
315     catch (IOException e) {
316       ExecutionHelper.showErrors(project, Collections.<Exception>singletonList(e), consoleType.getTitle(), null);
317       return null;
318     }
319     return ports;
320   }
321
322   protected GeneralCommandLine createCommandLine(@NotNull final Sdk sdk,
323                                                  @NotNull final Map<String, String> environmentVariables,
324                                                  String workingDir, int[] ports) {
325     return doCreateConsoleCmdLine(sdk, environmentVariables, workingDir, ports, PythonHelper.CONSOLE);
326   }
327
328   @NotNull
329   protected GeneralCommandLine doCreateConsoleCmdLine(Sdk sdk,
330                                                       Map<String, String> environmentVariables,
331                                                       String workingDir,
332                                                       int[] ports,
333                                                       PythonHelper helper) {
334     GeneralCommandLine cmd =
335       PythonCommandLineState.createPythonCommandLine(myProject, new PythonConsoleRunParams(myConsoleSettings, workingDir, sdk,
336                                                                                            environmentVariables), false,
337                                                      PtyCommandLine.isEnabled() && !SystemInfo.isWindows);
338     cmd.withWorkDirectory(myWorkingDir);
339
340     ParamsGroup group = cmd.getParametersList().getParamsGroup(PythonCommandLineState.GROUP_SCRIPT);
341     helper.addToGroup(group, cmd);
342
343     for (int port : ports) {
344       group.addParameter(String.valueOf(port));
345     }
346
347     return cmd;
348   }
349
350   private PythonConsoleView createConsoleView() {
351     PythonConsoleView consoleView = new PythonConsoleView(myProject, myTitle, mySdk);
352     myPydevConsoleCommunication.setConsoleFile(consoleView.getVirtualFile());
353     consoleView.addMessageFilter(new PythonTracebackFilter(myProject));
354     return consoleView;
355   }
356
357   private Process createProcess() throws ExecutionException {
358     if (PySdkUtil.isRemote(mySdk)) {
359       PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
360       if (manager != null) {
361         UsageTrigger.trigger(CONSOLE_FEATURE + ".remote");
362         return createRemoteConsoleProcess(manager, myGeneralCommandLine.getParametersList().getArray(),
363                                           myGeneralCommandLine.getEnvironment(), myGeneralCommandLine.getWorkDirectory());
364       }
365       throw new PythonRemoteInterpreterManager.PyRemoteInterpreterExecutionException();
366     }
367     else {
368       myCommandLine = myGeneralCommandLine.getCommandLineString();
369       Map<String, String> envs = myGeneralCommandLine.getEnvironment();
370       EncodingEnvironmentUtil.setLocaleEnvironmentIfMac(envs, myGeneralCommandLine.getCharset());
371
372       UsageTrigger.trigger(CONSOLE_FEATURE + ".local");
373       final Process server = myGeneralCommandLine.createProcess();
374
375       try {
376         myPydevConsoleCommunication = new PydevConsoleCommunication(myProject, myPorts[0], server, myPorts[1]);
377       }
378       catch (Exception e) {
379         throw new ExecutionException(e.getMessage());
380       }
381       return server;
382     }
383   }
384
385   private RemoteProcess createRemoteConsoleProcess(PythonRemoteInterpreterManager manager,
386                                                    String[] command,
387                                                    Map<String, String> env,
388                                                    File workDirectory)
389     throws ExecutionException {
390     PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData();
391     assert data != null;
392
393     GeneralCommandLine commandLine = new GeneralCommandLine();
394
395     commandLine.setWorkDirectory(workDirectory);
396
397     commandLine.withParameters(command);
398
399     commandLine.getEnvironment().putAll(env);
400
401     commandLine.getParametersList().set(0, PythonRemoteInterpreterManager.toSystemDependent(new File(data.getHelpersPath(),
402                                                                                                      PYDEV_PYDEVCONSOLE_PY)
403                                                                                               .getPath(),
404                                                                                             PySourcePosition.isWindowsPath(
405                                                                                               data.getInterpreterPath())
406     ));
407     commandLine.getParametersList().set(1, "0");
408     commandLine.getParametersList().set(2, "0");
409
410     try {
411       PyRemotePathMapper pathMapper = PydevConsoleRunner.getPathMapper(myProject, mySdk, myConsoleSettings);
412
413       assert pathMapper != null;
414
415       commandLine.putUserData(PyRemoteProcessStarter.OPEN_FOR_INCOMING_CONNECTION, true);
416
417       myRemoteProcessHandlerBase = PyRemoteProcessStarterManagerUtil
418         .getManager(data).startRemoteProcess(myProject, commandLine, manager, data,
419                                              pathMapper);
420
421       myCommandLine = myRemoteProcessHandlerBase.getCommandLine();
422
423       RemoteProcess remoteProcess = myRemoteProcessHandlerBase.getProcess();
424
425       Couple<Integer> remotePorts = getRemotePortsFromProcess(remoteProcess);
426
427       if (remoteProcess instanceof Tunnelable) {
428         Tunnelable tunnelableProcess = (Tunnelable)remoteProcess;
429         tunnelableProcess.addLocalTunnel(myPorts[0], remotePorts.first);
430         tunnelableProcess.addRemoteTunnel(remotePorts.second, "localhost", myPorts[1]);
431
432         if (LOG.isDebugEnabled()) {
433           LOG.debug(String.format("Using tunneled communication for Python console: port %d (=> %d) on IDE side, " +
434                                   "port %d (=> %d) on pydevconsole.py side", myPorts[1], remotePorts.second, myPorts[0],
435                                   remotePorts.first));
436         }
437
438         myPydevConsoleCommunication = new PydevRemoteConsoleCommunication(myProject, myPorts[0], remoteProcess, myPorts[1]);
439       }
440       else {
441         if (LOG.isDebugEnabled()) {
442           LOG.debug(String.format("Using direct communication for Python console: port %d on IDE side, port %d on pydevconsole.py side",
443                                   remotePorts.second, remotePorts.first));
444         }
445
446         myPydevConsoleCommunication =
447           new PydevRemoteConsoleCommunication(myProject, remotePorts.first, remoteProcess, remotePorts.second);
448       }
449
450       return remoteProcess;
451     }
452     catch (Exception e) {
453       throw new ExecutionException(e.getMessage(), e);
454     }
455   }
456
457   private static Couple<Integer> getRemotePortsFromProcess(RemoteProcess process) throws ExecutionException {
458     Scanner s = new Scanner(process.getInputStream());
459
460     return Couple.of(readInt(s, process), readInt(s, process));
461   }
462
463   private static int readInt(Scanner s, Process process) throws ExecutionException {
464     long started = System.currentTimeMillis();
465
466     StringBuilder sb = new StringBuilder();
467     boolean flag = false;
468
469     while (System.currentTimeMillis() - started < PORTS_WAITING_TIMEOUT) {
470       if (s.hasNextLine()) {
471         String line = s.nextLine();
472         sb.append(line).append("\n");
473         try {
474           int i = Integer.parseInt(line);
475           if (flag) {
476             LOG.warn("Unexpected strings in output:\n" + sb.toString());
477           }
478           return i;
479         }
480         catch (NumberFormatException ignored) {
481           flag = true;
482           continue;
483         }
484       }
485
486       TimeoutUtil.sleep(200);
487
488       if (process.exitValue() != 0) {
489         String error;
490         try {
491           error = "Console process terminated with error:\n" + StreamUtil.readText(process.getErrorStream()) + sb.toString();
492         }
493         catch (Exception ignored) {
494           error = "Console process terminated with exit code " + process.exitValue() + ", output:" + sb.toString();
495         }
496         throw new ExecutionException(error);
497       }
498       else {
499         break;
500       }
501     }
502
503     throw new ExecutionException("Couldn't read integer value from stream");
504   }
505
506   private PyConsoleProcessHandler createProcessHandler(final Process process) {
507     if (PySdkUtil.isRemote(mySdk)) {
508       PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
509       if (manager != null) {
510         PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData();
511         assert data != null;
512         myProcessHandler =
513           manager.createConsoleProcessHandler((RemoteProcess)process, myConsoleView, myPydevConsoleCommunication,
514                                               myCommandLine, CharsetToolkit.UTF8_CHARSET,
515                                               manager.setupMappings(myProject, data, null),
516                                               myRemoteProcessHandlerBase.getRemoteSocketToLocalHostProvider());
517       }
518       else {
519         LOG.error("Can't create remote console process handler");
520       }
521     }
522     else {
523       myProcessHandler = new PyConsoleProcessHandler(process, myConsoleView, myPydevConsoleCommunication, myCommandLine,
524                                                      CharsetToolkit.UTF8_CHARSET);
525     }
526     return myProcessHandler;
527   }
528
529
530   private void initAndRun() throws ExecutionException {
531     // Create Server process
532     final Process process = createProcess();
533     UIUtil.invokeLaterIfNeeded(() -> {
534       // Init console view
535       myConsoleView = createConsoleView();
536       if (myConsoleView != null) {
537         ((JComponent)myConsoleView).setBorder(new SideBorder(JBColor.border(), SideBorder.LEFT));
538       }
539       myProcessHandler = createProcessHandler(process);
540
541       myConsoleExecuteActionHandler = createExecuteActionHandler();
542
543       ProcessTerminatedListener.attach(myProcessHandler);
544
545       PythonConsoleView consoleView = myConsoleView;
546       myProcessHandler.addProcessListener(new ProcessAdapter() {
547         @Override
548         public void processTerminated(ProcessEvent event) {
549           consoleView.setEditable(false);
550         }
551       });
552
553       // Attach to process
554       myConsoleView.attachToProcess(myProcessHandler);
555       createContentDescriptorAndActions();
556
557
558       // Run
559       myProcessHandler.startNotify();
560     });
561   }
562
563   protected void createContentDescriptorAndActions() {
564     // Runner creating
565     final DefaultActionGroup toolbarActions = new DefaultActionGroup();
566     final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarActions, false);
567
568     // Runner creating
569     final JPanel panel = new JPanel(new BorderLayout());
570     panel.add(actionToolbar.getComponent(), BorderLayout.WEST);
571     panel.add(myConsoleView.getComponent(), BorderLayout.CENTER);
572
573     actionToolbar.setTargetComponent(panel);
574
575     final RunContentDescriptor contentDescriptor =
576       new RunContentDescriptor(myConsoleView, myProcessHandler, panel, new ConsoleTitleGen(myProject, myTitle).makeTitle(), null);
577
578     contentDescriptor.setFocusComputable(() -> myConsoleView.getConsoleEditor().getContentComponent());
579     contentDescriptor.setAutoFocusContent(true);
580
581
582     // tool bar actions
583     final List<AnAction> actions = fillToolBarActions(toolbarActions, contentDescriptor);
584     registerActionShortcuts(actions, myConsoleView.getConsoleEditor().getComponent());
585     registerActionShortcuts(actions, panel);
586
587     ExecutionManager.getInstance(myProject).getContentManager().showRunContent(myExecutor, contentDescriptor);
588   }
589
590   private void connect(final String[] statements2execute) {
591     if (handshake()) {
592       ApplicationManager.getApplication().invokeLater(() -> {
593         // Propagate console communication to language console
594         final PythonConsoleView consoleView = myConsoleView;
595
596         consoleView.setConsoleCommunication(myPydevConsoleCommunication);
597         consoleView.setSdk(mySdk);
598         consoleView.setExecutionHandler(myConsoleExecuteActionHandler);
599         myProcessHandler.addProcessListener(new ProcessAdapter() {
600           @Override
601           public void onTextAvailable(ProcessEvent event, Key outputType) {
602             consoleView.print(event.getText(), outputType);
603           }
604         });
605
606         enableConsoleExecuteAction();
607
608         for (String statement : statements2execute) {
609           consoleView.executeStatement(statement + "\n", ProcessOutputTypes.SYSTEM);
610         }
611
612         fireConsoleInitializedEvent(consoleView);
613       });
614     }
615     else {
616       myConsoleView.print("Couldn't connect to console process.", ProcessOutputTypes.STDERR);
617       myProcessHandler.destroyProcess();
618       myConsoleView.setEditable(false);
619     }
620   }
621
622
623   protected AnAction createRerunAction() {
624     return new RestartAction(this);
625   }
626
627   private AnAction createInterruptAction() {
628     AnAction anAction = new AnAction() {
629       @Override
630       public void actionPerformed(final AnActionEvent e) {
631         if (myPydevConsoleCommunication.isExecuting()) {
632           myConsoleView.print("^C", ProcessOutputTypes.SYSTEM);
633         }
634         myPydevConsoleCommunication.interrupt();
635       }
636
637       @Override
638       public void update(final AnActionEvent e) {
639         EditorEx consoleEditor = myConsoleView.getConsoleEditor();
640         boolean enabled = IJSwingUtilities.hasFocus(consoleEditor.getComponent()) && !consoleEditor.getSelectionModel().hasSelection();
641         e.getPresentation().setEnabled(enabled);
642       }
643     };
644     anAction
645       .registerCustomShortcutSet(KeyEvent.VK_C, InputEvent.CTRL_MASK, myConsoleView.getConsoleEditor().getComponent());
646     anAction.getTemplatePresentation().setVisible(false);
647     return anAction;
648   }
649
650   private AnAction createTabCompletionAction() {
651     final AnAction runCompletions = new AnAction() {
652       @Override
653       public void actionPerformed(AnActionEvent e) {
654
655         Editor editor = myConsoleView.getConsoleEditor();
656         if (LookupManager.getActiveLookup(editor) != null) {
657           AnAction replace = ActionManager.getInstance().getAction("EditorChooseLookupItemReplace");
658           ActionUtil.performActionDumbAware(replace, e);
659           return;
660         }
661         AnAction completionAction = ActionManager.getInstance().getAction("CodeCompletion");
662         if (completionAction == null) {
663           return;
664         }
665         ActionUtil.performActionDumbAware(completionAction, e);
666       }
667
668       @Override
669       public void update(AnActionEvent e) {
670         Editor editor = myConsoleView.getConsoleEditor();
671         if (LookupManager.getActiveLookup(editor) != null) {
672           e.getPresentation().setEnabled(false);
673         }
674         int offset = editor.getCaretModel().getOffset();
675         Document document = editor.getDocument();
676         int lineStart = document.getLineStartOffset(document.getLineNumber(offset));
677         String textToCursor = document.getText(new TextRange(lineStart, offset));
678         e.getPresentation().setEnabled(!CharMatcher.WHITESPACE.matchesAllOf(textToCursor));
679       }
680     };
681
682     runCompletions
683       .registerCustomShortcutSet(KeyEvent.VK_TAB, 0, myConsoleView.getConsoleEditor().getComponent());
684     runCompletions.getTemplatePresentation().setVisible(false);
685     return runCompletions;
686   }
687
688
689
690   private boolean isIndentSubstring(String text) {
691     int indentSize = myConsoleExecuteActionHandler.getPythonIndent();
692     return text.length() >= indentSize && CharMatcher.WHITESPACE.matchesAllOf(text.substring(text.length() - indentSize));
693   }
694
695   private void enableConsoleExecuteAction() {
696     myConsoleExecuteActionHandler.setEnabled(true);
697   }
698
699   private boolean handshake() {
700     boolean res;
701     long started = System.currentTimeMillis();
702     do {
703       try {
704         res = myPydevConsoleCommunication.handshake();
705       }
706       catch (XmlRpcException ignored) {
707         res = false;
708       }
709       if (res) {
710         break;
711       }
712       else {
713         long now = System.currentTimeMillis();
714         if (now - started > APPROPRIATE_TO_WAIT) {
715           break;
716         }
717         else {
718           TimeoutUtil.sleep(100);
719         }
720       }
721     }
722     while (true);
723     return res;
724   }
725
726
727   private AnAction createStopAction() {
728     AnAction generalStopAction = ActionManager.getInstance().getAction(IdeActions.ACTION_STOP_PROGRAM);
729     final AnAction stopAction = new DumbAwareAction() {
730       @Override
731       public void update(AnActionEvent e) {
732         generalStopAction.update(e);
733       }
734
735       @Override
736       public void actionPerformed(AnActionEvent e) {
737         e = stopConsole(e);
738
739         generalStopAction.actionPerformed(e);
740       }
741     };
742     stopAction.copyFrom(generalStopAction);
743     return stopAction;
744   }
745
746   private AnAction createCloseAction(final RunContentDescriptor descriptor) {
747     final AnAction generalCloseAction = new CloseAction(myExecutor, descriptor, myProject);
748
749     final AnAction stopAction = new DumbAwareAction() {
750       @Override
751       public void update(AnActionEvent e) {
752         generalCloseAction.update(e);
753       }
754
755       @Override
756       public void actionPerformed(AnActionEvent e) {
757         e = stopConsole(e);
758
759         clearContent(descriptor);
760
761         generalCloseAction.actionPerformed(e);
762       }
763     };
764     stopAction.copyFrom(generalCloseAction);
765     return stopAction;
766   }
767
768   protected void clearContent(RunContentDescriptor descriptor) {
769   }
770
771   private AnActionEvent stopConsole(AnActionEvent e) {
772     if (myPydevConsoleCommunication != null) {
773       e = new AnActionEvent(e.getInputEvent(), e.getDataContext(), e.getPlace(),
774                             e.getPresentation(), e.getActionManager(), e.getModifiers());
775       try {
776         closeCommunication();
777         // waiting for REPL communication before destroying process handler
778         Thread.sleep(300);
779       }
780       catch (Exception ignored) {
781         // Ignore
782       }
783     }
784     return e;
785   }
786
787   protected AnAction createSplitLineAction() {
788
789     class ConsoleSplitLineAction extends EditorAction {
790
791       private static final String CONSOLE_SPLIT_LINE_ACTION_ID = "Console.SplitLine";
792
793       public ConsoleSplitLineAction() {
794         super(new EditorWriteActionHandler() {
795
796           private final SplitLineAction mySplitLineAction = new SplitLineAction();
797
798           @Override
799           public boolean isEnabled(Editor editor, DataContext dataContext) {
800             return mySplitLineAction.getHandler().isEnabled(editor, dataContext);
801           }
802
803           @Override
804           public void executeWriteAction(Editor editor, @Nullable Caret caret, DataContext dataContext) {
805             ((EditorWriteActionHandler)mySplitLineAction.getHandler()).executeWriteAction(editor, caret, dataContext);
806             editor.getCaretModel().getCurrentCaret().moveCaretRelatively(0, 1, false, true);
807           }
808         });
809       }
810
811       public void setup() {
812         EmptyAction.setupAction(this, CONSOLE_SPLIT_LINE_ACTION_ID, null);
813       }
814     }
815
816     ConsoleSplitLineAction action = new ConsoleSplitLineAction();
817     action.setup();
818     return action;
819   }
820
821   private void closeCommunication() {
822     if (!myProcessHandler.isProcessTerminated()) {
823       myPydevConsoleCommunication.close();
824     }
825   }
826
827   @NotNull
828   protected PydevConsoleExecuteActionHandler createExecuteActionHandler() {
829     myConsoleExecuteActionHandler =
830       new PydevConsoleExecuteActionHandler(myConsoleView, myProcessHandler, myPydevConsoleCommunication);
831     myConsoleExecuteActionHandler.setEnabled(false);
832     new ConsoleHistoryController(myConsoleType.getTypeId(), "", myConsoleView).install();
833     return myConsoleExecuteActionHandler;
834   }
835
836   @Override
837   public PydevConsoleCommunication getPydevConsoleCommunication() {
838     return myPydevConsoleCommunication;
839   }
840
841   static VirtualFile getConsoleFile(PsiFile psiFile) {
842     VirtualFile file = psiFile.getViewProvider().getVirtualFile();
843     if (file instanceof LightVirtualFile) {
844       file = ((LightVirtualFile)file).getOriginalFile();
845     }
846     return file;
847   }
848
849   @Override
850   public void addConsoleListener(ConsoleListener consoleListener) {
851     myConsoleListeners.add(consoleListener);
852   }
853
854   private void fireConsoleInitializedEvent(LanguageConsoleView consoleView) {
855     for (ConsoleListener listener : myConsoleListeners) {
856       listener.handleConsoleInitialized(consoleView);
857     }
858     myConsoleListeners.clear();
859   }
860
861   @Override
862   public PydevConsoleExecuteActionHandler getConsoleExecuteActionHandler() {
863     return myConsoleExecuteActionHandler;
864   }
865
866
867   private static class RestartAction extends AnAction {
868     private PydevConsoleRunnerImpl myConsoleRunner;
869
870
871     private RestartAction(PydevConsoleRunnerImpl runner) {
872       copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_RERUN));
873       getTemplatePresentation().setIcon(AllIcons.Actions.Restart);
874       myConsoleRunner = runner;
875     }
876
877     @Override
878     public void actionPerformed(AnActionEvent e) {
879       myConsoleRunner.rerun();
880     }
881   }
882
883   private void rerun() {
884     new Task.Backgroundable(myProject, "Restarting Console", true) {
885       @Override
886       public void run(@NotNull ProgressIndicator indicator) {
887         if (myProcessHandler != null) {
888           UIUtil.invokeAndWaitIfNeeded((Runnable)() -> closeCommunication());
889
890           boolean processStopped = myProcessHandler.waitFor(5000L);
891           if (!processStopped && myProcessHandler.canKillProcess()) {
892             myProcessHandler.killProcess();
893           }
894           myProcessHandler.waitFor();
895         }
896
897         UIUtil.invokeLaterIfNeeded(() -> {
898
899           myRunRunAction.run();
900         });
901       }
902     }.queue();
903   }
904
905
906   private class ConnectDebuggerAction extends ToggleAction implements DumbAware {
907     private boolean mySelected = false;
908     private XDebugSession mySession = null;
909
910     public ConnectDebuggerAction() {
911       super("Attach Debugger", "Enables tracing of code executed in console", AllIcons.Actions.StartDebugger);
912     }
913
914     @Override
915     public boolean isSelected(AnActionEvent e) {
916       return mySelected;
917     }
918
919     @Override
920     public void update(AnActionEvent e) {
921       if (mySession != null) {
922         e.getPresentation().setEnabled(false);
923       }
924       else {
925         e.getPresentation().setEnabled(true);
926       }
927     }
928
929     @Override
930     public void setSelected(AnActionEvent e, boolean state) {
931       mySelected = state;
932
933       if (mySelected) {
934         try {
935           mySession = connectToDebugger();
936         }
937         catch (Exception e1) {
938           LOG.error(e1);
939           Messages.showErrorDialog("Can't connect to debugger", "Error Connecting Debugger");
940         }
941       }
942       else {
943         //TODO: disable debugging
944       }
945     }
946   }
947
948
949   private static class NewConsoleAction extends AnAction implements DumbAware {
950     public NewConsoleAction() {
951       super("New Console", "Creates new python console", AllIcons.General.Add);
952     }
953
954     @Override
955     public void update(AnActionEvent e) {
956       e.getPresentation().setEnabled(true);
957     }
958
959     @Override
960     public void actionPerformed(AnActionEvent e) {
961       PydevConsoleRunner runner =
962         PythonConsoleRunnerFactory.getInstance().createConsoleRunner(e.getData(CommonDataKeys.PROJECT), e.getData(LangDataKeys.MODULE));
963       runner.run();
964     }
965   }
966
967   private XDebugSession connectToDebugger() throws ExecutionException {
968     final ServerSocket serverSocket = PythonCommandLineState.createServerSocket();
969
970     final XDebugSession session = XDebuggerManager.getInstance(myProject).
971       startSessionAndShowTab("Python Console Debugger", PythonIcons.Python.Python, null, true, new XDebugProcessStarter() {
972         @NotNull
973         public XDebugProcess start(@NotNull final XDebugSession session) {
974           PythonDebugLanguageConsoleView debugConsoleView = new PythonDebugLanguageConsoleView(myProject, mySdk);
975
976           PyConsoleDebugProcessHandler consoleDebugProcessHandler =
977             new PyConsoleDebugProcessHandler(myProcessHandler);
978
979           PyConsoleDebugProcess consoleDebugProcess =
980             new PyConsoleDebugProcess(session, serverSocket, debugConsoleView,
981                                       consoleDebugProcessHandler);
982
983           PythonDebugConsoleCommunication communication =
984             PyDebugRunner.initDebugConsoleView(myProject, consoleDebugProcess, debugConsoleView, consoleDebugProcessHandler, session);
985
986           communication.addCommunicationListener(new ConsoleCommunicationListener() {
987             @Override
988             public void commandExecuted(boolean more) {
989               session.rebuildViews();
990             }
991
992             @Override
993             public void inputRequested() {
994             }
995           });
996
997           myPydevConsoleCommunication.setDebugCommunication(communication);
998           debugConsoleView.attachToProcess(consoleDebugProcessHandler);
999
1000           consoleDebugProcess.waitForNextConnection();
1001
1002           try {
1003             consoleDebugProcess.connect(myPydevConsoleCommunication);
1004           }
1005           catch (Exception e) {
1006             LOG.error(e); //TODO
1007           }
1008
1009           myProcessHandler.notifyTextAvailable("\nDebugger connected.\n", ProcessOutputTypes.STDERR);
1010
1011           return consoleDebugProcess;
1012         }
1013       });
1014
1015     return session;
1016   }
1017
1018   @Override
1019   public PyConsoleProcessHandler getProcessHandler() {
1020     return myProcessHandler;
1021   }
1022
1023   @Override
1024   public PythonConsoleView getConsoleView() {
1025     return myConsoleView;
1026   }
1027
1028   public static PythonConsoleRunnerFactory factory() {
1029     return new PydevConsoleRunnerFactory();
1030   }
1031
1032   private static class PythonConsoleRunParams implements PythonRunParams {
1033     private final PyConsoleOptions.PyConsoleSettings myConsoleSettings;
1034     private String myWorkingDir;
1035     private Sdk mySdk;
1036     private Map<String, String> myEnvironmentVariables;
1037
1038     public PythonConsoleRunParams(@NotNull PyConsoleOptions.PyConsoleSettings consoleSettings,
1039                                   @NotNull String workingDir,
1040                                   @NotNull Sdk sdk,
1041                                   @NotNull Map<String, String> envs) {
1042       myConsoleSettings = consoleSettings;
1043       myWorkingDir = workingDir;
1044       mySdk = sdk;
1045       myEnvironmentVariables = envs;
1046       myEnvironmentVariables.putAll(consoleSettings.getEnvs());
1047     }
1048
1049     @Override
1050     public String getInterpreterOptions() {
1051       return myConsoleSettings.getInterpreterOptions();
1052     }
1053
1054     @Override
1055     public void setInterpreterOptions(String interpreterOptions) {
1056       throw new UnsupportedOperationException();
1057     }
1058
1059     @Override
1060     public String getWorkingDirectory() {
1061       return myWorkingDir;
1062     }
1063
1064     @Override
1065     public void setWorkingDirectory(String workingDirectory) {
1066       throw new UnsupportedOperationException();
1067     }
1068
1069     @Nullable
1070     @Override
1071     public String getSdkHome() {
1072       return mySdk.getHomePath();
1073     }
1074
1075     @Override
1076     public void setSdkHome(String sdkHome) {
1077       throw new UnsupportedOperationException();
1078     }
1079
1080     @Override
1081     public void setModule(Module module) {
1082       throw new UnsupportedOperationException();
1083     }
1084
1085     @Override
1086     public String getModuleName() {
1087       return myConsoleSettings.getModuleName();
1088     }
1089
1090     @Override
1091     public boolean isUseModuleSdk() {
1092       return myConsoleSettings.isUseModuleSdk();
1093     }
1094
1095     @Override
1096     public void setUseModuleSdk(boolean useModuleSdk) {
1097       throw new UnsupportedOperationException();
1098     }
1099
1100     @Override
1101     public boolean isPassParentEnvs() {
1102       return myConsoleSettings.isPassParentEnvs();
1103     }
1104
1105     @Override
1106     public void setPassParentEnvs(boolean passParentEnvs) {
1107       throw new UnsupportedOperationException();
1108     }
1109
1110     @Override
1111     public Map<String, String> getEnvs() {
1112       return myEnvironmentVariables;
1113     }
1114
1115     @Override
1116     public void setEnvs(Map<String, String> envs) {
1117       throw new UnsupportedOperationException();
1118     }
1119
1120     @Nullable
1121     @Override
1122     public PathMappingSettings getMappingSettings() {
1123       throw new UnsupportedOperationException();
1124     }
1125
1126     @Override
1127     public void setMappingSettings(@Nullable PathMappingSettings mappingSettings) {
1128       throw new UnsupportedOperationException();
1129     }
1130
1131     @Override
1132     public boolean shouldAddContentRoots() {
1133       return myConsoleSettings.shouldAddContentRoots();
1134     }
1135
1136     @Override
1137     public boolean shouldAddSourceRoots() {
1138       return myConsoleSettings.shouldAddSourceRoots();
1139     }
1140
1141     @Override
1142     public void setAddContentRoots(boolean flag) {
1143       throw new UnsupportedOperationException();
1144     }
1145
1146     @Override
1147     public void setAddSourceRoots(boolean flag) {
1148       throw new UnsupportedOperationException();
1149     }
1150   }
1151 }