bc00af821211c229f33e3281f70131de83a55f6e
[idea/community.git] / python / src / com / jetbrains / python / actions / PyExecuteSelectionAction.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.actions;
17
18 import com.google.common.collect.Lists;
19 import com.intellij.execution.ExecutionHelper;
20 import com.intellij.execution.console.LanguageConsoleView;
21 import com.intellij.execution.process.ProcessHandler;
22 import com.intellij.execution.ui.RunContentDescriptor;
23 import com.intellij.openapi.actionSystem.*;
24 import com.intellij.openapi.editor.*;
25 import com.intellij.openapi.editor.ex.util.EditorUtil;
26 import com.intellij.openapi.module.Module;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.util.Pair;
29 import com.intellij.openapi.util.text.StringUtil;
30 import com.intellij.psi.PsiDocumentManager;
31 import com.intellij.psi.PsiFile;
32 import com.intellij.util.Consumer;
33 import com.jetbrains.python.console.PyCodeExecutor;
34 import com.jetbrains.python.console.PydevConsoleRunner;
35 import com.jetbrains.python.console.PythonConsoleRunnerFactory;
36 import com.jetbrains.python.psi.PyFile;
37 import org.jetbrains.annotations.NotNull;
38 import org.jetbrains.annotations.Nullable;
39
40 import java.util.Collection;
41 import java.util.List;
42
43 public class PyExecuteSelectionAction extends AnAction {
44
45   public static final String EXECUTE_SELECTION_IN_CONSOLE = "Execute Selection in Console";
46
47   public PyExecuteSelectionAction() {
48     super(EXECUTE_SELECTION_IN_CONSOLE);
49   }
50
51   public void actionPerformed(AnActionEvent e) {
52     Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext());
53     if (editor != null) {
54       final String selectionText = getSelectionText(editor);
55       if (selectionText != null) {
56         execute(e, selectionText);
57       }
58       else {
59         String line = getLineUnderCaret(editor);
60         if (line != null) {
61           execute(e, line);
62           moveCaretDown(editor);
63         }
64       }
65     }
66   }
67
68   private static void moveCaretDown(Editor editor) {
69     VisualPosition pos = editor.getCaretModel().getVisualPosition();
70     Pair<LogicalPosition, LogicalPosition> lines = EditorUtil.calcSurroundingRange(editor, pos, pos);
71     int offset = editor.getCaretModel().getOffset();
72
73     LogicalPosition lineStart = lines.first;
74     LogicalPosition nextLineStart = lines.second;
75
76     int start = editor.logicalPositionToOffset(lineStart);
77     int end = editor.logicalPositionToOffset(nextLineStart);
78
79     Document document = editor.getDocument();
80
81     if (nextLineStart.line < document.getLineCount()) {
82
83       int newOffset = end + offset - start;
84
85       int nextLineEndOffset = document.getLineEndOffset(nextLineStart.line);
86       if (newOffset >= nextLineEndOffset) {
87         newOffset = nextLineEndOffset;
88       }
89
90       editor.getCaretModel().moveToOffset(newOffset);
91       editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
92     }
93   }
94
95   private static void execute(final AnActionEvent e, final String selectionText) {
96     final Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext());
97     Project project = e.getProject();
98     Module module = e.getData(LangDataKeys.MODULE);
99
100     findCodeExecutor(e, codeExecutor -> executeInConsole(codeExecutor, selectionText, editor), editor, project, module);
101   }
102
103   private static String getLineUnderCaret(Editor editor) {
104     VisualPosition caretPos = editor.getCaretModel().getVisualPosition();
105
106     Pair<LogicalPosition, LogicalPosition> lines = EditorUtil.calcSurroundingRange(editor, caretPos, caretPos);
107
108     LogicalPosition lineStart = lines.first;
109     LogicalPosition nextLineStart = lines.second;
110     int start = editor.logicalPositionToOffset(lineStart);
111     int end = editor.logicalPositionToOffset(nextLineStart);
112     if (end <= start) {
113       return null;
114     }
115     return editor.getDocument().getCharsSequence().subSequence(start, end).toString();
116   }
117
118   @Nullable
119   private static String getSelectionText(@NotNull Editor editor) {
120     if (editor.getSelectionModel().hasSelection()) {
121       SelectionModel model = editor.getSelectionModel();
122
123       return model.getSelectedText();
124     }
125     else {
126       return null;
127     }
128   }
129
130   public void update(AnActionEvent e) {
131     Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext());
132     Presentation presentation = e.getPresentation();
133
134     boolean enabled = false;
135     if (editor != null && isPython(editor)) {
136       String text = getSelectionText(editor);
137       if (text != null) {
138         presentation.setText(EXECUTE_SELECTION_IN_CONSOLE);
139       }
140       else {
141         text = getLineUnderCaret(editor);
142         if (text != null) {
143           presentation.setText("Execute Line in Console");
144         }
145       }
146
147       enabled = !StringUtil.isEmpty(text);
148     }
149
150     presentation.setEnabled(enabled);
151     presentation.setVisible(enabled);
152   }
153
154   private static boolean isPython(Editor editor) {
155     if (editor == null) {
156       return false;
157     }
158
159     Project project = editor.getProject();
160
161     if (project == null) {
162       return false;
163     }
164
165     PsiFile psi = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
166     return psi instanceof PyFile;
167   }
168
169   private static void selectConsole(@NotNull DataContext dataContext, @NotNull Project project,
170                                     final Consumer<PyCodeExecutor> consumer) {
171     Collection<RunContentDescriptor> consoles = getConsoles(project);
172
173     ExecutionHelper
174       .selectContentDescriptor(dataContext, project, consoles, "Select console to execute in", descriptor -> {
175         if (descriptor != null && descriptor.getExecutionConsole() instanceof PyCodeExecutor) {
176           consumer.consume((PyCodeExecutor)descriptor.getExecutionConsole());
177         }
178       });
179   }
180
181   private static Collection<RunContentDescriptor> getConsoles(Project project) {
182
183
184     return ExecutionHelper.findRunningConsole(project, dom -> dom.getExecutionConsole() instanceof PyCodeExecutor && isAlive(dom));
185   }
186
187   private static boolean isAlive(RunContentDescriptor dom) {
188     ProcessHandler processHandler = dom.getProcessHandler();
189     return processHandler != null && !processHandler.isProcessTerminated();
190   }
191
192   private static void findCodeExecutor(AnActionEvent e, Consumer<PyCodeExecutor> consumer, Editor editor, Project project, Module module) {
193     if (project != null && editor != null) {
194       if (canFindConsole(e)) {
195         selectConsole(e.getDataContext(), project, consumer);
196       }
197       else {
198         startConsole(project, consumer, module);
199       }
200     }
201   }
202
203   private static void startConsole(final Project project,
204                                    final Consumer<PyCodeExecutor> consumer,
205                                    Module context) {
206
207     PythonConsoleRunnerFactory consoleRunnerFactory = PythonConsoleRunnerFactory.getInstance();
208     PydevConsoleRunner runner = consoleRunnerFactory.createConsoleRunner(project, null);
209     runner.addConsoleListener(new PydevConsoleRunner.ConsoleListener() {
210       @Override
211       public void handleConsoleInitialized(LanguageConsoleView consoleView) {
212         if (consoleView instanceof PyCodeExecutor) {
213           consumer.consume((PyCodeExecutor)consoleView);
214         }
215       }
216     });
217     runner.run();
218   }
219
220
221   private static boolean canFindConsole(AnActionEvent e) {
222     Project project = e.getProject();
223     if (project != null) {
224       Collection<RunContentDescriptor> descriptors = getConsoles(project);
225       return descriptors.size() > 0;
226     }
227     else {
228       return false;
229     }
230   }
231
232   private static void executeInConsole(@NotNull PyCodeExecutor codeExecutor, @NotNull String text, Editor editor) {
233     codeExecutor.executeCode(text, editor);
234   }
235 }