Fixed exception when LayeredLexer and tokens with 0 length
[idea/community.git] / python / src / com / jetbrains / python / debugger / PyDebugRunner.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.debugger;
17
18 import com.google.common.collect.Lists;
19 import com.intellij.execution.ExecutionException;
20 import com.intellij.execution.ExecutionResult;
21 import com.intellij.execution.configurations.*;
22 import com.intellij.execution.console.LanguageConsoleBuilder;
23 import com.intellij.execution.executors.DefaultDebugExecutor;
24 import com.intellij.execution.process.ProcessHandler;
25 import com.intellij.execution.runners.ExecutionEnvironment;
26 import com.intellij.execution.runners.GenericProgramRunner;
27 import com.intellij.execution.ui.ExecutionConsole;
28 import com.intellij.execution.ui.RunContentDescriptor;
29 import com.intellij.openapi.application.ApplicationManager;
30 import com.intellij.openapi.fileEditor.FileDocumentManager;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.roots.ProjectRootManager;
33 import com.intellij.openapi.util.text.StringUtil;
34 import com.intellij.openapi.vfs.VirtualFile;
35 import com.intellij.xdebugger.XDebugProcess;
36 import com.intellij.xdebugger.XDebugProcessStarter;
37 import com.intellij.xdebugger.XDebugSession;
38 import com.intellij.xdebugger.XDebuggerManager;
39 import com.jetbrains.python.PythonHelpersLocator;
40 import com.jetbrains.python.console.PythonConsoleView;
41 import com.jetbrains.python.console.PythonDebugConsoleCommunication;
42 import com.jetbrains.python.console.PythonDebugLanguageConsoleView;
43 import com.jetbrains.python.console.pydev.ConsoleCommunicationListener;
44 import com.jetbrains.python.run.AbstractPythonRunConfiguration;
45 import com.jetbrains.python.run.CommandLinePatcher;
46 import com.jetbrains.python.run.PythonCommandLineState;
47 import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
50
51 import java.io.File;
52 import java.net.ServerSocket;
53 import java.util.List;
54
55 /**
56  * @author yole
57  */
58 public class PyDebugRunner extends GenericProgramRunner {
59   public static final String PY_DEBUG_RUNNER = "PyDebugRunner";
60
61   @SuppressWarnings("SpellCheckingInspection")
62   public static final String DEBUGGER_MAIN = "pydev/pydevd.py";
63   public static final String CLIENT_PARAM = "--client";
64   public static final String PORT_PARAM = "--port";
65   public static final String FILE_PARAM = "--file";
66   public static final String IDE_PROJECT_ROOTS = "IDE_PROJECT_ROOTS";
67   @SuppressWarnings("SpellCheckingInspection")
68   public static final String GEVENT_SUPPORT = "GEVENT_SUPPORT";
69   public static boolean isModule = false;
70
71   @Override
72   @NotNull
73   public String getRunnerId() {
74     return PY_DEBUG_RUNNER;
75   }
76
77   @Override
78   public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
79     return DefaultDebugExecutor.EXECUTOR_ID.equals(executorId) &&
80            profile instanceof AbstractPythonRunConfiguration &&
81            ((AbstractPythonRunConfiguration)profile).canRunWithCoverage();
82   }
83
84   @Override
85   protected RunContentDescriptor doExecute(@NotNull RunProfileState state, @NotNull final ExecutionEnvironment environment) throws ExecutionException {
86     FileDocumentManager.getInstance().saveAllDocuments();
87
88     final PythonCommandLineState pyState = (PythonCommandLineState)state;
89     final ServerSocket serverSocket = PythonCommandLineState.createServerSocket();
90     final int serverLocalPort = serverSocket.getLocalPort();
91     RunProfile profile = environment.getRunProfile();
92     final ExecutionResult result = pyState.execute(environment.getExecutor(), createCommandLinePatchers(environment.getProject(), pyState, profile, serverLocalPort));
93
94     final XDebugSession session = XDebuggerManager.getInstance(environment.getProject()).
95       startSession(environment, new XDebugProcessStarter() {
96         @Override
97         @NotNull
98         public XDebugProcess start(@NotNull final XDebugSession session) {
99           PyDebugProcess pyDebugProcess =
100             new PyDebugProcess(session, serverSocket, result.getExecutionConsole(), result.getProcessHandler(),
101                                pyState.isMultiprocessDebug());
102
103           createConsoleCommunicationAndSetupActions(environment.getProject(), result, pyDebugProcess, session);
104
105
106           return pyDebugProcess;
107         }
108       });
109     return session.getRunContentDescriptor();
110   }
111
112   public static int findIndex(List<String> paramList, String paramName) {
113     for (int i = 0; i < paramList.size(); i++) {
114       if (paramName.equals(paramList.get(i))) {
115         return i + 1;
116       }
117     }
118     return -1;
119   }
120
121   public static void createConsoleCommunicationAndSetupActions(@NotNull final Project project,
122                                                                @NotNull final ExecutionResult result,
123                                                                @NotNull PyDebugProcess debugProcess, @NotNull XDebugSession session) {
124     ExecutionConsole console = result.getExecutionConsole();
125     if (console instanceof PythonDebugLanguageConsoleView) {
126       ProcessHandler processHandler = result.getProcessHandler();
127
128       initDebugConsoleView(project, debugProcess, (PythonDebugLanguageConsoleView)console, processHandler, session);
129     }
130   }
131
132   public static PythonDebugConsoleCommunication initDebugConsoleView(Project project,
133                                                                      PyDebugProcess debugProcess,
134                                                                      PythonDebugLanguageConsoleView console,
135                                                                      ProcessHandler processHandler, final XDebugSession session) {
136     PythonConsoleView pythonConsoleView = console.getPydevConsoleView();
137     PythonDebugConsoleCommunication debugConsoleCommunication = new PythonDebugConsoleCommunication(project, debugProcess);
138
139     pythonConsoleView.setConsoleCommunication(debugConsoleCommunication);
140
141
142     PydevDebugConsoleExecuteActionHandler consoleExecuteActionHandler = new PydevDebugConsoleExecuteActionHandler(pythonConsoleView,
143                                                                                                                   processHandler,
144                                                                                                                   debugConsoleCommunication);
145     pythonConsoleView.setExecutionHandler(consoleExecuteActionHandler);
146
147     debugProcess.getSession().addSessionListener(consoleExecuteActionHandler);
148     new LanguageConsoleBuilder(pythonConsoleView).processHandler(processHandler).initActions(consoleExecuteActionHandler, "py");
149
150
151     debugConsoleCommunication.addCommunicationListener(new ConsoleCommunicationListener() {
152       @Override
153       public void commandExecuted(boolean more) {
154         session.rebuildViews();
155       }
156
157       @Override
158       public void inputRequested() {
159       }
160     });
161
162     return debugConsoleCommunication;
163   }
164
165   @Nullable
166   private static CommandLinePatcher createRunConfigPatcher(RunProfileState state, RunProfile profile) {
167     CommandLinePatcher runConfigPatcher = null;
168     if (state instanceof PythonCommandLineState && profile instanceof AbstractPythonRunConfiguration) {
169       runConfigPatcher = (AbstractPythonRunConfiguration)profile;
170     }
171     return runConfigPatcher;
172   }
173
174   public static CommandLinePatcher[] createCommandLinePatchers(final Project project, final PythonCommandLineState state,
175                                                                RunProfile profile,
176                                                                final int serverLocalPort) {
177     return new CommandLinePatcher[]{createDebugServerPatcher(project, state, serverLocalPort), createRunConfigPatcher(state, profile)};
178   }
179
180   private static CommandLinePatcher createDebugServerPatcher(final Project project,
181                                                              final PythonCommandLineState pyState,
182                                                              final int serverLocalPort) {
183     return new CommandLinePatcher() {
184
185       private void patchExeParams(ParametersList parametersList) {
186         // we should remove '-m' parameter, but notify debugger of it
187         // but we can't remove one parameter from group, so we create new parameters group
188         ParamsGroup newExeParams = new ParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS);
189         int exeParamsIndex = parametersList.getParamsGroups().indexOf(
190           parametersList.getParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS));
191         ParamsGroup exeParamsOld = parametersList.removeParamsGroup(exeParamsIndex);
192         isModule = false;
193         for (String param: exeParamsOld.getParameters()) {
194           if (!param.equals("-m")) {
195             newExeParams.addParameter(param);
196           } else {
197             isModule = true;
198           }
199         }
200
201         parametersList.addParamsGroupAt(exeParamsIndex, newExeParams);
202       }
203
204
205       @Override
206       public void patchCommandLine(GeneralCommandLine commandLine) {
207         // script name is the last parameter; all other params are for python interpreter; insert just before name
208         ParametersList parametersList = commandLine.getParametersList();
209
210         @SuppressWarnings("ConstantConditions") @NotNull
211         ParamsGroup debugParams = parametersList.getParamsGroup(PythonCommandLineState.GROUP_DEBUGGER);
212
213         patchExeParams(parametersList);
214
215         @SuppressWarnings("ConstantConditions") @NotNull
216         ParamsGroup exeParams = parametersList.getParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS);
217
218         final PythonSdkFlavor flavor = pyState.getSdkFlavor();
219         if (flavor != null) {
220           assert exeParams != null;
221           for (String option : flavor.getExtraDebugOptions()) {
222             exeParams.addParameter(option);
223           }
224         }
225
226         assert debugParams != null;
227         fillDebugParameters(project, debugParams, serverLocalPort, pyState, commandLine);
228       }
229     };
230   }
231
232   private static void fillDebugParameters(@NotNull Project project,
233                                           @NotNull ParamsGroup debugParams,
234                                           int serverLocalPort,
235                                           @NotNull PythonCommandLineState pyState,
236                                           @NotNull GeneralCommandLine generalCommandLine) {
237     debugParams.addParameter(PythonHelpersLocator.getHelperPath(DEBUGGER_MAIN));
238     if (pyState.isMultiprocessDebug()) {
239       //noinspection SpellCheckingInspection
240       debugParams.addParameter("--multiproc");
241     }
242
243     if (isModule) {
244       debugParams.addParameter("--module");
245     }
246
247     if (ApplicationManager.getApplication().isUnitTestMode()) {
248       debugParams.addParameter("--DEBUG");
249     }
250
251     if (PyDebuggerOptionsProvider.getInstance(project).isSaveCallSignatures()) {
252       debugParams.addParameter("--save-signatures");
253     }
254
255     if (PyDebuggerOptionsProvider.getInstance(project).isSupportGeventDebugging()) {
256       generalCommandLine.getEnvironment().put(GEVENT_SUPPORT, "True");
257     }
258
259     addProjectRootsToEnv(project, generalCommandLine);
260
261     final String[] debuggerArgs = new String[]{
262       CLIENT_PARAM, "127.0.0.1",
263       PORT_PARAM, String.valueOf(serverLocalPort),
264       FILE_PARAM
265     };
266     for (String s : debuggerArgs) {
267       debugParams.addParameter(s);
268     }
269   }
270
271   private static void addProjectRootsToEnv(@NotNull Project project, @NotNull GeneralCommandLine commandLine) {
272
273     List<String> roots = Lists.newArrayList();
274     for (VirtualFile contentRoot : ProjectRootManager.getInstance(project).getContentRoots()) {
275       roots.add(contentRoot.getPath());
276     }
277
278     commandLine.getEnvironment().put(IDE_PROJECT_ROOTS, StringUtil.join(roots, File.pathSeparator));
279   }
280 }