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)) {
141 myInMultilineStringState = null;
143 console.setLanguage(PythonLanguage.getInstance());
144 console.setPrompt(PyConsoleUtil.ORDINARY_PROMPT);
151 if (PyConsoleUtil.isDoubleQuoteMultilineStarts(line)) {
152 myInMultilineStringState = PyConsoleUtil.DOUBLE_QUOTE_MULTILINE;
154 else if (PyConsoleUtil.isSingleQuoteMultilineStarts(line)) {
155 myInMultilineStringState = PyConsoleUtil.SINGLE_QUOTE_MULTILINE;
157 if (myInMultilineStringState != null) {
159 console.setLanguage(PlainTextLanguage.INSTANCE);
160 console.setPrompt(PyConsoleUtil.INDENT_PROMPT);
165 // Process line continuation
166 if (line.endsWith("\\")) {
167 console.setPrompt(PyConsoleUtil.INDENT_PROMPT);
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();
178 if ((myCurrentIndentSize > 0 && indent > 0) || flag) {
179 setCurrentIndentSize(indent);
180 indentEditor(currentEditor, indent);
181 more(console, currentEditor);
183 myConsoleCommunication.notifyCommandExecuted(true);
189 sendLineToConsole(new ConsoleCommunication.ConsoleCodeFragment(myInputBuffer.toString(), true), console, currentEditor);
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);
201 executingPrompt(console);
203 myConsoleCommunication.execInterpreter(code, new Function<InterpreterResponse, Object>() {
205 public Object fun(final InterpreterResponse interpreterResponse) {
207 myInputBuffer = null;
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() {
219 indentEditor(currentEditor, myCurrentIndentSize);
225 if (!myConsoleCommunication.isWaitingForInput()) {
226 ordinaryPrompt(console, currentEditor);
228 setCurrentIndentSize(0);
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);
242 private static String lastLine(@NotNull String text) {
243 String[] lines = StringUtil.splitByLinesDontTrim(text);
244 return lines[lines.length - 1];
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);
255 executingPrompt(console);
259 private static void executingPrompt(LanguageConsoleImpl console) {
260 console.setPrompt(PyConsoleUtil.EXECUTING_PROMPT);
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);
270 public static String getPrevCommandRunningMessage() {
271 return "Previous command is still running. Please wait or press Ctrl+C in console to interrupt.";
275 public void commandExecuted(boolean more) {
277 final LanguageConsoleImpl console = myConsoleView.getConsole();
278 final Editor currentEditor = console.getConsoleEditor();
280 ordinaryPrompt(console, currentEditor);
285 public void inputRequested() {
286 final LanguageConsoleImpl console = myConsoleView.getConsole();
287 final Editor currentEditor = console.getConsoleEditor();
289 if (!PyConsoleUtil.INPUT_PROMPT.equals(console.getPrompt()) && !PyConsoleUtil.HELP_PROMPT.equals(console.getPrompt())) {
290 console.setPrompt(PyConsoleUtil.INPUT_PROMPT);
291 PyConsoleUtil.scrollDown(currentEditor);
293 setCurrentIndentSize(1);
296 @SuppressWarnings({"override", "deprecation"})
297 public void finishExecution() {
298 final LanguageConsoleImpl console = myConsoleView.getConsole();
299 final Editor currentEditor = console.getConsoleEditor();
301 if (myInputBuffer != null) {
305 cleanEditor(currentEditor);
306 //console.setPrompt(PyConsoleHighlightingUtil.ORDINARY_PROMPT);
309 public int getCurrentIndentSize() {
310 return myCurrentIndentSize;
313 public void setCurrentIndentSize(int currentIndentSize) {
314 myCurrentIndentSize = currentIndentSize;
315 VirtualFile file = getConsoleFile();
317 PyConsoleUtil.setCurrentIndentSize(file, currentIndentSize);
322 private VirtualFile getConsoleFile() {
323 if (myConsoleView != null) {
324 return myConsoleView.getConsole().getFile().getVirtualFile();
331 public int getPythonIndent() {
332 return CodeStyleSettingsManager.getSettings(getProject()).getIndentSize(PythonFileType.INSTANCE);
335 private void indentEditor(final Editor editor, final int indentSize) {
336 new WriteCommandAction(getProject()) {
338 protected void run(@NotNull Result result) throws Throwable {
339 EditorModificationUtil.insertStringAtCaret(editor, IndentHelperImpl.fillIndent(getProject(), PythonFileType.INSTANCE, indentSize));
344 private void cleanEditor(final Editor editor) {
345 new WriteCommandAction(getProject()) {
347 protected void run(@NotNull Result result) throws Throwable {
348 editor.getDocument().setText("");
353 private Project getProject() {
354 return myConsoleView.getConsole().getProject();
357 public String getCantExecuteMessage() {
359 return getConsoleIsNotEnabledMessage();
361 else if (!canExecuteNow()) {
362 return getPrevCommandRunningMessage();
365 return "Can't execute the command";
370 public void runExecuteAction(@NotNull LanguageConsoleView console) {
372 if (!canExecuteNow()) {
373 HintManager.getInstance().showErrorHint(console.getConsole().getConsoleEditor(), getPrevCommandRunningMessage());
376 doRunExecuteAction(console);
380 HintManager.getInstance().showErrorHint(console.getConsole().getConsoleEditor(), getConsoleIsNotEnabledMessage());
384 private void doRunExecuteAction(LanguageConsoleView console) {
385 if (shouldCopyToHistory(console.getConsole())) {
386 copyToHistoryAndExecute(console);
389 processLine(console.getConsole().getConsoleEditor().getDocument().getText());
393 private static boolean shouldCopyToHistory(@NotNull LanguageConsoleImpl console) {
394 return !PyConsoleUtil.isPagingPrompt(console.getPrompt());
397 private void copyToHistoryAndExecute(LanguageConsoleView console) {
398 super.runExecuteAction(console);
401 public boolean canExecuteNow() {
402 return !myConsoleCommunication.isExecuting() || myConsoleCommunication.isWaitingForInput();
405 protected String getConsoleIsNotEnabledMessage() {
406 return "Console is not enabled.";
409 protected void setEnabled(boolean flag) {
413 public boolean isEnabled() {