Merge branch 'PY-10179' of https://github.com/Amarchuk/intellij-community into Amarch...
[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) || PyConsoleUtil.isSingleQuoteMultilineStarts(line)) {
141         myInMultilineStringState = null;
142         // restore language
143         console.setLanguage(PythonLanguage.getInstance());
144         console.setPrompt(PyConsoleUtil.ORDINARY_PROMPT);
145       } else {
146         return;
147       }
148     }
149     else {
150       if (PyConsoleUtil.isDoubleQuoteMultilineStarts(line)) {
151         myInMultilineStringState = PyConsoleUtil.DOUBLE_QUOTE_MULTILINE;
152       }
153       else if (PyConsoleUtil.isSingleQuoteMultilineStarts(line)) {
154         myInMultilineStringState = PyConsoleUtil.SINGLE_QUOTE_MULTILINE;
155       }
156       if (myInMultilineStringState != null) {
157         // change language
158         console.setLanguage(PlainTextLanguage.INSTANCE);
159         console.setPrompt(PyConsoleUtil.INDENT_PROMPT);
160         return;
161       }
162     }
163
164     // Process line continuation
165     if (line.endsWith("\\")) {
166       console.setPrompt(PyConsoleUtil.INDENT_PROMPT);
167       return;
168     }
169
170     if (!StringUtil.isEmptyOrSpaces(line)) {
171       int indent = IndentHelperImpl.getIndent(getProject(), PythonFileType.INSTANCE, line, false);
172       boolean flag = false;
173       if (PyConsoleIndentUtil.shouldIndent(line)) {
174         indent += getPythonIndent();
175         flag = true;
176       }
177       if ((myCurrentIndentSize > 0 && indent > 0) || flag) {
178         setCurrentIndentSize(indent);
179         indentEditor(currentEditor, indent);
180         more(console, currentEditor);
181
182         myConsoleCommunication.notifyCommandExecuted(true);
183         return;
184       }
185     }
186
187
188     sendLineToConsole(new ConsoleCommunication.ConsoleCodeFragment(myInputBuffer.toString(), true), console, currentEditor);
189   }
190
191   private void sendLineToConsole(@NotNull final ConsoleCommunication.ConsoleCodeFragment code,
192                                  @NotNull final LanguageConsoleImpl console,
193                                  @NotNull final Editor currentEditor) {
194     if (myConsoleCommunication != null) {
195       final boolean waitedForInputBefore = myConsoleCommunication.isWaitingForInput();
196       if (myConsoleCommunication.isWaitingForInput()) {
197         myInputBuffer.setLength(0);
198       }
199       else {
200         executingPrompt(console);
201       }
202       myConsoleCommunication.execInterpreter(code, new Function<InterpreterResponse, Object>() {
203         @Override
204         public Object fun(final InterpreterResponse interpreterResponse) {
205           // clear
206           myInputBuffer = null;
207           // Handle prompt
208           if (interpreterResponse.more) {
209             more(console, currentEditor);
210             if (myCurrentIndentSize == 0) {
211               // compute current indentation
212               setCurrentIndentSize(
213                 IndentHelperImpl.getIndent(getProject(), PythonFileType.INSTANCE, lastLine(code.getText()), false) + getPythonIndent());
214               // In this case we can insert indent automatically
215               UIUtil.invokeLaterIfNeeded(new Runnable() {
216                 @Override
217                 public void run() {
218                   indentEditor(currentEditor, myCurrentIndentSize);
219                 }
220               });
221             }
222           }
223           else {
224             if (!myConsoleCommunication.isWaitingForInput()) {
225               ordinaryPrompt(console, currentEditor);
226             }
227             setCurrentIndentSize(0);
228           }
229
230           return null;
231         }
232       });
233       // After requesting input we got no call back to change prompt, change it manually
234       if (waitedForInputBefore && !myConsoleCommunication.isWaitingForInput()) {
235         console.setPrompt(PyConsoleUtil.ORDINARY_PROMPT);
236         PyConsoleUtil.scrollDown(currentEditor);
237       }
238     }
239   }
240
241   private static String lastLine(@NotNull String text) {
242     String[] lines = StringUtil.splitByLinesDontTrim(text);
243     return lines[lines.length - 1];
244   }
245
246   private void ordinaryPrompt(LanguageConsoleImpl console, Editor currentEditor) {
247     if (!myConsoleCommunication.isExecuting()) {
248       if (!PyConsoleUtil.ORDINARY_PROMPT.equals(console.getPrompt())) {
249         console.setPrompt(PyConsoleUtil.ORDINARY_PROMPT);
250         PyConsoleUtil.scrollDown(currentEditor);
251       }
252     }
253     else {
254       executingPrompt(console);
255     }
256   }
257
258   private static void executingPrompt(LanguageConsoleImpl console) {
259     console.setPrompt(PyConsoleUtil.EXECUTING_PROMPT);
260   }
261
262   private static void more(LanguageConsoleImpl console, Editor currentEditor) {
263     if (!PyConsoleUtil.INDENT_PROMPT.equals(console.getPrompt())) {
264       console.setPrompt(PyConsoleUtil.INDENT_PROMPT);
265       PyConsoleUtil.scrollDown(currentEditor);
266     }
267   }
268
269   public static String getPrevCommandRunningMessage() {
270     return "Previous command is still running. Please wait or press Ctrl+C in console to interrupt.";
271   }
272
273   @Override
274   public void commandExecuted(boolean more) {
275     if (!more) {
276       final LanguageConsoleImpl console = myConsoleView.getConsole();
277       final Editor currentEditor = console.getConsoleEditor();
278
279       ordinaryPrompt(console, currentEditor);
280     }
281   }
282
283   @Override
284   public void inputRequested() {
285     final LanguageConsoleImpl console = myConsoleView.getConsole();
286     final Editor currentEditor = console.getConsoleEditor();
287
288     if (!PyConsoleUtil.INPUT_PROMPT.equals(console.getPrompt()) && !PyConsoleUtil.HELP_PROMPT.equals(console.getPrompt())) {
289       console.setPrompt(PyConsoleUtil.INPUT_PROMPT);
290       PyConsoleUtil.scrollDown(currentEditor);
291     }
292     setCurrentIndentSize(1);
293   }
294
295   @SuppressWarnings({"override", "deprecation"})
296   public void finishExecution() {
297     final LanguageConsoleImpl console = myConsoleView.getConsole();
298     final Editor currentEditor = console.getConsoleEditor();
299
300     if (myInputBuffer != null) {
301       processLine("\n");
302     }
303
304     cleanEditor(currentEditor);
305     //console.setPrompt(PyConsoleHighlightingUtil.ORDINARY_PROMPT);
306   }
307
308   public int getCurrentIndentSize() {
309     return myCurrentIndentSize;
310   }
311
312   public void setCurrentIndentSize(int currentIndentSize) {
313     myCurrentIndentSize = currentIndentSize;
314     VirtualFile file = getConsoleFile();
315     if (file != null) {
316       PyConsoleUtil.setCurrentIndentSize(file, currentIndentSize);
317     }
318   }
319
320   @Nullable
321   private VirtualFile getConsoleFile() {
322     if (myConsoleView != null) {
323       return myConsoleView.getConsole().getFile().getVirtualFile();
324     }
325     else {
326       return null;
327     }
328   }
329
330   public int getPythonIndent() {
331     return CodeStyleSettingsManager.getSettings(getProject()).getIndentSize(PythonFileType.INSTANCE);
332   }
333
334   private void indentEditor(final Editor editor, final int indentSize) {
335     new WriteCommandAction(getProject()) {
336       @Override
337       protected void run(@NotNull Result result) throws Throwable {
338         EditorModificationUtil.insertStringAtCaret(editor, IndentHelperImpl.fillIndent(getProject(), PythonFileType.INSTANCE, indentSize));
339       }
340     }.execute();
341   }
342
343   private void cleanEditor(final Editor editor) {
344     new WriteCommandAction(getProject()) {
345       @Override
346       protected void run(@NotNull Result result) throws Throwable {
347         editor.getDocument().setText("");
348       }
349     }.execute();
350   }
351
352   private Project getProject() {
353     return myConsoleView.getConsole().getProject();
354   }
355
356   public String getCantExecuteMessage() {
357     if (!isEnabled()) {
358       return getConsoleIsNotEnabledMessage();
359     }
360     else if (!canExecuteNow()) {
361       return getPrevCommandRunningMessage();
362     }
363     else {
364       return "Can't execute the command";
365     }
366   }
367
368   @Override
369   public void runExecuteAction(@NotNull LanguageConsoleView console) {
370     if (isEnabled()) {
371       if (!canExecuteNow()) {
372         HintManager.getInstance().showErrorHint(console.getConsole().getConsoleEditor(), getPrevCommandRunningMessage());
373       }
374       else {
375         doRunExecuteAction(console);
376       }
377     }
378     else {
379       HintManager.getInstance().showErrorHint(console.getConsole().getConsoleEditor(), getConsoleIsNotEnabledMessage());
380     }
381   }
382
383   private void doRunExecuteAction(LanguageConsoleView console) {
384     if (shouldCopyToHistory(console.getConsole())) {
385       copyToHistoryAndExecute(console);
386     }
387     else {
388       processLine(console.getConsole().getConsoleEditor().getDocument().getText());
389     }
390   }
391
392   private static boolean shouldCopyToHistory(@NotNull LanguageConsoleImpl console) {
393     return !PyConsoleUtil.isPagingPrompt(console.getPrompt());
394   }
395
396   private void copyToHistoryAndExecute(LanguageConsoleView console) {
397     super.runExecuteAction(console);
398   }
399
400   public boolean canExecuteNow() {
401     return !myConsoleCommunication.isExecuting() || myConsoleCommunication.isWaitingForInput();
402   }
403
404   protected String getConsoleIsNotEnabledMessage() {
405     return "Console is not enabled.";
406   }
407
408   protected void setEnabled(boolean flag) {
409     myEnabled = flag;
410   }
411
412   public boolean isEnabled() {
413     return myEnabled;
414   }
415 }