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