Option to run python process in a terminal emulation mode (PY-22487)
[idea/community.git] / python / src / com / jetbrains / python / run / PythonScriptCommandLineState.java
1 /*
2  * Copyright 2000-2016 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.run;
17
18 import com.google.common.collect.Lists;
19 import com.intellij.execution.DefaultExecutionResult;
20 import com.intellij.execution.ExecutionException;
21 import com.intellij.execution.ExecutionResult;
22 import com.intellij.execution.Executor;
23 import com.intellij.execution.configurations.GeneralCommandLine;
24 import com.intellij.execution.configurations.ParametersList;
25 import com.intellij.execution.configurations.ParamsGroup;
26 import com.intellij.execution.console.ConsoleExecuteAction;
27 import com.intellij.execution.executors.DefaultDebugExecutor;
28 import com.intellij.execution.filters.UrlFilter;
29 import com.intellij.execution.process.OSProcessHandler;
30 import com.intellij.execution.process.ProcessHandler;
31 import com.intellij.execution.runners.ExecutionEnvironment;
32 import com.intellij.openapi.actionSystem.AnAction;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.projectRoots.Sdk;
35 import com.intellij.openapi.util.SystemInfo;
36 import com.intellij.openapi.util.text.StringUtil;
37 import com.intellij.terminal.TerminalExecutionConsole;
38 import com.intellij.util.ArrayUtil;
39 import com.intellij.util.io.BaseDataReader;
40 import com.intellij.util.io.BaseOutputReader;
41 import com.jetbrains.python.PythonHelper;
42 import com.jetbrains.python.console.PyConsoleOptions;
43 import com.jetbrains.python.console.PyConsoleType;
44 import com.jetbrains.python.console.PydevConsoleRunnerImpl;
45 import com.jetbrains.python.console.actions.ShowVarsAction;
46 import com.jetbrains.python.sdk.PythonEnvUtil;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49
50 import java.util.List;
51 import java.util.Map;
52
53 import static com.intellij.execution.runners.AbstractConsoleRunnerWithHistory.registerActionShortcuts;
54
55 /**
56  * @author yole
57  */
58 public class PythonScriptCommandLineState extends PythonCommandLineState {
59   private final PythonRunConfiguration myConfig;
60
61   public PythonScriptCommandLineState(PythonRunConfiguration runConfiguration, ExecutionEnvironment env) {
62     super(runConfiguration, env);
63     myConfig = runConfiguration;
64   }
65
66   @Override
67   public ExecutionResult execute(Executor executor, final CommandLinePatcher... patchers) throws ExecutionException {
68     if (myConfig.showCommandLineAfterwards() && !myConfig.emulateTerminal()) {
69       if (executor.getId() == DefaultDebugExecutor.EXECUTOR_ID) {
70         return super.execute(executor, ArrayUtil.append(patchers, new CommandLinePatcher() {
71           @Override
72           public void patchCommandLine(GeneralCommandLine commandLine) {
73             commandLine.getParametersList().getParamsGroup(PythonCommandLineState.GROUP_DEBUGGER).addParameterAt(1, "--cmd-line");
74           }
75         }));
76       }
77
78       PythonScriptWithConsoleRunner runner =
79         new PythonScriptWithConsoleRunner(myConfig.getProject(), myConfig.getSdk(), PyConsoleType.PYTHON, myConfig.getWorkingDirectory(),
80                                           myConfig.getEnvs(), patchers,
81                                           PyConsoleOptions.getInstance(myConfig.getProject()).getPythonConsoleSettings());
82
83       runner.setEnableAfterConnection(false);
84       runner.runSync();
85       // runner.getProcessHandler() would be null if execution error occurred
86       if (runner.getProcessHandler() == null) {
87         return null;
88       }
89       runner.getPydevConsoleCommunication().setConsoleView(runner.getConsoleView());
90       List<AnAction> actions = Lists.newArrayList(createActions(runner.getConsoleView(), runner.getProcessHandler()));
91       actions.add(new ShowVarsAction(runner.getConsoleView(), runner.getPydevConsoleCommunication()));
92
93       return new DefaultExecutionResult(runner.getConsoleView(), runner.getProcessHandler(), actions.toArray(new AnAction[actions.size()]));
94     }
95     else if (myConfig.emulateTerminal()) {
96       setRunWithPty(true);
97
98       final ProcessHandler processHandler = startProcess();
99
100       TerminalExecutionConsole executeConsole = new TerminalExecutionConsole(myConfig.getProject(), processHandler);
101
102       executeConsole.addMessageFilter(myConfig.getProject(), new PythonTracebackFilter(myConfig.getProject()));
103       executeConsole.addMessageFilter(myConfig.getProject(), new UrlFilter());
104
105       processHandler.startNotify();
106
107       return new DefaultExecutionResult(executeConsole, processHandler, AnAction.EMPTY_ARRAY);
108     }
109     else {
110       return super.execute(executor, patchers);
111     }
112   }
113
114   @Override
115   public void customizeEnvironmentVars(Map<String, String> envs, boolean passParentEnvs) {
116     if (myConfig.emulateTerminal()) {
117       if (!SystemInfo.isWindows) {
118         envs.put("TERM", "xterm-256color");
119       }
120     }
121   }
122
123   @Override
124   protected ProcessHandler doCreateProcess(GeneralCommandLine commandLine) throws ExecutionException {
125     if (myConfig.emulateTerminal()) {
126       return new OSProcessHandler(commandLine) {
127         @NotNull
128         @Override
129         protected BaseOutputReader.Options readerOptions() {
130           return new BaseOutputReader.Options() {
131             @Override
132             public BaseDataReader.SleepingPolicy policy() {
133               return BaseDataReader.SleepingPolicy.BLOCKING;
134             }
135
136             @Override
137             public boolean splitToLines() {
138               return false;
139             }
140
141             @Override
142             public boolean withSeparators() {
143               return true;
144             }
145           };
146         }
147       };
148     }
149     else {
150       return super.doCreateProcess(commandLine);
151     }
152   }
153
154   @Override
155   protected void buildCommandLineParameters(GeneralCommandLine commandLine) {
156     ParametersList parametersList = commandLine.getParametersList();
157     ParamsGroup exe_options = parametersList.getParamsGroup(GROUP_EXE_OPTIONS);
158     assert exe_options != null;
159     exe_options.addParametersString(myConfig.getInterpreterOptions());
160
161     ParamsGroup script_parameters = parametersList.getParamsGroup(GROUP_SCRIPT);
162     assert script_parameters != null;
163     if (!StringUtil.isEmptyOrSpaces(myConfig.getScriptName())) {
164       script_parameters.addParameter(myConfig.getScriptName());
165     }
166
167     final String script_options_string = myConfig.getScriptParameters();
168     if (script_options_string != null) script_parameters.addParametersString(script_options_string);
169
170     if (!StringUtil.isEmptyOrSpaces(myConfig.getWorkingDirectory())) {
171       commandLine.setWorkDirectory(myConfig.getWorkingDirectory());
172     }
173   }
174
175   /**
176    * @author traff
177    */
178   public class PythonScriptWithConsoleRunner extends PydevConsoleRunnerImpl {
179
180     private CommandLinePatcher[] myPatchers;
181     private String PYDEV_RUN_IN_CONSOLE_PY = "pydev/pydev_run_in_console.py";
182
183     public PythonScriptWithConsoleRunner(@NotNull Project project,
184                                          @NotNull Sdk sdk,
185                                          @NotNull PyConsoleType consoleType,
186                                          @Nullable String workingDir,
187                                          Map<String, String> environmentVariables,
188                                          CommandLinePatcher[] patchers,
189                                          PyConsoleOptions.PyConsoleSettings consoleSettings,
190                                          String... statementsToExecute) {
191       super(project, sdk, consoleType, workingDir, environmentVariables, consoleSettings, (s) -> {
192       }, statementsToExecute);
193       myPatchers = patchers;
194     }
195
196     @Override
197     protected void createContentDescriptorAndActions() {
198       AnAction a = new ConsoleExecuteAction(super.getConsoleView(), myConsoleExecuteActionHandler,
199                                             myConsoleExecuteActionHandler.getEmptyExecuteAction(), myConsoleExecuteActionHandler);
200       registerActionShortcuts(Lists.newArrayList(a), getConsoleView().getConsoleEditor().getComponent());
201     }
202
203     @Override
204     protected String getRunnerFileFromHelpers() {
205       return PYDEV_RUN_IN_CONSOLE_PY;
206     }
207
208     @Override
209     protected GeneralCommandLine createCommandLine(@NotNull Sdk sdk,
210                                                    @NotNull Map<String, String> environmentVariables,
211                                                    String workingDir, int[] ports) {
212       GeneralCommandLine consoleCmdLine = doCreateConsoleCmdLine(sdk, environmentVariables, workingDir, ports, PythonHelper.RUN_IN_CONSOLE);
213
214       final GeneralCommandLine cmd = generateCommandLine(myPatchers);
215
216       ParamsGroup group = consoleCmdLine.getParametersList().getParamsGroup(PythonCommandLineState.GROUP_SCRIPT);
217       assert group != null;
218       group.addParameters(cmd.getParametersList().getList());
219
220       PythonEnvUtil.mergePythonPath(consoleCmdLine.getEnvironment(), cmd.getEnvironment());
221
222       consoleCmdLine.getEnvironment().putAll(cmd.getEnvironment());
223
224       return consoleCmdLine;
225     }
226   }
227 }