Merge remote-tracking branch 'origin/master'
[idea/community.git] / python / src / com / jetbrains / python / console / PydevConsoleExecuteActionHandler.java
1 /*
2  * Copyright 2000-2013 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.intellij.codeInsight.hint.HintManager;
19 import com.intellij.execution.console.LanguageConsoleImpl;
20 import com.intellij.execution.console.LanguageConsoleView;
21 import com.intellij.execution.console.ProcessBackedConsoleExecuteActionHandler;
22 import com.intellij.execution.process.ProcessHandler;
23 import com.intellij.openapi.application.Result;
24 import com.intellij.openapi.command.WriteCommandAction;
25 import com.intellij.openapi.editor.Editor;
26 import com.intellij.openapi.editor.EditorModificationUtil;
27 import com.intellij.openapi.fileTypes.PlainTextLanguage;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.util.text.StringUtil;
30 import com.intellij.openapi.vfs.VirtualFile;
31 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
32 import com.intellij.psi.impl.source.codeStyle.IndentHelperImpl;
33 import com.intellij.util.Function;
34 import com.intellij.util.ui.UIUtil;
35 import com.jetbrains.python.PythonFileType;
36 import com.jetbrains.python.PythonLanguage;
37 import com.jetbrains.python.console.pydev.ConsoleCommunication;
38 import com.jetbrains.python.console.pydev.ConsoleCommunicationListener;
39 import com.jetbrains.python.console.pydev.InterpreterResponse;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42
43 /**
44  * @author traff
45  */
46 public class PydevConsoleExecuteActionHandler extends ProcessBackedConsoleExecuteActionHandler implements ConsoleCommunicationListener {
47   private final LanguageConsoleView myConsoleView;
48
49   private String myInMultilineStringState = null;
50   private StringBuilder myInputBuffer;
51   private int myCurrentIndentSize = 0;
52
53   private final ConsoleCommunication myConsoleCommunication;
54   private boolean myEnabled = false;
55
56   public PydevConsoleExecuteActionHandler(LanguageConsoleView consoleView,
57                                           ProcessHandler myProcessHandler,
58                                           ConsoleCommunication consoleCommunication) {
59     super(myProcessHandler, false);
60     myConsoleView = consoleView;
61     myConsoleCommunication = consoleCommunication;
62     myConsoleCommunication.addCommunicationListener(this);
63   }
64
65   @Override
66   public void processLine(@NotNull final String text) {
67     processLine(text, false);
68   }
69
70   public void processLine(@NotNull final String text, boolean execAnyway) {
71     int indentBefore = myCurrentIndentSize;
72     if (text.isEmpty()) {
73       processOneLine(text);
74     }
75     else {
76       if (StringUtil.countNewLines(text.trim()) > 0) {
77         executeMultiLine(text);
78       }
79       else {
80         processOneLine(text);
81       }
82     }
83     if (execAnyway && myCurrentIndentSize > 0 && indentBefore == 0) { //if code was indented and we need to exec anyway
84       finishExecution();
85     }
86   }
87
88   private void executeMultiLine(@NotNull String text) {
89     if (myInputBuffer == null) {
90       myInputBuffer = new StringBuilder();
91     }
92
93     myInputBuffer.append(text);
94
95     final LanguageConsoleImpl console = myConsoleView.getConsole();
96     final Editor currentEditor = console.getConsoleEditor();
97
98     sendLineToConsole(new ConsoleCommunication.ConsoleCodeFragment(myInputBuffer.toString(), false), console, currentEditor);
99   }
100
101   private void processOneLine(String line) {
102     int indentSize = IndentHelperImpl.getIndent(getProject(), PythonFileType.INSTANCE, line, false);
103     line = StringUtil.trimTrailing(line);
104     if (StringUtil.isEmptyOrSpaces(line)) {
105       doProcessLine("\n");
106     }
107     else if (indentSize == 0 &&
108              indentSize < myCurrentIndentSize &&
109              !PyConsoleIndentUtil.shouldIndent(line) &&
110              !myConsoleCommunication.isWaitingForInput()) {
111       doProcessLine("\n");
112       doProcessLine(line);
113     }
114     else {
115       doProcessLine(line);
116     }
117   }
118
119   public void doProcessLine(final String line) {
120     final LanguageConsoleImpl console = myConsoleView.getConsole();
121     final Editor currentEditor = console.getConsoleEditor();
122
123     if (myInputBuffer == null) {
124       myInputBuffer = new StringBuilder();
125     }
126
127     if (!StringUtil.isEmptyOrSpaces(line)) {
128       myInputBuffer.append(line);
129       if (!line.endsWith("\n")) {
130         myInputBuffer.append("\n");
131       }
132     }
133
134     if (StringUtil.isEmptyOrSpaces(line) && StringUtil.isEmptyOrSpaces(myInputBuffer.toString())) {
135       myInputBuffer.append("");
136     }
137
138     // multiline strings handling
139     if (myInMultilineStringState != null) {
140       if (PyConsoleUtil.isDoubleQuoteMultilineStarts(line)) {
141         myInMultilineStringState = null;
142         // restore language
143         console.setLanguage(PythonLanguage.getInstance());
144         console.setPrompt(PyConsoleUtil.ORDINARY_PROMPT);
145       }
146       else {
147         return;
148       }
149     }
150     else {
151       if (PyConsoleUtil.isDoubleQuoteMultilineStarts(line)) {
152         myInMultilineStringState = PyConsoleUtil.DOUBLE_QUOTE_MULTILINE;
153       }
154       else if (PyConsoleUtil.isSingleQuoteMultilineStarts(line)) {
155         myInMultilineStringState = PyConsoleUtil.SINGLE_QUOTE_MULTILINE;
156       }
157       if (myInMultilineStringState != null) {
158         // change language
159         console.setLanguage(PlainTextLanguage.INSTANCE);
160         console.setPrompt(PyConsoleUtil.INDENT_PROMPT);
161         return;
162       }
163     }
164
165     // Process line continuation
166     if (line.endsWith("\\")) {
167       console.setPrompt(PyConsoleUtil.INDENT_PROMPT);
168       return;
169     }
170
171     if (!StringUtil.isEmptyOrSpaces(line)) {
172       int indent = IndentHelperImpl.getIndent(getProject(), PythonFileType.INSTANCE, line, false);
173       boolean flag = false;
174       if (PyConsoleIndentUtil.shouldIndent(line)) {
175         indent += getPythonIndent();
176         flag = true;
177       }
178       if ((myCurrentIndentSize > 0 && indent > 0) || flag) {
179         setCurrentIndentSize(indent);
180         indentEditor(currentEditor, indent);
181         more(console, currentEditor);
182
183         myConsoleCommunication.notifyCommandExecuted(true);
184         return;
185       }
186     }
187
188
189     sendLineToConsole(new ConsoleCommunication.ConsoleCodeFragment(myInputBuffer.toString(), true), console, currentEditor);
190   }
191
192   private void sendLineToConsole(@NotNull final ConsoleCommunication.ConsoleCodeFragment code,
193                                  @NotNull final LanguageConsoleImpl console,
194                                  @NotNull final Editor currentEditor) {
195     if (myConsoleCommunication != null) {
196       final boolean waitedForInputBefore = myConsoleCommunication.isWaitingForInput();
197       if (myConsoleCommunication.isWaitingForInput()) {
198         myInputBuffer.setLength(0);
199       }
200       else {
201         executingPrompt(console);
202       }
203       myConsoleCommunication.execInterpreter(code, new Function<InterpreterResponse, Object>() {
204         @Override
205         public Object fun(final InterpreterResponse interpreterResponse) {
206           // clear
207           myInputBuffer = null;
208           // Handle prompt
209           if (interpreterResponse.more) {
210             more(console, currentEditor);
211             if (myCurrentIndentSize == 0) {
212               // compute current indentation
213               setCurrentIndentSize(
214                 IndentHelperImpl.getIndent(getProject(), PythonFileType.INSTANCE, lastLine(code.getText()), false) + getPythonIndent());
215               // In this case we can insert indent automatically
216               UIUtil.invokeLaterIfNeeded(new Runnable() {
217                 @Override
218                 public void run() {
219                   indentEditor(currentEditor, myCurrentIndentSize);
220                 }
221               });
222             }
223           }
224           else {
225             if (!myConsoleCommunication.isWaitingForInput()) {
226               ordinaryPrompt(console, currentEditor);
227             }
228             setCurrentIndentSize(0);
229           }
230
231           return null;
232         }
233       });
234       // After requesting input we got no call back to change prompt, change it manually
235       if (waitedForInputBefore && !myConsoleCommunication.isWaitingForInput()) {
236         console.setPrompt(PyConsoleUtil.ORDINARY_PROMPT);
237         PyConsoleUtil.scrollDown(currentEditor);
238       }
239     }
240   }
241
242   private static String lastLine(@NotNull String text) {
243     String[] lines = StringUtil.splitByLinesDontTrim(text);
244     return lines[lines.length - 1];
245   }
246
247   private void ordinaryPrompt(LanguageConsoleImpl console, Editor currentEditor) {
248     if (!myConsoleCommunication.isExecuting()) {
249       if (!PyConsoleUtil.ORDINARY_PROMPT.equals(console.getPrompt())) {
250         console.setPrompt(PyConsoleUtil.ORDINARY_PROMPT);
251         PyConsoleUtil.scrollDown(currentEditor);
252       }
253     }
254     else {
255       executingPrompt(console);
256     }
257   }
258
259   private static void executingPrompt(LanguageConsoleImpl console) {
260     console.setPrompt(PyConsoleUtil.EXECUTING_PROMPT);
261   }
262
263   private static void more(LanguageConsoleImpl console, Editor currentEditor) {
264     if (!PyConsoleUtil.INDENT_PROMPT.equals(console.getPrompt())) {
265       console.setPrompt(PyConsoleUtil.INDENT_PROMPT);
266       PyConsoleUtil.scrollDown(currentEditor);
267     }
268   }
269
270   public static String getPrevCommandRunningMessage() {
271     return "Previous command is still running. Please wait or press Ctrl+C in console to interrupt.";
272   }
273
274   @Override
275   public void commandExecuted(boolean more) {
276     if (!more) {
277       final LanguageConsoleImpl console = myConsoleView.getConsole();
278       final Editor currentEditor = console.getConsoleEditor();
279
280       ordinaryPrompt(console, currentEditor);
281     }
282   }
283
284   @Override
285   public void inputRequested() {
286     final LanguageConsoleImpl console = myConsoleView.getConsole();
287     final Editor currentEditor = console.getConsoleEditor();
288
289     if (!PyConsoleUtil.INPUT_PROMPT.equals(console.getPrompt()) && !PyConsoleUtil.HELP_PROMPT.equals(console.getPrompt())) {
290       console.setPrompt(PyConsoleUtil.INPUT_PROMPT);
291       PyConsoleUtil.scrollDown(currentEditor);
292     }
293     setCurrentIndentSize(1);
294   }
295
296   @SuppressWarnings({"override", "deprecation"})
297   public void finishExecution() {
298     final LanguageConsoleImpl console = myConsoleView.getConsole();
299     final Editor currentEditor = console.getConsoleEditor();
300
301     if (myInputBuffer != null) {
302       processLine("\n");
303     }
304
305     cleanEditor(currentEditor);
306     //console.setPrompt(PyConsoleHighlightingUtil.ORDINARY_PROMPT);
307   }
308
309   public int getCurrentIndentSize() {
310     return myCurrentIndentSize;
311   }
312
313   public void setCurrentIndentSize(int currentIndentSize) {
314     myCurrentIndentSize = currentIndentSize;
315     VirtualFile file = getConsoleFile();
316     if (file != null) {
317       PyConsoleUtil.setCurrentIndentSize(file, currentIndentSize);
318     }
319   }
320
321   @Nullable
322   private VirtualFile getConsoleFile() {
323     if (myConsoleView != null) {
324       return myConsoleView.getConsole().getFile().getVirtualFile();
325     }
326     else {
327       return null;
328     }
329   }
330
331   public int getPythonIndent() {
332     return CodeStyleSettingsManager.getSettings(getProject()).getIndentSize(PythonFileType.INSTANCE);
333   }
334
335   private void indentEditor(final Editor editor, final int indentSize) {
336     new WriteCommandAction(getProject()) {
337       @Override
338       protected void run(@NotNull Result result) throws Throwable {
339         EditorModificationUtil.insertStringAtCaret(editor, IndentHelperImpl.fillIndent(getProject(), PythonFileType.INSTANCE, indentSize));
340       }
341     }.execute();
342   }
343
344   private void cleanEditor(final Editor editor) {
345     new WriteCommandAction(getProject()) {
346       @Override
347       protected void run(@NotNull Result result) throws Throwable {
348         editor.getDocument().setText("");
349       }
350     }.execute();
351   }
352
353   private Project getProject() {
354     return myConsoleView.getConsole().getProject();
355   }
356
357   public String getCantExecuteMessage() {
358     if (!isEnabled()) {
359       return getConsoleIsNotEnabledMessage();
360     }
361     else if (!canExecuteNow()) {
362       return getPrevCommandRunningMessage();
363     }
364     else {
365       return "Can't execute the command";
366     }
367   }
368
369   @Override
370   public void runExecuteAction(@NotNull LanguageConsoleView console) {
371     if (isEnabled()) {
372       if (!canExecuteNow()) {
373         HintManager.getInstance().showErrorHint(console.getConsole().getConsoleEditor(), getPrevCommandRunningMessage());
374       }
375       else {
376         doRunExecuteAction(console);
377       }
378     }
379     else {
380       HintManager.getInstance().showErrorHint(console.getConsole().getConsoleEditor(), getConsoleIsNotEnabledMessage());
381     }
382   }
383
384   private void doRunExecuteAction(LanguageConsoleView console) {
385     if (shouldCopyToHistory(console.getConsole())) {
386       copyToHistoryAndExecute(console);
387     }
388     else {
389       processLine(console.getConsole().getConsoleEditor().getDocument().getText());
390     }
391   }
392
393   private static boolean shouldCopyToHistory(@NotNull LanguageConsoleImpl console) {
394     return !PyConsoleUtil.isPagingPrompt(console.getPrompt());
395   }
396
397   private void copyToHistoryAndExecute(LanguageConsoleView console) {
398     super.runExecuteAction(console);
399   }
400
401   public boolean canExecuteNow() {
402     return !myConsoleCommunication.isExecuting() || myConsoleCommunication.isWaitingForInput();
403   }
404
405   protected String getConsoleIsNotEnabledMessage() {
406     return "Console is not enabled.";
407   }
408
409   protected void setEnabled(boolean flag) {
410     myEnabled = flag;
411   }
412
413   public boolean isEnabled() {
414     return myEnabled;
415   }
416 }