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