2 * Copyright 2000-2013 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.jetbrains.python.console;
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;
46 public class PydevConsoleExecuteActionHandler extends ProcessBackedConsoleExecuteActionHandler implements ConsoleCommunicationListener {
47 private final LanguageConsoleView myConsoleView;
49 private String myInMultilineStringState = null;
50 private StringBuilder myInputBuffer;
51 private int myCurrentIndentSize = 0;
53 private final ConsoleCommunication myConsoleCommunication;
54 private boolean myEnabled = false;
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);
66 public void processLine(@NotNull final String text) {
67 processLine(text, false);
70 public void processLine(@NotNull final String text, boolean execAnyway) {
71 int indentBefore = myCurrentIndentSize;
76 if (StringUtil.countNewLines(text.trim()) > 0) {
77 executeMultiLine(text);
83 if (execAnyway && myCurrentIndentSize > 0 && indentBefore == 0) { //if code was indented and we need to exec anyway
88 private void executeMultiLine(@NotNull String text) {
89 if (myInputBuffer == null) {
90 myInputBuffer = new StringBuilder();
93 myInputBuffer.append(text);
95 final LanguageConsoleImpl console = myConsoleView.getConsole();
96 final Editor currentEditor = console.getConsoleEditor();
98 sendLineToConsole(new ConsoleCommunication.ConsoleCodeFragment(myInputBuffer.toString(), false), console, currentEditor);
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)) {
107 else if (indentSize == 0 &&
108 indentSize < myCurrentIndentSize &&
109 !PyConsoleIndentUtil.shouldIndent(line) &&
110 !myConsoleCommunication.isWaitingForInput()) {
119 public void doProcessLine(final String line) {
120 final LanguageConsoleImpl console = myConsoleView.getConsole();
121 final Editor currentEditor = console.getConsoleEditor();
123 if (myInputBuffer == null) {
124 myInputBuffer = new StringBuilder();
127 if (!StringUtil.isEmptyOrSpaces(line)) {
128 myInputBuffer.append(line);
129 if (!line.endsWith("\n")) {
130 myInputBuffer.append("\n");
134 if (StringUtil.isEmptyOrSpaces(line) && StringUtil.isEmptyOrSpaces(myInputBuffer.toString())) {
135 myInputBuffer.append("");
138 // multiline strings handling
139 if (myInMultilineStringState != null) {
140 if (PyConsoleUtil.isDoubleQuoteMultilineStarts(line) || PyConsoleUtil.isSingleQuoteMultilineStarts(line)) {
141 myInMultilineStringState = null;
143 console.setLanguage(PythonLanguage.getInstance());
144 console.setPrompt(PyConsoleUtil.ORDINARY_PROMPT);
150 if (PyConsoleUtil.isDoubleQuoteMultilineStarts(line)) {
151 myInMultilineStringState = PyConsoleUtil.DOUBLE_QUOTE_MULTILINE;
153 else if (PyConsoleUtil.isSingleQuoteMultilineStarts(line)) {
154 myInMultilineStringState = PyConsoleUtil.SINGLE_QUOTE_MULTILINE;
156 if (myInMultilineStringState != null) {
158 console.setLanguage(PlainTextLanguage.INSTANCE);
159 console.setPrompt(PyConsoleUtil.INDENT_PROMPT);
164 // Process line continuation
165 if (line.endsWith("\\")) {
166 console.setPrompt(PyConsoleUtil.INDENT_PROMPT);
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();
177 if ((myCurrentIndentSize > 0 && indent > 0) || flag) {
178 setCurrentIndentSize(indent);
179 indentEditor(currentEditor, indent);
180 more(console, currentEditor);
182 myConsoleCommunication.notifyCommandExecuted(true);
188 sendLineToConsole(new ConsoleCommunication.ConsoleCodeFragment(myInputBuffer.toString(), true), console, currentEditor);
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);
200 executingPrompt(console);
202 myConsoleCommunication.execInterpreter(code, new Function<InterpreterResponse, Object>() {
204 public Object fun(final InterpreterResponse interpreterResponse) {
206 myInputBuffer = null;
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() {
218 indentEditor(currentEditor, myCurrentIndentSize);
224 if (!myConsoleCommunication.isWaitingForInput()) {
225 ordinaryPrompt(console, currentEditor);
227 setCurrentIndentSize(0);
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);
241 private static String lastLine(@NotNull String text) {
242 String[] lines = StringUtil.splitByLinesDontTrim(text);
243 return lines[lines.length - 1];
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);
254 executingPrompt(console);
258 private static void executingPrompt(LanguageConsoleImpl console) {
259 console.setPrompt(PyConsoleUtil.EXECUTING_PROMPT);
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);
269 public static String getPrevCommandRunningMessage() {
270 return "Previous command is still running. Please wait or press Ctrl+C in console to interrupt.";
274 public void commandExecuted(boolean more) {
276 final LanguageConsoleImpl console = myConsoleView.getConsole();
277 final Editor currentEditor = console.getConsoleEditor();
279 ordinaryPrompt(console, currentEditor);
284 public void inputRequested() {
285 final LanguageConsoleImpl console = myConsoleView.getConsole();
286 final Editor currentEditor = console.getConsoleEditor();
288 if (!PyConsoleUtil.INPUT_PROMPT.equals(console.getPrompt()) && !PyConsoleUtil.HELP_PROMPT.equals(console.getPrompt())) {
289 console.setPrompt(PyConsoleUtil.INPUT_PROMPT);
290 PyConsoleUtil.scrollDown(currentEditor);
292 setCurrentIndentSize(1);
295 @SuppressWarnings({"override", "deprecation"})
296 public void finishExecution() {
297 final LanguageConsoleImpl console = myConsoleView.getConsole();
298 final Editor currentEditor = console.getConsoleEditor();
300 if (myInputBuffer != null) {
304 cleanEditor(currentEditor);
305 //console.setPrompt(PyConsoleHighlightingUtil.ORDINARY_PROMPT);
308 public int getCurrentIndentSize() {
309 return myCurrentIndentSize;
312 public void setCurrentIndentSize(int currentIndentSize) {
313 myCurrentIndentSize = currentIndentSize;
314 VirtualFile file = getConsoleFile();
316 PyConsoleUtil.setCurrentIndentSize(file, currentIndentSize);
321 private VirtualFile getConsoleFile() {
322 if (myConsoleView != null) {
323 return myConsoleView.getConsole().getFile().getVirtualFile();
330 public int getPythonIndent() {
331 return CodeStyleSettingsManager.getSettings(getProject()).getIndentSize(PythonFileType.INSTANCE);
334 private void indentEditor(final Editor editor, final int indentSize) {
335 new WriteCommandAction(getProject()) {
337 protected void run(@NotNull Result result) throws Throwable {
338 EditorModificationUtil.insertStringAtCaret(editor, IndentHelperImpl.fillIndent(getProject(), PythonFileType.INSTANCE, indentSize));
343 private void cleanEditor(final Editor editor) {
344 new WriteCommandAction(getProject()) {
346 protected void run(@NotNull Result result) throws Throwable {
347 editor.getDocument().setText("");
352 private Project getProject() {
353 return myConsoleView.getConsole().getProject();
356 public String getCantExecuteMessage() {
358 return getConsoleIsNotEnabledMessage();
360 else if (!canExecuteNow()) {
361 return getPrevCommandRunningMessage();
364 return "Can't execute the command";
369 public void runExecuteAction(@NotNull LanguageConsoleView console) {
371 if (!canExecuteNow()) {
372 HintManager.getInstance().showErrorHint(console.getConsole().getConsoleEditor(), getPrevCommandRunningMessage());
375 doRunExecuteAction(console);
379 HintManager.getInstance().showErrorHint(console.getConsole().getConsoleEditor(), getConsoleIsNotEnabledMessage());
383 private void doRunExecuteAction(LanguageConsoleView console) {
384 if (shouldCopyToHistory(console.getConsole())) {
385 copyToHistoryAndExecute(console);
388 processLine(console.getConsole().getConsoleEditor().getDocument().getText());
392 private static boolean shouldCopyToHistory(@NotNull LanguageConsoleImpl console) {
393 return !PyConsoleUtil.isPagingPrompt(console.getPrompt());
396 private void copyToHistoryAndExecute(LanguageConsoleView console) {
397 super.runExecuteAction(console);
400 public boolean canExecuteNow() {
401 return !myConsoleCommunication.isExecuting() || myConsoleCommunication.isWaitingForInput();
404 protected String getConsoleIsNotEnabledMessage() {
405 return "Console is not enabled.";
408 protected void setEnabled(boolean flag) {
412 public boolean isEnabled() {