Merge branch 'akoshevoy/path_mappers'
[idea/community.git] / python / src / com / jetbrains / python / console / PydevConsoleRunner.java
1 /*
2  * Copyright 2000-2014 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.base.Function;
20 import com.google.common.base.Joiner;
21 import com.google.common.collect.Collections2;
22 import com.intellij.execution.ExecutionException;
23 import com.intellij.execution.ExecutionHelper;
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.console.ConsoleHistoryController;
28 import com.intellij.execution.console.LanguageConsoleView;
29 import com.intellij.execution.console.ProcessBackedConsoleExecuteActionHandler;
30 import com.intellij.execution.process.CommandLineArgumentsProvider;
31 import com.intellij.execution.process.ProcessAdapter;
32 import com.intellij.execution.process.ProcessEvent;
33 import com.intellij.execution.process.ProcessOutputTypes;
34 import com.intellij.execution.runners.AbstractConsoleRunnerWithHistory;
35 import com.intellij.execution.ui.RunContentDescriptor;
36 import com.intellij.icons.AllIcons;
37 import com.intellij.openapi.actionSystem.*;
38 import com.intellij.openapi.application.ApplicationManager;
39 import com.intellij.openapi.application.Result;
40 import com.intellij.openapi.command.WriteCommandAction;
41 import com.intellij.openapi.diagnostic.Logger;
42 import com.intellij.openapi.editor.Caret;
43 import com.intellij.openapi.editor.Editor;
44 import com.intellij.openapi.editor.actionSystem.EditorAction;
45 import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
46 import com.intellij.openapi.editor.actions.SplitLineAction;
47 import com.intellij.openapi.editor.ex.EditorEx;
48 import com.intellij.openapi.fileEditor.FileDocumentManager;
49 import com.intellij.openapi.module.Module;
50 import com.intellij.openapi.module.ModuleManager;
51 import com.intellij.openapi.progress.ProgressIndicator;
52 import com.intellij.openapi.progress.ProgressManager;
53 import com.intellij.openapi.progress.Task;
54 import com.intellij.openapi.project.DumbAware;
55 import com.intellij.openapi.project.DumbAwareAction;
56 import com.intellij.openapi.project.Project;
57 import com.intellij.openapi.projectRoots.Sdk;
58 import com.intellij.openapi.ui.Messages;
59 import com.intellij.openapi.util.Couple;
60 import com.intellij.openapi.util.Key;
61 import com.intellij.openapi.util.Pair;
62 import com.intellij.openapi.util.io.FileUtil;
63 import com.intellij.openapi.util.io.StreamUtil;
64 import com.intellij.openapi.vfs.CharsetToolkit;
65 import com.intellij.openapi.vfs.VirtualFile;
66 import com.intellij.openapi.vfs.encoding.EncodingProjectManager;
67 import com.intellij.psi.PsiElement;
68 import com.intellij.psi.PsiFile;
69 import com.intellij.psi.impl.source.tree.FileElement;
70 import com.intellij.remote.RemoteSshProcess;
71 import com.intellij.testFramework.LightVirtualFile;
72 import com.intellij.util.ArrayUtil;
73 import com.intellij.util.IJSwingUtilities;
74 import com.intellij.util.PathMapper;
75 import com.intellij.util.TimeoutUtil;
76 import com.intellij.util.containers.ContainerUtil;
77 import com.intellij.util.net.NetUtils;
78 import com.intellij.util.ui.UIUtil;
79 import com.intellij.xdebugger.XDebugProcess;
80 import com.intellij.xdebugger.XDebugProcessStarter;
81 import com.intellij.xdebugger.XDebugSession;
82 import com.intellij.xdebugger.XDebuggerManager;
83 import com.jetbrains.python.PythonHelpersLocator;
84 import com.jetbrains.python.console.completion.PydevConsoleElement;
85 import com.jetbrains.python.console.parsing.PythonConsoleData;
86 import com.jetbrains.python.console.pydev.ConsoleCommunication;
87 import com.jetbrains.python.console.pydev.ConsoleCommunicationListener;
88 import com.jetbrains.python.debugger.PyDebugRunner;
89 import com.jetbrains.python.debugger.PySourcePosition;
90 import com.jetbrains.python.remote.PyRemotePathMapper;
91 import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase;
92 import com.jetbrains.python.remote.PyRemoteSdkCredentials;
93 import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
94 import com.jetbrains.python.run.ProcessRunner;
95 import com.jetbrains.python.run.PythonCommandLineState;
96 import com.jetbrains.python.run.PythonTracebackFilter;
97 import com.jetbrains.python.sdk.PySdkUtil;
98 import com.jetbrains.python.sdk.PythonSdkType;
99 import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
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 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.nio.charset.Charset;
111 import java.util.*;
112
113 import static com.jetbrains.python.sdk.PythonEnvUtil.setPythonIOEncoding;
114 import static com.jetbrains.python.sdk.PythonEnvUtil.setPythonUnbuffered;
115
116 /**
117  * @author oleg
118  */
119 public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonConsoleView> {
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(PydevConsoleRunner.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
128   private Sdk mySdk;
129   private CommandLineArgumentsProvider myCommandLineArgumentsProvider;
130   protected int[] myPorts;
131   private PydevConsoleCommunication myPydevConsoleCommunication;
132   private PyConsoleProcessHandler myProcessHandler;
133   protected PydevConsoleExecuteActionHandler myConsoleExecuteActionHandler;
134   private List<ConsoleListener> myConsoleListeners = ContainerUtil.createLockFreeCopyOnWriteList();
135   private final PyConsoleType myConsoleType;
136   private Map<String, String> myEnvironmentVariables;
137   private String myCommandLine;
138   private String[] myStatementsToExecute = ArrayUtil.EMPTY_STRING_ARRAY;
139
140   public static Key<ConsoleCommunication> CONSOLE_KEY = new Key<ConsoleCommunication>("PYDEV_CONSOLE_KEY");
141
142   public static Key<Sdk> CONSOLE_SDK = new Key<Sdk>("PYDEV_CONSOLE_SDK_KEY");
143
144   private static final long APPROPRIATE_TO_WAIT = 60000;
145   private PyRemoteSdkCredentials myRemoteCredentials;
146
147   private String myConsoleTitle = null;
148
149   public PydevConsoleRunner(@NotNull final Project project,
150                             @NotNull Sdk sdk, @NotNull final PyConsoleType consoleType,
151                             @Nullable final String workingDir,
152                             Map<String, String> environmentVariables, String... statementsToExecute) {
153     super(project, consoleType.getTitle(), workingDir);
154     mySdk = sdk;
155     myConsoleType = consoleType;
156     myEnvironmentVariables = environmentVariables;
157     myStatementsToExecute = statementsToExecute;
158   }
159
160   @Nullable
161   public static PathMapper getPathMapper(Project project, Sdk sdk) {
162     if (PySdkUtil.isRemote(sdk)) {
163       PythonRemoteInterpreterManager instance = PythonRemoteInterpreterManager.getInstance();
164       if (instance != null) {
165         //noinspection ConstantConditions
166         return instance.setupMappings(project, (PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData(), null);
167       }
168     }
169     return null;
170   }
171
172   @NotNull
173   public static Pair<Sdk, Module> findPythonSdkAndModule(@NotNull Project project, @Nullable Module contextModule) {
174     Sdk sdk = null;
175     Module module = null;
176     PyConsoleOptions.PyConsoleSettings settings = PyConsoleOptions.getInstance(project).getPythonConsoleSettings();
177     String sdkHome = settings.getSdkHome();
178     if (sdkHome != null) {
179       sdk = PythonSdkType.findSdkByPath(sdkHome);
180       if (settings.getModuleName() != null) {
181         module = ModuleManager.getInstance(project).findModuleByName(settings.getModuleName());
182       }
183       else {
184         module = contextModule;
185         if (module == null && ModuleManager.getInstance(project).getModules().length > 0) {
186           module = ModuleManager.getInstance(project).getModules()[0];
187         }
188       }
189     }
190     if (sdk == null && settings.isUseModuleSdk()) {
191       if (contextModule != null) {
192         module = contextModule;
193       }
194       else if (settings.getModuleName() != null) {
195         module = ModuleManager.getInstance(project).findModuleByName(settings.getModuleName());
196       }
197       if (module != null) {
198         if (PythonSdkType.findPythonSdk(module) != null) {
199           sdk = PythonSdkType.findPythonSdk(module);
200         }
201       }
202     }
203     else if (contextModule != null) {
204       if (module == null) {
205         module = contextModule;
206       }
207       if (sdk == null) {
208         sdk = PythonSdkType.findPythonSdk(module);
209       }
210     }
211
212     if (sdk == null) {
213       for (Module m : ModuleManager.getInstance(project).getModules()) {
214         if (PythonSdkType.findPythonSdk(m) != null) {
215           sdk = PythonSdkType.findPythonSdk(m);
216           module = m;
217           break;
218         }
219       }
220     }
221     if (sdk == null) {
222       if (PythonSdkType.getAllSdks().size() > 0) {
223         //noinspection UnusedAssignment
224         sdk = PythonSdkType.getAllSdks().get(0); //take any python sdk
225       }
226     }
227     return Pair.create(sdk, module);
228   }
229
230   public static String constructPythonPathCommand(Collection<String> pythonPath, String command) {
231     final String path = Joiner.on(", ").join(Collections2.transform(pythonPath, new Function<String, String>() {
232       @Override
233       public String apply(String input) {
234         return "'" + input.replace("\\", "\\\\").replace("'", "\\'") + "'";
235       }
236     }));
237
238     return command.replace(WORKING_DIR_ENV, path);
239   }
240
241   public static Map<String, String> addDefaultEnvironments(Sdk sdk, Map<String, String> envs, @NotNull Project project) {
242     setCorrectStdOutEncoding(envs, project);
243
244     PythonSdkFlavor.initPythonPath(envs, true, PythonCommandLineState.getAddedPaths(sdk));
245     return envs;
246   }
247
248   /**
249    * Add requered ENV var to Python task to set its stdout charset to current project charset to allow it print correctly.
250    * @param envs map of envs to add variable
251    * @param project current project
252    */
253   public static void setCorrectStdOutEncoding(@NotNull final Map<String, String> envs, @NotNull final Project project) {
254     final Charset defaultCharset = getProjectDefaultCharset(project);
255     final String encoding = defaultCharset.name();
256     setPythonIOEncoding(setPythonUnbuffered(envs), encoding);
257   }
258
259   /**
260    * Set command line charset as current project charset.
261    * Add required ENV var to Python task to set its stdout charset to current project charset to allow it print correctly.
262    *
263    * @param commandLine command line
264    * @param project     current project
265    */
266   public static void setCorrectStdOutEncoding(@NotNull GeneralCommandLine commandLine, @NotNull final Project project) {
267     final Charset defaultCharset = getProjectDefaultCharset(project);
268     commandLine.setCharset(defaultCharset);
269     setPythonIOEncoding(commandLine.getEnvironment(), defaultCharset.name());
270   }
271
272   @NotNull
273   private static Charset getProjectDefaultCharset(@NotNull Project project) {
274     return EncodingProjectManager.getInstance(project).getDefaultCharset();
275   }
276
277   @Override
278   protected List<AnAction> fillToolBarActions(final DefaultActionGroup toolbarActions,
279                                               final Executor defaultExecutor,
280                                               final RunContentDescriptor contentDescriptor) {
281     AnAction backspaceHandlingAction = createBackspaceHandlingAction();
282     //toolbarActions.add(backspaceHandlingAction);
283     AnAction interruptAction = createInterruptAction();
284
285     AnAction rerunAction = createRerunAction();
286     toolbarActions.add(rerunAction);
287
288     List<AnAction> actions = super.fillToolBarActions(toolbarActions, defaultExecutor, contentDescriptor);
289
290     actions.add(0, rerunAction);
291
292     actions.add(backspaceHandlingAction);
293     actions.add(interruptAction);
294
295     actions.add(createSplitLineAction());
296
297     AnAction showVarsAction = new ShowVarsAction();
298     toolbarActions.add(showVarsAction);
299     toolbarActions.add(ConsoleHistoryController.getController(getConsoleView()).getBrowseHistory());
300
301     toolbarActions.add(new ConnectDebuggerAction());
302
303     toolbarActions.add(new NewConsoleAction());
304
305     return actions;
306   }
307
308   public void runSync() {
309     myPorts = findAvailablePorts(getProject(), myConsoleType);
310
311     assert myPorts != null;
312
313     myCommandLineArgumentsProvider = createCommandLineArgumentsProvider(mySdk, myEnvironmentVariables, myPorts);
314
315     try {
316       super.initAndRun();
317     }
318     catch (ExecutionException e) {
319       LOG.warn("Error running console", e);
320       ExecutionHelper.showErrors(getProject(), Arrays.<Exception>asList(e), "Python Console", null);
321     }
322
323     ProgressManager.getInstance().run(new Task.Backgroundable(getProject(), "Connecting to console", false) {
324       @Override
325       public void run(@NotNull final ProgressIndicator indicator) {
326         indicator.setText("Connecting to console...");
327         connect(myStatementsToExecute);
328       }
329     });
330   }
331
332   /**
333    * Opens console
334    */
335   public void open() {
336     run();
337   }
338
339
340   /**
341    * Creates new console tab
342    */
343   public void createNewConsole() {
344     run();
345   }
346
347   public void run() {
348     UIUtil.invokeAndWaitIfNeeded(new Runnable() {
349       @Override
350       public void run() {
351         FileDocumentManager.getInstance().saveAllDocuments();
352       }
353     });
354
355     myPorts = findAvailablePorts(getProject(), myConsoleType);
356
357     assert myPorts != null;
358
359     myCommandLineArgumentsProvider = createCommandLineArgumentsProvider(mySdk, myEnvironmentVariables, myPorts);
360
361     UIUtil.invokeLaterIfNeeded(new Runnable() {
362       @Override
363       public void run() {
364         ProgressManager.getInstance().run(new Task.Backgroundable(getProject(), "Connecting to console", false) {
365           @Override
366           public void run(@NotNull final ProgressIndicator indicator) {
367             indicator.setText("Connecting to console...");
368             try {
369               initAndRun(myStatementsToExecute);
370             }
371             catch (ExecutionException e) {
372               LOG.warn("Error running console", e);
373               assert myProject != null;
374               ExecutionHelper.showErrors(myProject, Arrays.<Exception>asList(e), getTitle(), null);
375             }
376           }
377         });
378       }
379     });
380   }
381
382   private static int[] findAvailablePorts(Project project, PyConsoleType consoleType) {
383     final int[] ports;
384     try {
385       // File "pydev/console/pydevconsole.py", line 223, in <module>
386       // port, client_port = sys.argv[1:3]
387       ports = NetUtils.findAvailableSocketPorts(2);
388     }
389     catch (IOException e) {
390       ExecutionHelper.showErrors(project, Arrays.<Exception>asList(e), consoleType.getTitle(), null);
391       return null;
392     }
393     return ports;
394   }
395
396   protected CommandLineArgumentsProvider createCommandLineArgumentsProvider(final Sdk sdk,
397                                                                             final Map<String, String> environmentVariables,
398                                                                             int[] ports) {
399     final ArrayList<String> args = new ArrayList<String>();
400     args.add(sdk.getHomePath());
401     final String versionString = sdk.getVersionString();
402     if (versionString == null || !versionString.toLowerCase().contains("jython")) {
403       args.add("-u");
404     }
405     args.add(FileUtil.toSystemDependentName(PythonHelpersLocator.getHelperPath(PYDEV_PYDEVCONSOLE_PY)));
406     for (int port : ports) {
407       args.add(String.valueOf(port));
408     }
409     return new CommandLineArgumentsProvider() {
410       @Override
411       public String[] getArguments() {
412         return ArrayUtil.toStringArray(args);
413       }
414
415       @Override
416       public boolean passParentEnvs() {
417         return false;
418       }
419
420       @Override
421       public Map<String, String> getAdditionalEnvs() {
422         return addDefaultEnvironments(sdk, environmentVariables, getProject());
423       }
424     };
425   }
426
427   @Override
428   protected PythonConsoleView createConsoleView() {
429     PythonConsoleView consoleView = new PythonConsoleView(getProject(), getConsoleTitle(), mySdk);
430     myPydevConsoleCommunication.setConsoleFile(consoleView.getVirtualFile());
431     consoleView.addMessageFilter(new PythonTracebackFilter(getProject()));
432     return consoleView;
433   }
434
435   @Override
436   protected Process createProcess() throws ExecutionException {
437     if (PySdkUtil.isRemote(mySdk)) {
438       PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
439       if (manager != null) {
440         return createRemoteConsoleProcess(manager, myCommandLineArgumentsProvider.getArguments(),
441                                           myCommandLineArgumentsProvider.getAdditionalEnvs());
442       }
443       throw new PythonRemoteInterpreterManager.PyRemoteInterpreterExecutionException();
444     }
445     else {
446       myCommandLine = myCommandLineArgumentsProvider.getCommandLineString();
447       Map<String, String> envs = myCommandLineArgumentsProvider.getAdditionalEnvs();
448       if (envs != null) {
449         EncodingEnvironmentUtil.fixDefaultEncodingIfMac(envs, getProject());
450       }
451       final Process server = ProcessRunner
452         .createProcess(getWorkingDir(), envs, myCommandLineArgumentsProvider.getArguments());
453       try {
454         myPydevConsoleCommunication = new PydevConsoleCommunication(getProject(), myPorts[0], server, myPorts[1]);
455       }
456       catch (Exception e) {
457         throw new ExecutionException(e.getMessage());
458       }
459       return server;
460     }
461   }
462
463   private Process createRemoteConsoleProcess(PythonRemoteInterpreterManager manager, String[] command, Map<String, String> env)
464     throws ExecutionException {
465     PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData();
466     assert data != null;
467
468     GeneralCommandLine commandLine = new GeneralCommandLine(command);
469
470
471     commandLine.getEnvironment().putAll(env);
472
473     commandLine.getParametersList().set(1, PythonRemoteInterpreterManager.toSystemDependent(new File(data.getHelpersPath(),
474                                                                                                      PYDEV_PYDEVCONSOLE_PY)
475                                                                                               .getPath(),
476                                                                                             PySourcePosition.isWindowsPath(
477                                                                                               data.getInterpreterPath())
478     ));
479     commandLine.getParametersList().set(2, "0");
480     commandLine.getParametersList().set(3, "0");
481
482     myCommandLine = commandLine.getCommandLineString();
483
484     try {
485       myRemoteCredentials = data.getRemoteSdkCredentials(true);
486       PyRemotePathMapper pathMapper = manager.setupMappings(getProject(), data, null);
487
488       RemoteSshProcess remoteProcess =
489         manager.createRemoteProcess(getProject(), myRemoteCredentials, pathMapper, commandLine, true);
490
491
492       Couple<Integer> remotePorts = getRemotePortsFromProcess(remoteProcess);
493
494       remoteProcess.addLocalTunnel(myPorts[0], myRemoteCredentials.getHost(), remotePorts.first);
495       remoteProcess.addRemoteTunnel(remotePorts.second, "localhost", myPorts[1]);
496
497
498       myPydevConsoleCommunication = new PydevRemoteConsoleCommunication(getProject(), myPorts[0], remoteProcess, myPorts[1]);
499       return remoteProcess;
500     }
501     catch (Exception e) {
502       throw new ExecutionException(e.getMessage());
503     }
504   }
505
506   private static Couple<Integer> getRemotePortsFromProcess(RemoteSshProcess process) throws ExecutionException {
507     Scanner s = new Scanner(process.getInputStream());
508
509     return Couple.of(readInt(s, process), readInt(s, process));
510   }
511
512   private static int readInt(Scanner s, Process process) throws ExecutionException {
513     long started = System.currentTimeMillis();
514
515     StringBuilder sb = new StringBuilder();
516     boolean flag = false;
517
518     while (System.currentTimeMillis() - started < PORTS_WAITING_TIMEOUT) {
519       if (s.hasNextLine()) {
520         String line = s.nextLine();
521         sb.append(line).append("\n");
522         try {
523           int i = Integer.parseInt(line);
524           if (flag) {
525             LOG.warn("Unexpected strings in output:\n" + sb.toString());
526           }
527           return i;
528         }
529         catch (NumberFormatException ignored) {
530           flag = true;
531           continue;
532         }
533       }
534
535       TimeoutUtil.sleep(200);
536
537       if (process.exitValue() != 0) {
538         String error;
539         try {
540           error = "Console process terminated with error:\n" + StreamUtil.readText(process.getErrorStream()) + sb.toString();
541         }
542         catch (Exception ignored) {
543           error = "Console process terminated with exit code " + process.exitValue() + ", output:" + sb.toString();
544         }
545         throw new ExecutionException(error);
546       }
547       else {
548         break;
549       }
550     }
551
552     throw new ExecutionException("Couldn't read integer value from stream");
553   }
554
555   @Override
556   protected PyConsoleProcessHandler createProcessHandler(final Process process) {
557     if (PySdkUtil.isRemote(mySdk)) {
558       PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
559       if (manager != null) {
560         PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData();
561         assert data != null;
562         myProcessHandler =
563           manager.createConsoleProcessHandler(process, myRemoteCredentials, getConsoleView(), myPydevConsoleCommunication,
564                                               myCommandLine, CharsetToolkit.UTF8_CHARSET,
565                                               manager.setupMappings(getProject(), data, null));
566       }
567       else {
568         LOG.error("Can't create remote console process handler");
569       }
570     }
571     else {
572       myProcessHandler = new PyConsoleProcessHandler(process, getConsoleView(), myPydevConsoleCommunication, myCommandLine,
573                                                      CharsetToolkit.UTF8_CHARSET);
574     }
575     return myProcessHandler;
576   }
577
578   public void initAndRun(final String... statements2execute) throws ExecutionException {
579     super.initAndRun();
580
581     connect(statements2execute);
582   }
583
584   public void connect(final String[] statements2execute) {
585     if (handshake()) {
586       ApplicationManager.getApplication().invokeLater(new Runnable() {
587
588         @Override
589         public void run() {
590           // Propagate console communication to language console
591           final PythonConsoleView consoleView = getConsoleView();
592
593           consoleView.setConsoleCommunication(myPydevConsoleCommunication);
594           consoleView.setSdk(mySdk);
595           consoleView.setExecutionHandler(myConsoleExecuteActionHandler);
596           myProcessHandler.addProcessListener(new ProcessAdapter() {
597             @Override
598             public void onTextAvailable(ProcessEvent event, Key outputType) {
599               consoleView.print(event.getText(), outputType);
600             }
601           });
602
603           enableConsoleExecuteAction();
604
605           for (String statement : statements2execute) {
606             consoleView.executeStatement(statement + "\n", ProcessOutputTypes.SYSTEM);
607           }
608
609           fireConsoleInitializedEvent(consoleView);
610         }
611       });
612     }
613     else {
614       getConsoleView().print("Couldn't connect to console process.", ProcessOutputTypes.STDERR);
615       myProcessHandler.destroyProcess();
616       finishConsole();
617     }
618   }
619
620   @Override
621   protected String constructConsoleTitle(@NotNull String consoleTitle) {
622     if (myConsoleTitle == null) {
623       myConsoleTitle = super.constructConsoleTitle(consoleTitle);
624     }
625     return myConsoleTitle;
626   }
627
628   protected AnAction createRerunAction() {
629     return new RestartAction(this);
630   }
631
632   private AnAction createInterruptAction() {
633     AnAction anAction = new AnAction() {
634       @Override
635       public void actionPerformed(final AnActionEvent e) {
636         if (myPydevConsoleCommunication.isExecuting()) {
637           getConsoleView().print("^C", ProcessOutputTypes.SYSTEM);
638         }
639         myPydevConsoleCommunication.interrupt();
640       }
641
642       @Override
643       public void update(final AnActionEvent e) {
644         EditorEx consoleEditor = getConsoleView().getConsoleEditor();
645         boolean enabled = IJSwingUtilities.hasFocus(consoleEditor.getComponent()) && !consoleEditor.getSelectionModel().hasSelection();
646         e.getPresentation().setEnabled(enabled);
647       }
648     };
649     anAction
650       .registerCustomShortcutSet(KeyEvent.VK_C, InputEvent.CTRL_MASK, getConsoleView().getConsoleEditor().getComponent());
651     anAction.getTemplatePresentation().setVisible(false);
652     return anAction;
653   }
654
655
656   private AnAction createBackspaceHandlingAction() {
657     final AnAction upAction = new AnAction() {
658       @Override
659       public void actionPerformed(final AnActionEvent e) {
660         new WriteCommandAction(getConsoleView().getProject(), getConsoleView().getFile()) {
661           @Override
662           protected void run(@NotNull final Result result) throws Throwable {
663             String text = getConsoleView().getEditorDocument().getText();
664             String newText = text.substring(0, text.length() - myConsoleExecuteActionHandler.getPythonIndent());
665             getConsoleView().getEditorDocument().setText(newText);
666             getConsoleView().getConsoleEditor().getCaretModel().moveToOffset(newText.length());
667           }
668         }.execute();
669       }
670
671       @Override
672       public void update(final AnActionEvent e) {
673         e.getPresentation()
674           .setEnabled(myConsoleExecuteActionHandler.getCurrentIndentSize() >= myConsoleExecuteActionHandler.getPythonIndent() &&
675                       isIndentSubstring(getConsoleView().getEditorDocument().getText()));
676       }
677     };
678     upAction.registerCustomShortcutSet(KeyEvent.VK_BACK_SPACE, 0, null);
679     upAction.getTemplatePresentation().setVisible(false);
680     return upAction;
681   }
682
683   private boolean isIndentSubstring(String text) {
684     int indentSize = myConsoleExecuteActionHandler.getPythonIndent();
685     return text.length() >= indentSize && CharMatcher.WHITESPACE.matchesAllOf(text.substring(text.length() - indentSize));
686   }
687
688   private void enableConsoleExecuteAction() {
689     myConsoleExecuteActionHandler.setEnabled(true);
690   }
691
692   private boolean handshake() {
693     boolean res;
694     long started = System.currentTimeMillis();
695     do {
696       try {
697         res = myPydevConsoleCommunication.handshake();
698       }
699       catch (XmlRpcException ignored) {
700         res = false;
701       }
702       if (res) {
703         break;
704       }
705       else {
706         long now = System.currentTimeMillis();
707         if (now - started > APPROPRIATE_TO_WAIT) {
708           break;
709         }
710         else {
711           TimeoutUtil.sleep(100);
712         }
713       }
714     }
715     while (true);
716     return res;
717   }
718
719   @Override
720   protected AnAction createStopAction() {
721     final AnAction generalStopAction = super.createStopAction();
722     return createConsoleStoppingAction(generalStopAction);
723   }
724
725   @Override
726   protected AnAction createCloseAction(Executor defaultExecutor, final RunContentDescriptor descriptor) {
727     final AnAction generalCloseAction = super.createCloseAction(defaultExecutor, descriptor);
728
729     final AnAction stopAction = new DumbAwareAction() {
730       @Override
731       public void update(AnActionEvent e) {
732         generalCloseAction.update(e);
733       }
734
735       @Override
736       public void actionPerformed(AnActionEvent e) {
737         e = stopConsole(e);
738
739         clearContent(descriptor);
740
741         generalCloseAction.actionPerformed(e);
742       }
743     };
744     stopAction.copyFrom(generalCloseAction);
745     return stopAction;
746   }
747
748   protected void clearContent(RunContentDescriptor descriptor) {
749   }
750
751   private AnAction createConsoleStoppingAction(final AnAction generalStopAction) {
752     final AnAction stopAction = new DumbAwareAction() {
753       @Override
754       public void update(AnActionEvent e) {
755         generalStopAction.update(e);
756       }
757
758       @Override
759       public void actionPerformed(AnActionEvent e) {
760         e = stopConsole(e);
761
762         generalStopAction.actionPerformed(e);
763       }
764     };
765     stopAction.copyFrom(generalStopAction);
766     return stopAction;
767   }
768
769   private AnActionEvent stopConsole(AnActionEvent e) {
770     if (myPydevConsoleCommunication != null) {
771       e = new AnActionEvent(e.getInputEvent(), e.getDataContext(), e.getPlace(),
772                             e.getPresentation(), e.getActionManager(), e.getModifiers());
773       try {
774         closeCommunication();
775         // waiting for REPL communication before destroying process handler
776         Thread.sleep(300);
777       }
778       catch (Exception ignored) {
779         // Ignore
780       }
781     }
782     return e;
783   }
784
785   protected AnAction createSplitLineAction() {
786
787     class ConsoleSplitLineAction extends EditorAction {
788
789       private static final String CONSOLE_SPLIT_LINE_ACTION_ID = "Console.SplitLine";
790
791       public ConsoleSplitLineAction() {
792         super(new EditorWriteActionHandler() {
793
794           private final SplitLineAction mySplitLineAction = new SplitLineAction();
795
796           @Override
797           public boolean isEnabled(Editor editor, DataContext dataContext) {
798             return mySplitLineAction.getHandler().isEnabled(editor, dataContext);
799           }
800
801           @Override
802           public void executeWriteAction(Editor editor, @Nullable Caret caret, DataContext dataContext) {
803             ((EditorWriteActionHandler)mySplitLineAction.getHandler()).executeWriteAction(editor, caret, dataContext);
804             editor.getCaretModel().getCurrentCaret().moveCaretRelatively(0, 1, false, true);
805           }
806         });
807       }
808
809       public void setup() {
810         EmptyAction.setupAction(this, CONSOLE_SPLIT_LINE_ACTION_ID, null);
811       }
812     }
813
814     ConsoleSplitLineAction action = new ConsoleSplitLineAction();
815     action.setup();
816     return action;
817   }
818
819   private void closeCommunication() {
820     if (!myProcessHandler.isProcessTerminated()) {
821       myPydevConsoleCommunication.close();
822     }
823   }
824
825   @NotNull
826   @Override
827   protected ProcessBackedConsoleExecuteActionHandler createExecuteActionHandler() {
828     myConsoleExecuteActionHandler =
829       new PydevConsoleExecuteActionHandler(getConsoleView(), getProcessHandler(), myPydevConsoleCommunication);
830     myConsoleExecuteActionHandler.setEnabled(false);
831     new ConsoleHistoryController(myConsoleType.getTypeId(), "", getConsoleView()).install();
832     return myConsoleExecuteActionHandler;
833   }
834
835   public PydevConsoleCommunication getPydevConsoleCommunication() {
836     return myPydevConsoleCommunication;
837   }
838
839   public static boolean isInPydevConsole(final PsiElement element) {
840     return element instanceof PydevConsoleElement || getConsoleCommunication(element) != null;
841   }
842
843   public static boolean isPythonConsole(@Nullable FileElement element) {
844     return getPythonConsoleData(element) != null;
845   }
846
847   @Nullable
848   public static PythonConsoleData getPythonConsoleData(@Nullable FileElement element) {
849     if (element == null || element.getPsi() == null || element.getPsi().getContainingFile() == null) {
850       return null;
851     }
852
853     VirtualFile file = getConsoleFile(element.getPsi().getContainingFile());
854
855     if (file == null) {
856       return null;
857     }
858     return file.getUserData(PyConsoleUtil.PYTHON_CONSOLE_DATA);
859   }
860
861   private static VirtualFile getConsoleFile(PsiFile psiFile) {
862     VirtualFile file = psiFile.getViewProvider().getVirtualFile();
863     if (file instanceof LightVirtualFile) {
864       file = ((LightVirtualFile)file).getOriginalFile();
865     }
866     return file;
867   }
868
869   @Nullable
870   public static ConsoleCommunication getConsoleCommunication(final PsiElement element) {
871     final PsiFile containingFile = element.getContainingFile();
872     return containingFile != null ? containingFile.getCopyableUserData(CONSOLE_KEY) : null;
873   }
874
875   @Nullable
876   public static Sdk getConsoleSdk(final PsiElement element) {
877     final PsiFile containingFile = element.getContainingFile();
878     return containingFile != null ? containingFile.getCopyableUserData(CONSOLE_SDK) : null;
879   }
880
881   @Override
882   protected boolean shouldAddNumberToTitle() {
883     return true;
884   }
885
886   public void addConsoleListener(ConsoleListener consoleListener) {
887     myConsoleListeners.add(consoleListener);
888   }
889
890   public void removeConsoleListener(ConsoleListener consoleListener) {
891     myConsoleListeners.remove(consoleListener);
892   }
893
894   private void fireConsoleInitializedEvent(LanguageConsoleView consoleView) {
895     for (ConsoleListener listener : myConsoleListeners) {
896       listener.handleConsoleInitialized(consoleView);
897     }
898   }
899
900
901   public interface ConsoleListener {
902     void handleConsoleInitialized(LanguageConsoleView consoleView);
903   }
904
905
906   private static class RestartAction extends AnAction {
907     private PydevConsoleRunner myConsoleRunner;
908
909
910     private RestartAction(PydevConsoleRunner runner) {
911       copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_RERUN));
912       getTemplatePresentation().setIcon(AllIcons.Actions.Restart);
913       myConsoleRunner = runner;
914     }
915
916     @Override
917     public void actionPerformed(AnActionEvent e) {
918       myConsoleRunner.rerun();
919     }
920   }
921
922   private void rerun() {
923     new Task.Backgroundable(getProject(), "Restarting console", true) {
924       @Override
925       public void run(@NotNull ProgressIndicator indicator) {
926         UIUtil.invokeLaterIfNeeded(new Runnable() {
927           @Override
928           public void run() {
929             closeCommunication();
930           }
931         });
932
933         myProcessHandler.waitFor();
934
935         UIUtil.invokeLaterIfNeeded(new Runnable() {
936           @Override
937           public void run() {
938             PydevConsoleRunner.this.run();
939           }
940         });
941       }
942     }.queue();
943   }
944
945   private class ShowVarsAction extends ToggleAction implements DumbAware {
946     private boolean mySelected = false;
947
948     public ShowVarsAction() {
949       super("Show Variables", "Shows active console variables", AllIcons.Debugger.Watches);
950     }
951
952     @Override
953     public boolean isSelected(AnActionEvent e) {
954       return mySelected;
955     }
956
957     @Override
958     public void setSelected(AnActionEvent e, boolean state) {
959       mySelected = state;
960
961       if (mySelected) {
962         getConsoleView().showVariables(myPydevConsoleCommunication);
963       }
964       else {
965         getConsoleView().restoreWindow();
966       }
967     }
968   }
969
970
971   private class ConnectDebuggerAction extends ToggleAction implements DumbAware {
972     private boolean mySelected = false;
973     private XDebugSession mySession = null;
974
975     public ConnectDebuggerAction() {
976       super("Attach Debugger", "Enables tracing of code executed in console", AllIcons.Actions.StartDebugger);
977     }
978
979     @Override
980     public boolean isSelected(AnActionEvent e) {
981       return mySelected;
982     }
983
984     @Override
985     public void update(AnActionEvent e) {
986       if (mySession != null) {
987         e.getPresentation().setEnabled(false);
988       }
989       else {
990         e.getPresentation().setEnabled(true);
991       }
992     }
993
994     @Override
995     public void setSelected(AnActionEvent e, boolean state) {
996       mySelected = state;
997
998       if (mySelected) {
999         try {
1000           mySession = connectToDebugger();
1001         }
1002         catch (Exception e1) {
1003           LOG.error(e1);
1004           Messages.showErrorDialog("Can't connect to debugger", "Error Connecting Debugger");
1005         }
1006       }
1007       else {
1008         //TODO: disable debugging
1009       }
1010     }
1011   }
1012
1013
1014   private static class NewConsoleAction extends AnAction implements DumbAware {
1015     public NewConsoleAction() {
1016       super("New Console", "Creates new python console", AllIcons.General.Add);
1017     }
1018
1019     @Override
1020     public void update(AnActionEvent e) {
1021       e.getPresentation().setEnabled(true);
1022     }
1023
1024     @Override
1025     public void actionPerformed(AnActionEvent e) {
1026       PydevConsoleRunner runner = PythonConsoleRunnerFactory.getInstance().createConsoleRunner(e.getData(CommonDataKeys.PROJECT), e.getData(LangDataKeys.MODULE));
1027       runner.createNewConsole();
1028     }
1029   }
1030
1031   private XDebugSession connectToDebugger() throws ExecutionException {
1032     final ServerSocket serverSocket = PythonCommandLineState.createServerSocket();
1033
1034     final XDebugSession session = XDebuggerManager.getInstance(getProject()).
1035       startSessionAndShowTab("Python Console Debugger", PythonIcons.Python.Python, null, true, new XDebugProcessStarter() {
1036         @NotNull
1037         public XDebugProcess start(@NotNull final XDebugSession session) {
1038           PythonDebugLanguageConsoleView debugConsoleView = new PythonDebugLanguageConsoleView(getProject(), mySdk);
1039
1040           PyConsoleDebugProcessHandler consoleDebugProcessHandler =
1041             new PyConsoleDebugProcessHandler(myProcessHandler);
1042
1043           PyConsoleDebugProcess consoleDebugProcess =
1044             new PyConsoleDebugProcess(session, serverSocket, debugConsoleView,
1045                                       consoleDebugProcessHandler);
1046
1047           PythonDebugConsoleCommunication communication =
1048             PyDebugRunner.initDebugConsoleView(getProject(), consoleDebugProcess, debugConsoleView, consoleDebugProcessHandler, session);
1049
1050           communication.addCommunicationListener(new ConsoleCommunicationListener() {
1051             @Override
1052             public void commandExecuted(boolean more) {
1053               session.rebuildViews();
1054             }
1055
1056             @Override
1057             public void inputRequested() {
1058             }
1059           });
1060
1061           myPydevConsoleCommunication.setDebugCommunication(communication);
1062           debugConsoleView.attachToProcess(consoleDebugProcessHandler);
1063
1064           consoleDebugProcess.waitForNextConnection();
1065
1066           try {
1067             consoleDebugProcess.connect(myPydevConsoleCommunication);
1068           }
1069           catch (Exception e) {
1070             LOG.error(e); //TODO
1071           }
1072
1073           myProcessHandler.notifyTextAvailable("\nDebugger connected.\n", ProcessOutputTypes.STDERR);
1074
1075           return consoleDebugProcess;
1076         }
1077       });
1078
1079     return session;
1080   }
1081
1082   public static PythonConsoleRunnerFactory factory() {
1083     return new PydevConsoleRunnerFactory();
1084   }
1085 }