596eb4e2325b4096958c246fff84b00e5dfbccea
[idea/community.git] / python / educational-python / src / com / jetbrains / python / edu / debugger / PyEduDebugRunner.java
1 package com.jetbrains.python.edu.debugger;
2
3 import com.intellij.execution.ExecutionResult;
4 import com.intellij.execution.Executor;
5 import com.intellij.execution.configurations.RunProfile;
6 import com.intellij.execution.configurations.RunProfileState;
7 import com.intellij.execution.filters.UrlFilter;
8 import com.intellij.execution.process.ProcessHandler;
9 import com.intellij.execution.runners.ExecutionEnvironment;
10 import com.intellij.execution.ui.ExecutionConsole;
11 import com.intellij.execution.ui.RunnerLayoutUi;
12 import com.intellij.execution.ui.actions.CloseAction;
13 import com.intellij.execution.ui.layout.PlaceInGrid;
14 import com.intellij.icons.AllIcons;
15 import com.intellij.ide.actions.ContextHelpAction;
16 import com.intellij.openapi.actionSystem.*;
17 import com.intellij.openapi.diagnostic.Logger;
18 import com.intellij.openapi.editor.Document;
19 import com.intellij.openapi.fileEditor.FileDocumentManager;
20 import com.intellij.openapi.module.ModuleManager;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.projectRoots.Sdk;
23 import com.intellij.openapi.util.SystemInfo;
24 import com.intellij.openapi.util.io.FileUtil;
25 import com.intellij.openapi.vfs.VfsUtil;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.ui.content.Content;
28 import com.intellij.ui.content.ContentManager;
29 import com.intellij.xdebugger.XDebugSession;
30 import com.intellij.xdebugger.XDebuggerBundle;
31 import com.intellij.xdebugger.impl.XDebugSessionImpl;
32 import com.intellij.xdebugger.impl.actions.XDebuggerActions;
33 import com.intellij.xdebugger.impl.ui.XDebugSessionTab;
34 import com.jetbrains.python.console.PythonDebugLanguageConsoleView;
35 import com.jetbrains.python.debugger.PyDebugProcess;
36 import com.jetbrains.python.debugger.PyDebugRunner;
37 import com.jetbrains.python.debugger.PyLineBreakpointType;
38 import com.jetbrains.python.debugger.PyRunCythonExtensionsFilter;
39 import com.jetbrains.python.run.PythonCommandLineState;
40 import com.jetbrains.python.run.PythonRunConfiguration;
41 import com.jetbrains.python.run.PythonTracebackFilter;
42 import com.jetbrains.python.sdk.PythonSdkType;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
45
46 import java.io.File;
47 import java.net.ServerSocket;
48
49 public class PyEduDebugRunner extends PyDebugRunner {
50   private static final Logger LOG = Logger.getInstance(PyEduDebugRunner.class);
51   public static final int NO_LINE = -1;
52
53   @Override
54   public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
55     return executorId.equals(PyEduDebugExecutor.ID);
56   }
57
58   @NotNull
59   @Override
60   protected PyDebugProcess createDebugProcess(@NotNull XDebugSession session,
61                                               ServerSocket serverSocket,
62                                               ExecutionResult result,
63                                               PythonCommandLineState pyState) {
64     ExecutionConsole executionConsole = result.getExecutionConsole();
65     ProcessHandler processHandler = result.getProcessHandler();
66     boolean isMultiProcess = pyState.isMultiprocessDebug();
67     String scriptName = getScriptName(pyState);
68     if (scriptName != null) {
69       VirtualFile file = VfsUtil.findFileByIoFile(new File(scriptName), true);
70       if (file != null) {
71         int line = getBreakpointLineNumber(file, session.getProject());
72         if (line != NO_LINE) {
73           return new PyEduDebugProcess(session, serverSocket,
74                                        executionConsole, processHandler,
75                                        isMultiProcess, scriptName, line + 1);
76         }
77       }
78     }
79     LOG.info("Failed to create PyEduDebugProcess. PyDebugProcess created instead.");
80     return new PyDebugProcess(session, serverSocket, executionConsole,
81                               processHandler, isMultiProcess);
82   }
83
84   @Nullable
85   private static String getScriptName(PythonCommandLineState pyState) {
86     ExecutionEnvironment environment = pyState.getEnvironment();
87     if (environment == null) {
88       return null;
89     }
90     RunProfile runProfile = environment.getRunProfile();
91     if (runProfile instanceof PythonRunConfiguration) {
92       String name = FileUtil.toSystemIndependentName(((PythonRunConfiguration)runProfile).getScriptName());
93       return SystemInfo.isWindows ? name.toLowerCase() : name;
94     }
95     return null;
96   }
97
98   /**
99    * @return the smallest line (from 0 to line number) suitable to set breakpoint on it, NO_LINE if there is no such line in the file
100    */
101   private static int getBreakpointLineNumber(@NotNull final VirtualFile file, @NotNull final Project project) {
102     Document document = FileDocumentManager.getInstance().getDocument(file);
103     if (document == null) {
104       return NO_LINE;
105     }
106     PyLineBreakpointType lineBreakpointType = new PyLineBreakpointType();
107     for (int line = 0; line < document.getLineCount(); line++) {
108       if (lineBreakpointType.canPutAt(file, line, project)) {
109         return line;
110       }
111     }
112     return NO_LINE;
113   }
114
115
116   @Override
117   protected void initSession(XDebugSession session, RunProfileState state, Executor executor) {
118     XDebugSessionTab tab = ((XDebugSessionImpl)session).getSessionTab();
119     if (tab != null) {
120       RunnerLayoutUi ui = tab.getUi();
121       ContentManager contentManager = ui.getContentManager();
122       Content content = findContent(contentManager, XDebuggerBundle.message("debugger.session.tab.watches.title"));
123       if (content != null) {
124         contentManager.removeContent(content, true);
125       }
126       content = findContent(contentManager, XDebuggerBundle.message("debugger.session.tab.console.content.name"));
127       if (content != null) {
128         contentManager.removeContent(content, true);
129       }
130       initEduConsole(session, ui);
131     }
132   }
133
134   private static void initEduConsole(@NotNull final XDebugSession session,
135                                      @NotNull final RunnerLayoutUi ui) {
136     Project project = session.getProject();
137     final Sdk sdk = PythonSdkType.findPythonSdk(ModuleManager.getInstance(project).getModules()[0]);
138     final PythonDebugLanguageConsoleView view = new PythonDebugLanguageConsoleView(project, sdk);
139     final ProcessHandler processHandler = session.getDebugProcess().getProcessHandler();
140
141     view.attachToProcess(processHandler);
142     view.addMessageFilter(new PythonTracebackFilter(project));
143     view.addMessageFilter(new UrlFilter());
144     view.addMessageFilter(new PyRunCythonExtensionsFilter(project));
145
146     view.enableConsole(false);
147
148     Content eduConsole =
149       ui.createContent("EduConsole", view.getComponent(),
150                        XDebuggerBundle.message("debugger.session.tab.console.content.name"),
151                        AllIcons.Debugger.ToolConsole, view.getPreferredFocusableComponent());
152     eduConsole.setCloseable(false);
153     ui.addContent(eduConsole, 0, PlaceInGrid.right, false);
154
155     Presentation presentation = view.getSwitchConsoleActionPresentation();
156     ToggleAction action = new ToggleAction(presentation.getText(), presentation.getDescription(), presentation.getIcon()) {
157
158       @Override
159       public boolean isSelected(AnActionEvent e) {
160         return !view.isPrimaryConsoleEnabled();
161       }
162
163       @Override
164       public void setSelected(AnActionEvent e, boolean state) {
165         view.enableConsole(!state);
166       }
167     };
168
169     eduConsole.setActions(new DefaultActionGroup(action), ActionPlaces.DEBUGGER_TOOLBAR,
170                           view.getPreferredFocusableComponent());
171     PyDebugProcess process = (PyDebugProcess)session.getDebugProcess();
172     PyDebugRunner.initDebugConsoleView(project, process, view, processHandler, session);
173
174     patchLeftToolbar(session, ui);
175   }
176
177   private static void patchLeftToolbar(@NotNull XDebugSession session, @NotNull RunnerLayoutUi ui) {
178     DefaultActionGroup newLeftToolbar = new DefaultActionGroup();
179
180     DefaultActionGroup firstGroup = new DefaultActionGroup();
181     addActionToGroup(firstGroup, XDebuggerActions.RESUME);
182     addActionToGroup(firstGroup, IdeActions.ACTION_STOP_PROGRAM);
183     newLeftToolbar.addAll(firstGroup);
184
185     newLeftToolbar.addSeparator();
186
187     Executor executor = PyEduDebugExecutor.getInstance();
188     newLeftToolbar.add(new CloseAction(executor, session.getRunContentDescriptor(), session.getProject()));
189     //TODO: return proper helpID
190     newLeftToolbar.add(new ContextHelpAction(executor.getHelpId()));
191
192     ui.getOptions().setLeftToolbar(newLeftToolbar, ActionPlaces.DEBUGGER_TOOLBAR);
193   }
194
195   private static void addActionToGroup(DefaultActionGroup group, String actionId) {
196     AnAction action = ActionManager.getInstance().getAction(actionId);
197     if (action != null) {
198       action.getTemplatePresentation().setEnabled(true);
199       group.add(action, Constraints.LAST);
200     }
201   }
202
203   @Nullable
204   private static Content findContent(ContentManager manager, String name) {
205     for (Content content : manager.getContents()) {
206       if (content.getDisplayName().equals(name)) {
207         return content;
208       }
209     }
210     return null;
211   }
212 }