| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.jetbrains.python.console; |
| |
| import com.intellij.codeInsight.hint.HintManager; |
| import com.intellij.execution.console.LanguageConsoleImpl; |
| import com.intellij.execution.console.LanguageConsoleView; |
| import com.intellij.execution.console.ProcessBackedConsoleExecuteActionHandler; |
| import com.intellij.execution.process.ProcessHandler; |
| import com.intellij.execution.ui.ConsoleViewContentType; |
| import com.intellij.openapi.application.Result; |
| import com.intellij.openapi.command.WriteCommandAction; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.EditorModificationUtil; |
| import com.intellij.openapi.editor.markup.TextAttributes; |
| import com.intellij.openapi.fileTypes.PlainTextLanguage; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.codeStyle.CodeStyleSettingsManager; |
| import com.intellij.psi.impl.source.codeStyle.IndentHelperImpl; |
| import com.intellij.util.Function; |
| import com.intellij.util.ui.UIUtil; |
| import com.jetbrains.python.PythonFileType; |
| import com.jetbrains.python.PythonLanguage; |
| import com.jetbrains.python.console.pydev.ConsoleCommunication; |
| import com.jetbrains.python.console.pydev.ConsoleCommunicationListener; |
| import com.jetbrains.python.console.pydev.InterpreterResponse; |
| import org.intellij.lang.annotations.JdkConstants; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.awt.*; |
| |
| /** |
| * @author traff |
| */ |
| public class PydevConsoleExecuteActionHandler extends ProcessBackedConsoleExecuteActionHandler implements ConsoleCommunicationListener { |
| private final LanguageConsoleView myConsoleView; |
| |
| private String myInMultilineStringState = null; |
| private StringBuilder myInputBuffer; |
| private int myCurrentIndentSize = 0; |
| |
| private final ConsoleCommunication myConsoleCommunication; |
| private boolean myEnabled = false; |
| |
| private int myIpythonInputPromptCount = 1; |
| |
| public PydevConsoleExecuteActionHandler(LanguageConsoleView consoleView, |
| ProcessHandler processHandler, |
| ConsoleCommunication consoleCommunication) { |
| super(processHandler, false); |
| myConsoleView = consoleView; |
| myConsoleCommunication = consoleCommunication; |
| myConsoleCommunication.addCommunicationListener(this); |
| } |
| |
| @Override |
| public void processLine(@NotNull final String text) { |
| processLine(text, false); |
| } |
| |
| public void processLine(@NotNull final String text, boolean execAnyway) { |
| int indentBefore = myCurrentIndentSize; |
| if (text.isEmpty()) { |
| processOneLine(text); |
| } |
| else { |
| if (StringUtil.countNewLines(text.trim()) > 0) { |
| executeMultiLine(text); |
| } |
| else { |
| processOneLine(text); |
| } |
| } |
| if (execAnyway && myCurrentIndentSize > 0 && indentBefore == 0) { //if code was indented and we need to exec anyway |
| finishExecution(); |
| } |
| } |
| |
| private void executeMultiLine(@NotNull String text) { |
| if (myInputBuffer == null) { |
| myInputBuffer = new StringBuilder(); |
| } |
| |
| myInputBuffer.append(text); |
| |
| final LanguageConsoleImpl console = myConsoleView.getConsole(); |
| final Editor currentEditor = console.getConsoleEditor(); |
| |
| sendLineToConsole(new ConsoleCommunication.ConsoleCodeFragment(myInputBuffer.toString(), false), console, currentEditor); |
| } |
| |
| private void processOneLine(String line) { |
| int indentSize = IndentHelperImpl.getIndent(getProject(), PythonFileType.INSTANCE, line, false); |
| line = StringUtil.trimTrailing(line); |
| if (StringUtil.isEmptyOrSpaces(line)) { |
| doProcessLine("\n"); |
| } |
| else if (indentSize == 0 && |
| indentSize < myCurrentIndentSize && |
| !PyConsoleIndentUtil.shouldIndent(line) && |
| !myConsoleCommunication.isWaitingForInput()) { |
| doProcessLine("\n"); |
| doProcessLine(line); |
| } |
| else { |
| doProcessLine(line); |
| } |
| } |
| |
| public void doProcessLine(final String line) { |
| final LanguageConsoleImpl console = myConsoleView.getConsole(); |
| final Editor currentEditor = console.getConsoleEditor(); |
| |
| if (myInputBuffer == null) { |
| myInputBuffer = new StringBuilder(); |
| } |
| |
| if (!StringUtil.isEmptyOrSpaces(line)) { |
| myInputBuffer.append(line); |
| if (!line.endsWith("\n")) { |
| myInputBuffer.append("\n"); |
| } |
| } |
| |
| if (StringUtil.isEmptyOrSpaces(line) && StringUtil.isEmptyOrSpaces(myInputBuffer.toString())) { |
| myInputBuffer.append(""); |
| } |
| |
| // multiline strings handling |
| if (myInMultilineStringState != null) { |
| if (PyConsoleUtil.isDoubleQuoteMultilineStarts(line) || PyConsoleUtil.isSingleQuoteMultilineStarts(line)) { |
| myInMultilineStringState = null; |
| // restore language |
| console.setLanguage(PythonLanguage.getInstance()); |
| console.setPrompt(PyConsoleUtil.ORDINARY_PROMPT); |
| } else { |
| if(line.equals("\n")) { |
| myInputBuffer.append("\n"); |
| } |
| return; |
| } |
| } |
| else { |
| if (PyConsoleUtil.isDoubleQuoteMultilineStarts(line)) { |
| myInMultilineStringState = PyConsoleUtil.DOUBLE_QUOTE_MULTILINE; |
| } |
| else if (PyConsoleUtil.isSingleQuoteMultilineStarts(line)) { |
| myInMultilineStringState = PyConsoleUtil.SINGLE_QUOTE_MULTILINE; |
| } |
| if (myInMultilineStringState != null) { |
| // change language |
| console.setLanguage(PlainTextLanguage.INSTANCE); |
| console.setPrompt(PyConsoleUtil.INDENT_PROMPT); |
| return; |
| } |
| } |
| |
| // Process line continuation |
| if (line.endsWith("\\")) { |
| console.setPrompt(PyConsoleUtil.INDENT_PROMPT); |
| return; |
| } |
| |
| if (!StringUtil.isEmptyOrSpaces(line)) { |
| int indent = IndentHelperImpl.getIndent(getProject(), PythonFileType.INSTANCE, line, false); |
| boolean flag = false; |
| if (PyConsoleIndentUtil.shouldIndent(line)) { |
| indent += getPythonIndent(); |
| flag = true; |
| } |
| if ((myCurrentIndentSize > 0 && indent > 0) || flag) { |
| setCurrentIndentSize(indent); |
| indentEditor(currentEditor, indent); |
| more(console, currentEditor); |
| |
| myConsoleCommunication.notifyCommandExecuted(true); |
| return; |
| } |
| } |
| |
| |
| sendLineToConsole(new ConsoleCommunication.ConsoleCodeFragment(myInputBuffer.toString(), true), console, currentEditor); |
| } |
| |
| private void sendLineToConsole(@NotNull final ConsoleCommunication.ConsoleCodeFragment code, |
| @NotNull final LanguageConsoleImpl console, |
| @NotNull final Editor currentEditor) { |
| if(!StringUtil.isEmptyOrSpaces(code.getText())) { |
| myIpythonInputPromptCount+=1; |
| } |
| if (myConsoleCommunication != null) { |
| final boolean waitedForInputBefore = myConsoleCommunication.isWaitingForInput(); |
| if (myConsoleCommunication.isWaitingForInput()) { |
| myInputBuffer.setLength(0); |
| } |
| else { |
| executingPrompt(console); |
| } |
| myConsoleCommunication.execInterpreter(code, new Function<InterpreterResponse, Object>() { |
| @Override |
| public Object fun(final InterpreterResponse interpreterResponse) { |
| // clear |
| myInputBuffer = null; |
| // Handle prompt |
| if (interpreterResponse.more) { |
| more(console, currentEditor); |
| if (myCurrentIndentSize == 0) { |
| // compute current indentation |
| setCurrentIndentSize( |
| IndentHelperImpl.getIndent(getProject(), PythonFileType.INSTANCE, lastLine(code.getText()), false) + getPythonIndent()); |
| // In this case we can insert indent automatically |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| indentEditor(currentEditor, myCurrentIndentSize); |
| } |
| }); |
| } |
| } |
| else { |
| if (!myConsoleCommunication.isWaitingForInput()) { |
| inPrompt(console, currentEditor); |
| } |
| setCurrentIndentSize(0); |
| } |
| |
| return null; |
| } |
| }); |
| // After requesting input we got no call back to change prompt, change it manually |
| if (waitedForInputBefore && !myConsoleCommunication.isWaitingForInput()) { |
| myIpythonInputPromptCount-=1; |
| inPrompt(console, currentEditor); |
| setCurrentIndentSize(0); |
| } |
| } |
| } |
| |
| private static String lastLine(@NotNull String text) { |
| String[] lines = StringUtil.splitByLinesDontTrim(text); |
| return lines[lines.length - 1]; |
| } |
| |
| private void inPrompt(LanguageConsoleImpl console, Editor currentEditor){ |
| if(ipythonEnabled(console)){ |
| ipythonInPrompt(console, currentEditor); |
| } else { |
| ordinaryPrompt(console, currentEditor); |
| } |
| } |
| |
| private void ordinaryPrompt(LanguageConsoleImpl console, Editor currentEditor) { |
| if (!myConsoleCommunication.isExecuting()) { |
| if (!PyConsoleUtil.ORDINARY_PROMPT.equals(console.getPrompt())) { |
| console.setPrompt(PyConsoleUtil.ORDINARY_PROMPT); |
| PyConsoleUtil.scrollDown(currentEditor); |
| } |
| } |
| else { |
| executingPrompt(console); |
| } |
| } |
| |
| private boolean ipythonEnabled(LanguageConsoleImpl console){ |
| return console.getFile().getVirtualFile() != null ? |
| PyConsoleUtil.getOrCreateIPythonData(console.getFile().getVirtualFile()).isIPythonEnabled() : false; |
| } |
| |
| private void ipythonInPrompt(LanguageConsoleImpl console, Editor currentEditor){ |
| TextAttributes attributes = ConsoleViewContentType.USER_INPUT.getAttributes(); |
| attributes.setFontType(Font.PLAIN); |
| console.setPromptAttributes(attributes); |
| console.setPrompt("In[" + myIpythonInputPromptCount + "]:"); |
| PyConsoleUtil.scrollDown(currentEditor); |
| } |
| |
| private static void executingPrompt(LanguageConsoleImpl console) { |
| console.setPrompt(PyConsoleUtil.EXECUTING_PROMPT); |
| } |
| |
| private static void more(LanguageConsoleImpl console, Editor currentEditor) { |
| if (!PyConsoleUtil.INDENT_PROMPT.equals(console.getPrompt())) { |
| console.setPrompt(PyConsoleUtil.INDENT_PROMPT); |
| PyConsoleUtil.scrollDown(currentEditor); |
| } |
| } |
| |
| public static String getPrevCommandRunningMessage() { |
| return "Previous command is still running. Please wait or press Ctrl+C in console to interrupt."; |
| } |
| |
| @Override |
| public void commandExecuted(boolean more) { |
| if (!more) { |
| final LanguageConsoleImpl console = myConsoleView.getConsole(); |
| final Editor currentEditor = console.getConsoleEditor(); |
| |
| if(!ipythonEnabled(console)){ |
| ordinaryPrompt(console, currentEditor); |
| } |
| } |
| } |
| |
| @Override |
| public void inputRequested() { |
| final LanguageConsoleImpl console = myConsoleView.getConsole(); |
| final Editor currentEditor = console.getConsoleEditor(); |
| |
| if (!PyConsoleUtil.INPUT_PROMPT.equals(console.getPrompt()) && !PyConsoleUtil.HELP_PROMPT.equals(console.getPrompt())) { |
| console.setPrompt(PyConsoleUtil.INPUT_PROMPT); |
| PyConsoleUtil.scrollDown(currentEditor); |
| } |
| setCurrentIndentSize(1); |
| } |
| |
| public void finishExecution() { |
| final LanguageConsoleImpl console = myConsoleView.getConsole(); |
| final Editor currentEditor = console.getConsoleEditor(); |
| |
| if (myInputBuffer != null) { |
| processLine("\n"); |
| } |
| |
| cleanEditor(currentEditor); |
| //console.setPrompt(PyConsoleHighlightingUtil.ORDINARY_PROMPT); |
| } |
| |
| public int getCurrentIndentSize() { |
| return myCurrentIndentSize; |
| } |
| |
| public void setCurrentIndentSize(int currentIndentSize) { |
| myCurrentIndentSize = currentIndentSize; |
| VirtualFile file = getConsoleFile(); |
| if (file != null) { |
| PyConsoleUtil.setCurrentIndentSize(file, currentIndentSize); |
| } |
| } |
| |
| @Nullable |
| private VirtualFile getConsoleFile() { |
| if (myConsoleView != null) { |
| return myConsoleView.getConsole().getFile().getVirtualFile(); |
| } |
| else { |
| return null; |
| } |
| } |
| |
| public int getPythonIndent() { |
| return CodeStyleSettingsManager.getSettings(getProject()).getIndentSize(PythonFileType.INSTANCE); |
| } |
| |
| private void indentEditor(final Editor editor, final int indentSize) { |
| new WriteCommandAction(getProject()) { |
| @Override |
| protected void run(@NotNull Result result) throws Throwable { |
| EditorModificationUtil.insertStringAtCaret(editor, IndentHelperImpl.fillIndent(getProject(), PythonFileType.INSTANCE, indentSize)); |
| } |
| }.execute(); |
| } |
| |
| private void cleanEditor(final Editor editor) { |
| new WriteCommandAction(getProject()) { |
| @Override |
| protected void run(@NotNull Result result) throws Throwable { |
| editor.getDocument().setText(""); |
| } |
| }.execute(); |
| } |
| |
| private Project getProject() { |
| return myConsoleView.getConsole().getProject(); |
| } |
| |
| public String getCantExecuteMessage() { |
| if (!isEnabled()) { |
| return getConsoleIsNotEnabledMessage(); |
| } |
| else if (!canExecuteNow()) { |
| return getPrevCommandRunningMessage(); |
| } |
| else { |
| return "Can't execute the command"; |
| } |
| } |
| |
| @Override |
| public void runExecuteAction(@NotNull LanguageConsoleView console) { |
| if (isEnabled()) { |
| if (!canExecuteNow()) { |
| HintManager.getInstance().showErrorHint(console.getConsole().getConsoleEditor(), getPrevCommandRunningMessage()); |
| } |
| else { |
| doRunExecuteAction(console); |
| } |
| } |
| else { |
| HintManager.getInstance().showErrorHint(console.getConsole().getConsoleEditor(), getConsoleIsNotEnabledMessage()); |
| } |
| } |
| |
| private void doRunExecuteAction(LanguageConsoleView console) { |
| if (shouldCopyToHistory(console.getConsole())) { |
| copyToHistoryAndExecute(console); |
| } |
| else { |
| processLine(console.getConsole().getConsoleEditor().getDocument().getText()); |
| } |
| } |
| |
| private static boolean shouldCopyToHistory(@NotNull LanguageConsoleImpl console) { |
| return !PyConsoleUtil.isPagingPrompt(console.getPrompt()); |
| } |
| |
| private void copyToHistoryAndExecute(LanguageConsoleView console) { |
| super.runExecuteAction(console); |
| } |
| |
| public boolean canExecuteNow() { |
| return !myConsoleCommunication.isExecuting() || myConsoleCommunication.isWaitingForInput(); |
| } |
| |
| protected String getConsoleIsNotEnabledMessage() { |
| return "Console is not enabled."; |
| } |
| |
| protected void setEnabled(boolean flag) { |
| myEnabled = flag; |
| } |
| |
| public boolean isEnabled() { |
| return myEnabled; |
| } |
| } |