Option to run python process in a terminal emulation mode (PY-22487)
authorDmitry Trofimov <dmitry.trofimov@jetbrains.com>
Wed, 1 Feb 2017 09:54:38 +0000 (10:54 +0100)
committerDmitry Trofimov <dmitry.trofimov@jetbrains.com>
Wed, 1 Feb 2017 11:30:54 +0000 (12:30 +0100)
platform/platform-impl/src/com/intellij/terminal/JBTerminalPanel.java
platform/platform-impl/src/com/intellij/terminal/JBTerminalWidget.java
platform/platform-impl/src/com/intellij/terminal/ProcessHandlerTtyConnector.java [new file with mode: 0644]
platform/platform-impl/src/com/intellij/terminal/TerminalExecutionConsole.java [new file with mode: 0644]
python/openapi/src/com/jetbrains/python/run/PythonRunConfigurationParams.java
python/src/com/jetbrains/python/run/PythonCommandLineState.java
python/src/com/jetbrains/python/run/PythonRunConfiguration.java
python/src/com/jetbrains/python/run/PythonRunConfigurationForm.form
python/src/com/jetbrains/python/run/PythonRunConfigurationForm.java
python/src/com/jetbrains/python/run/PythonScriptCommandLineState.java

index cab56c0118dd869fddfeeb01367fe5f219ae9433..a3badb9c7f42da694158ccce2c419a564120233d 100644 (file)
@@ -144,7 +144,7 @@ public class JBTerminalPanel extends TerminalPanel implements FocusListener, Ter
   }
 
   @Override
-  public boolean dispatch(@NotNull AWTEvent e) {
+  public boolean dispatch(AWTEvent e) {
     if (e instanceof KeyEvent && !skipKeyEvent((KeyEvent)e)) {
       dispatchEvent(e);
       return true;
@@ -233,7 +233,7 @@ public class JBTerminalPanel extends TerminalPanel implements FocusListener, Ter
 
   @Override
   protected BufferedImage createBufferedImage(int width, int height) {
-    return UIUtil.createImage(this, width, height, BufferedImage.TYPE_INT_ARGB);
+    return UIUtil.createImage(width, height, BufferedImage.TYPE_INT_ARGB);
   }
 
 
@@ -285,8 +285,7 @@ public class JBTerminalPanel extends TerminalPanel implements FocusListener, Ter
   }
 
   public FontInfo fontForChar(final char c, @JdkConstants.FontStyle int style) {
-    return ComplementaryFontsRegistry.getFontAbleToDisplay(c, style, mySettingsProvider.getColorScheme().getConsoleFontPreferences(),
-                                                           null);
+    return ComplementaryFontsRegistry.getFontAbleToDisplay(c, style, mySettingsProvider.getColorScheme().getConsoleFontPreferences());
   }
 
   @Override
index 83c24a1ce5c8959858bdc423b3d23c540ab283bc..833cdf95f66c278dd59ecbeeb9f5ec5d6d5bb974 100644 (file)
@@ -16,7 +16,6 @@
 package com.intellij.terminal;
 
 import com.google.common.base.Predicate;
-import com.intellij.execution.filters.Filter;
 import com.intellij.openapi.Disposable;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Disposer;
@@ -29,7 +28,6 @@ import com.intellij.util.ui.RegionPainter;
 import com.jediterm.terminal.SubstringFinder;
 import com.jediterm.terminal.TerminalStarter;
 import com.jediterm.terminal.TtyConnector;
-import com.jediterm.terminal.model.HyperlinkFilter;
 import com.jediterm.terminal.model.JediTerminal;
 import com.jediterm.terminal.model.StyleState;
 import com.jediterm.terminal.model.TerminalTextBuffer;
@@ -44,7 +42,6 @@ import java.awt.*;
 import java.awt.event.KeyEvent;
 import java.awt.event.KeyListener;
 import java.util.List;
-import java.util.stream.Collectors;
 
 public class JBTerminalWidget extends JediTermWidget implements Disposable {
 
@@ -65,42 +62,6 @@ public class JBTerminalWidget extends JediTermWidget implements Disposable {
     Disposer.register(parent, this);
   }
 
-  public void addMessageFilter(Project project, Filter filter) {
-    addHyperlinkFilter(new HyperlinkFilter() {
-      @Override
-      public Result apply(String line) {
-        Filter.Result r = filter.applyFilter(line, line.length());
-        if (r != null) {
-          return new Result() {
-
-            @Override
-            public List<ResultItem> getResultItems() {
-              return r.getResultItems().stream().map((item -> new ResultItem() {
-                @Override
-                public int getStartOffset() {
-                  return item.getHighlightStartOffset();
-                }
-
-                @Override
-                public int getEndOffset() {
-                  return item.getHighlightEndOffset();
-                }
-
-                @Override
-                public void navigate() {
-                  item.getHyperlinkInfo().navigate(project);
-                }
-              })).collect(Collectors.toList());
-            }
-          };
-        }
-        else {
-          return null;
-        }
-      }
-    });
-  }
-
   @Override
   protected JBTerminalPanel createTerminalPanel(@NotNull SettingsProvider settingsProvider,
                                                 @NotNull StyleState styleState,
diff --git a/platform/platform-impl/src/com/intellij/terminal/ProcessHandlerTtyConnector.java b/platform/platform-impl/src/com/intellij/terminal/ProcessHandlerTtyConnector.java
new file mode 100644 (file)
index 0000000..c103f00
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2000-2016 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.intellij.terminal;
+
+import com.intellij.execution.process.OSProcessHandler;
+import com.intellij.execution.process.ProcessHandler;
+import com.jediterm.terminal.Questioner;
+import com.jediterm.terminal.TtyConnector;
+import com.pty4j.PtyProcess;
+import com.pty4j.WinSize;
+import org.jetbrains.annotations.NotNull;
+
+import java.awt.*;
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+/**
+ * @author traff
+ */
+public class ProcessHandlerTtyConnector implements TtyConnector {
+  private final OSProcessHandler myProcessHandler;
+  private final PtyProcess myPtyProcess;
+  protected Charset myCharset;
+
+  public ProcessHandlerTtyConnector(@NotNull ProcessHandler processHandler, @NotNull Charset charset) {
+    if (!(processHandler instanceof OSProcessHandler)) {
+      throw new IllegalArgumentException("Works currently only with OSProcessHandler");
+    }
+    else {
+      myProcessHandler = (OSProcessHandler)processHandler;
+    }
+    if (!(myProcessHandler.getProcess() instanceof PtyProcess)) {
+      throw new IllegalArgumentException("Should be a PTY based process");
+    }
+    else {
+      myPtyProcess = (PtyProcess)myProcessHandler.getProcess();
+    }
+    myCharset = charset;
+  }
+
+
+  @Override
+  public boolean init(Questioner q) {
+    return true;
+  }
+
+  @Override
+  public void close() {
+    myProcessHandler.destroyProcess();
+  }
+
+  @Override
+  public void resize(Dimension termSize, Dimension pixelSize) {
+    if (termSize != null && pixelSize != null) {
+      if (myPtyProcess.isRunning()) {
+        myPtyProcess.setWinSize(
+          new WinSize(termSize.width, termSize.height, pixelSize.width, pixelSize.height));
+      }
+    }
+  }
+
+  @Override
+  public String getName() {
+    return "TtyConnector:" + myProcessHandler.toString();
+  }
+
+  @Override
+  public int read(char[] buf, int offset, int length) throws IOException {
+    throw new IllegalStateException("all reads should be performed by ProcessHandler");
+  }
+
+  @Override
+  public void write(byte[] bytes) throws IOException {
+    myProcessHandler.getProcessInput().write(bytes);
+  }
+
+  @Override
+  public boolean isConnected() {
+    return false;
+  }
+
+  @Override
+  public void write(String string) throws IOException {
+    myProcessHandler.getProcessInput().write(string.getBytes(myCharset));
+  }
+
+  @Override
+  public int waitFor() throws InterruptedException {
+    return myPtyProcess.waitFor();
+  }
+}
diff --git a/platform/platform-impl/src/com/intellij/terminal/TerminalExecutionConsole.java b/platform/platform-impl/src/com/intellij/terminal/TerminalExecutionConsole.java
new file mode 100644 (file)
index 0000000..ad7527a
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2000-2016 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.intellij.terminal;
+
+import com.intellij.execution.filters.Filter;
+import com.intellij.execution.process.ProcessAdapter;
+import com.intellij.execution.process.ProcessEvent;
+import com.intellij.execution.process.ProcessHandler;
+import com.intellij.execution.process.ProcessOutputTypes;
+import com.intellij.execution.ui.ConsoleViewContentType;
+import com.intellij.execution.ui.ExecutionConsole;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.jediterm.terminal.TerminalStarter;
+import com.jediterm.terminal.TtyConnector;
+import com.jediterm.terminal.model.HyperlinkFilter;
+import com.jediterm.terminal.model.JediTerminal;
+import com.jediterm.terminal.ui.TerminalSession;
+import com.jediterm.terminal.util.CharUtils;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author traff
+ */
+public class TerminalExecutionConsole implements ExecutionConsole {
+  private JBTerminalWidget myTerminalWidget;
+
+  public TerminalExecutionConsole(@NotNull Project project, @NotNull ProcessHandler processHandler) {
+    final JBTerminalSystemSettingsProviderBase provider = new JBTerminalSystemSettingsProviderBase();
+    AppendableTerminalDataStream dataStream = new AppendableTerminalDataStream();
+
+
+    myTerminalWidget = new JBTerminalWidget(project, 200, 24, provider, this) {
+      @Override
+      protected TerminalStarter createTerminalStarter(JediTerminal terminal, TtyConnector connector) {
+        return new TerminalStarter(terminal, connector, dataStream);
+      }
+    };
+
+    TerminalSession session = myTerminalWidget
+      .createTerminalSession(new ProcessHandlerTtyConnector(processHandler, Charset.forName("UTF-8"))); //TODO: take charset from settings
+
+    processHandler.addProcessListener(new ProcessAdapter() {
+      @Override
+      public void startNotified(ProcessEvent event) {
+        session.start();
+      }
+
+      @Override
+      public void onTextAvailable(ProcessEvent event, Key outputType) {
+        try {
+          if (outputType != ProcessOutputTypes.STDOUT) {
+            ConsoleViewContentType contentType = ConsoleViewContentType.getConsoleViewType(outputType);
+            dataStream.append(encodeColor(contentType.getAttributes().getForegroundColor()));
+          }
+          dataStream.append(event.getText());
+          if (outputType != ProcessOutputTypes.STDOUT) {
+            dataStream.append((char)CharUtils.ESC + "[39m"); //restore color
+          }
+
+          if (outputType == ProcessOutputTypes.SYSTEM) {
+            dataStream.append('\r');
+          }
+        }
+        catch (IOException e) {
+          // pass
+        }
+      }
+
+      @Override
+      public void processTerminated(ProcessEvent event) {
+        myTerminalWidget.getTerminalPanel().setCursorVisible(false);
+      }
+    });
+  }
+
+  public void addMessageFilter(Project project, Filter filter) {
+    myTerminalWidget.addHyperlinkFilter(new HyperlinkFilter() {
+      @Override
+      public Result apply(String line) {
+        Filter.Result r = filter.applyFilter(line, line.length());
+        if (r != null) {
+          return new Result() {
+
+            @Override
+            public List<ResultItem> getResultItems() {
+              return r.getResultItems().stream().map((item -> new ResultItem() {
+                @Override
+                public int getStartOffset() {
+                  return item.getHighlightStartOffset();
+                }
+
+                @Override
+                public int getEndOffset() {
+                  return item.getHighlightEndOffset();
+                }
+
+                @Override
+                public void navigate() {
+                  item.getHyperlinkInfo().navigate(project);
+                }
+              })).collect(Collectors.toList());
+            }
+          };
+        }
+        else {
+          return null;
+        }
+      }
+    });
+  }
+
+  private static String encodeColor(Color color) {
+    StringBuilder sb = new StringBuilder();
+    sb.append((char)CharUtils.ESC).append("[").append("38;2;").append(color.getRed()).append(";").append(color.getGreen()).append(";")
+      .append(color.getBlue()).append("m");
+    return sb.toString();
+  }
+
+  @Override
+  public JComponent getComponent() {
+    return myTerminalWidget.getComponent();
+  }
+
+  @Override
+  public JComponent getPreferredFocusableComponent() {
+    return myTerminalWidget.getComponent();
+  }
+
+  @Override
+  public void dispose() {
+    myTerminalWidget = null;
+  }
+}
index 77b98dd17ed9410296a4c79820fdd6f791c18c91..6b4aaa3a6aa6a135cb1adc37d4355776ef356144 100644 (file)
@@ -31,5 +31,8 @@ public interface PythonRunConfigurationParams {
 
   boolean showCommandLineAfterwards();
   void setShowCommandLineAfterwards(boolean showCommandLineAfterwards);
+
+  boolean emulateTerminal();
+  void setEmulateTerminal(boolean emulateTerminal);
 }
 
index 102d17048c25004d19d0ad2518de3f6512337a1b..73224708419faec9aa9feb98df4155e220d11422 100644 (file)
@@ -93,6 +93,10 @@ public abstract class PythonCommandLineState extends CommandLineState {
   private Boolean myMultiprocessDebug = null;
   private boolean myRunWithPty = PtyCommandLine.isEnabled();
 
+  public boolean isRunWithPty() {
+    return myRunWithPty;
+  }
+
   public boolean isDebug() {
     return PyDebugRunner.PY_DEBUG_RUNNER.equals(getEnvironment().getRunner().getRunnerId());
   }
index aba3aea6f9de3d5cc6e324327964d205c3d760e4..0a86309dd5cd44645fb6bc26fca0365b130fb5b6 100644 (file)
@@ -19,9 +19,11 @@ import com.intellij.execution.ExecutionException;
 import com.intellij.execution.Executor;
 import com.intellij.execution.configurations.*;
 import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.components.PathMacroManager;
 import com.intellij.openapi.options.SettingsEditor;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.InvalidDataException;
 import com.intellij.openapi.util.JDOMExternalizerUtil;
 import com.intellij.openapi.util.WriteExternalException;
 import com.intellij.openapi.util.io.FileUtil;
@@ -46,9 +48,12 @@ public class PythonRunConfiguration extends AbstractPythonRunConfiguration
   public static final String PARAMETERS = "PARAMETERS";
   public static final String MULTIPROCESS = "MULTIPROCESS";
   public static final String SHOW_COMMAND_LINE = "SHOW_COMMAND_LINE";
+  public static final String EMULATE_TERMINAL = "EMULATE_TERMINAL";
+
   private String myScriptName;
   private String myScriptParameters;
   private boolean myShowCommandLineAfterwards = false;
+  private boolean myEmulateTerminal = false;
 
   protected PythonRunConfiguration(Project project, ConfigurationFactory configurationFactory) {
     super(project, configurationFactory);
@@ -106,11 +111,22 @@ public class PythonRunConfiguration extends AbstractPythonRunConfiguration
     myShowCommandLineAfterwards = showCommandLineAfterwards;
   }
 
+  @Override
+  public boolean emulateTerminal() {
+    return myEmulateTerminal;
+  }
+
+  @Override
+  public void setEmulateTerminal(boolean emulateTerminal) {
+    myEmulateTerminal = emulateTerminal;
+  }
+
   public void readExternal(Element element) {
     super.readExternal(element);
     myScriptName = JDOMExternalizerUtil.readField(element, SCRIPT_NAME);
     myScriptParameters = JDOMExternalizerUtil.readField(element, PARAMETERS);
     myShowCommandLineAfterwards = Boolean.parseBoolean(JDOMExternalizerUtil.readField(element, SHOW_COMMAND_LINE, "false"));
+    myEmulateTerminal = Boolean.parseBoolean(JDOMExternalizerUtil.readField(element, EMULATE_TERMINAL, "false"));
   }
 
   public void writeExternal(Element element) throws WriteExternalException {
@@ -118,6 +134,7 @@ public class PythonRunConfiguration extends AbstractPythonRunConfiguration
     JDOMExternalizerUtil.writeField(element, SCRIPT_NAME, myScriptName);
     JDOMExternalizerUtil.writeField(element, PARAMETERS, myScriptParameters);
     JDOMExternalizerUtil.writeField(element, SHOW_COMMAND_LINE, Boolean.toString(myShowCommandLineAfterwards));
+    JDOMExternalizerUtil.writeField(element, EMULATE_TERMINAL, Boolean.toString(myEmulateTerminal));
   }
 
   public AbstractPythonRunConfigurationParams getBaseParams() {
@@ -129,6 +146,7 @@ public class PythonRunConfiguration extends AbstractPythonRunConfiguration
     target.setScriptName(source.getScriptName());
     target.setScriptParameters(source.getScriptParameters());
     target.setShowCommandLineAfterwards(source.showCommandLineAfterwards());
+    target.setEmulateTerminal(source.emulateTerminal());
   }
 
   @Override
index 33e45b9c04eee047ccebd1375fc99aea8b0395bd..abcb1da534bf981c454a41223e1eb9e7e9e41c01 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.run.PythonRunConfigurationForm">
-  <grid id="27dc6" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="6" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+  <grid id="27dc6" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="7" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
     <margin top="0" left="0" bottom="0" right="0"/>
     <constraints>
       <xy x="20" y="20" width="507" height="400"/>
@@ -44,7 +44,7 @@
       </component>
       <vspacer id="f8c96">
         <constraints>
-          <grid row="5" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+          <grid row="6" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
         </constraints>
       </vspacer>
       <grid id="9b4b8" binding="myCommonOptionsPlaceholder" layout-manager="BorderLayout" hgap="0" vgap="0">
       </grid>
       <component id="99b13" class="com.intellij.ui.components.JBCheckBox" binding="myShowCommandLineCheckbox">
         <constraints>
-          <grid row="4" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+          <grid row="5" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
         </constraints>
         <properties>
           <selected value="true"/>
           <text value="Show command line afterwards"/>
         </properties>
       </component>
+      <component id="d70ad" class="com.intellij.ui.components.JBCheckBox" binding="myEmulateTerminalCheckbox">
+        <constraints>
+          <grid row="4" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <selected value="true"/>
+          <text value="Emulate terminal in output panel"/>
+        </properties>
+      </component>
     </children>
   </grid>
 </form>
index cad905fd9618d73da7b31f0a2c465f44bcf301f0..fe93e2532a421bde727fba737b5447c20bebcf4c 100644 (file)
@@ -31,6 +31,8 @@ import com.jetbrains.python.debugger.PyDebuggerOptionsProvider;
 import org.jetbrains.annotations.NotNull;
 
 import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
 import java.awt.*;
 
 /**
@@ -46,6 +48,7 @@ public class PythonRunConfigurationForm implements PythonRunConfigurationParams,
   private JComponent anchor;
   private final Project myProject;
   private JBCheckBox myShowCommandLineCheckbox;
+  private JBCheckBox myEmulateTerminalCheckbox;
 
   public PythonRunConfigurationForm(PythonRunConfiguration configuration) {
     myCommonOptionsForm = PyCommonOptionsFormFactory.getInstance().createForm(configuration.getCommonOptionsFormData());
@@ -74,6 +77,14 @@ public class PythonRunConfigurationForm implements PythonRunConfigurationParams,
 
     myScriptTextField.addActionListener(listener);
 
+    myEmulateTerminalCheckbox.addChangeListener(new ChangeListener() {
+      @Override
+      public void stateChanged(ChangeEvent e) {
+        boolean selected = !myEmulateTerminalCheckbox.isSelected();
+        myShowCommandLineCheckbox.setEnabled(selected);
+      }
+    });
+
     setAnchor(myCommonOptionsForm.getAnchor());
   }
 
@@ -116,6 +127,16 @@ public class PythonRunConfigurationForm implements PythonRunConfigurationParams,
     myShowCommandLineCheckbox.setSelected(showCommandLineAfterwards);
   }
 
+  @Override
+  public boolean emulateTerminal() {
+    return myEmulateTerminalCheckbox.isSelected();
+  }
+
+  @Override
+  public void setEmulateTerminal(boolean emulateTerminal) {
+    myEmulateTerminalCheckbox.setSelected(emulateTerminal);
+  }
+
   @Override
   public JComponent getAnchor() {
     return anchor;
index 208f30bb0097e8489a2948663236ea80d64b708c..5e89ccd150b6fe0ed43e1bb86587d4c8a1d0f8d7 100644 (file)
@@ -25,12 +25,19 @@ import com.intellij.execution.configurations.ParametersList;
 import com.intellij.execution.configurations.ParamsGroup;
 import com.intellij.execution.console.ConsoleExecuteAction;
 import com.intellij.execution.executors.DefaultDebugExecutor;
+import com.intellij.execution.filters.UrlFilter;
+import com.intellij.execution.process.OSProcessHandler;
+import com.intellij.execution.process.ProcessHandler;
 import com.intellij.execution.runners.ExecutionEnvironment;
 import com.intellij.openapi.actionSystem.AnAction;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.util.SystemInfo;
 import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.terminal.TerminalExecutionConsole;
 import com.intellij.util.ArrayUtil;
+import com.intellij.util.io.BaseDataReader;
+import com.intellij.util.io.BaseOutputReader;
 import com.jetbrains.python.PythonHelper;
 import com.jetbrains.python.console.PyConsoleOptions;
 import com.jetbrains.python.console.PyConsoleType;
@@ -58,7 +65,7 @@ public class PythonScriptCommandLineState extends PythonCommandLineState {
 
   @Override
   public ExecutionResult execute(Executor executor, final CommandLinePatcher... patchers) throws ExecutionException {
-    if (myConfig.showCommandLineAfterwards()) {
+    if (myConfig.showCommandLineAfterwards() && !myConfig.emulateTerminal()) {
       if (executor.getId() == DefaultDebugExecutor.EXECUTOR_ID) {
         return super.execute(executor, ArrayUtil.append(patchers, new CommandLinePatcher() {
           @Override
@@ -85,11 +92,65 @@ public class PythonScriptCommandLineState extends PythonCommandLineState {
 
       return new DefaultExecutionResult(runner.getConsoleView(), runner.getProcessHandler(), actions.toArray(new AnAction[actions.size()]));
     }
+    else if (myConfig.emulateTerminal()) {
+      setRunWithPty(true);
+
+      final ProcessHandler processHandler = startProcess();
+
+      TerminalExecutionConsole executeConsole = new TerminalExecutionConsole(myConfig.getProject(), processHandler);
+
+      executeConsole.addMessageFilter(myConfig.getProject(), new PythonTracebackFilter(myConfig.getProject()));
+      executeConsole.addMessageFilter(myConfig.getProject(), new UrlFilter());
+
+      processHandler.startNotify();
+
+      return new DefaultExecutionResult(executeConsole, processHandler, AnAction.EMPTY_ARRAY);
+    }
     else {
       return super.execute(executor, patchers);
     }
   }
 
+  @Override
+  public void customizeEnvironmentVars(Map<String, String> envs, boolean passParentEnvs) {
+    if (myConfig.emulateTerminal()) {
+      if (!SystemInfo.isWindows) {
+        envs.put("TERM", "xterm-256color");
+      }
+    }
+  }
+
+  @Override
+  protected ProcessHandler doCreateProcess(GeneralCommandLine commandLine) throws ExecutionException {
+    if (myConfig.emulateTerminal()) {
+      return new OSProcessHandler(commandLine) {
+        @NotNull
+        @Override
+        protected BaseOutputReader.Options readerOptions() {
+          return new BaseOutputReader.Options() {
+            @Override
+            public BaseDataReader.SleepingPolicy policy() {
+              return BaseDataReader.SleepingPolicy.BLOCKING;
+            }
+
+            @Override
+            public boolean splitToLines() {
+              return false;
+            }
+
+            @Override
+            public boolean withSeparators() {
+              return true;
+            }
+          };
+        }
+      };
+    }
+    else {
+      return super.doCreateProcess(commandLine);
+    }
+  }
+
   @Override
   protected void buildCommandLineParameters(GeneralCommandLine commandLine) {
     ParametersList parametersList = commandLine.getParametersList();
@@ -127,7 +188,8 @@ public class PythonScriptCommandLineState extends PythonCommandLineState {
                                          CommandLinePatcher[] patchers,
                                          PyConsoleOptions.PyConsoleSettings consoleSettings,
                                          String... statementsToExecute) {
-      super(project, sdk, consoleType, workingDir, environmentVariables, consoleSettings, (s) -> {}, statementsToExecute);
+      super(project, sdk, consoleType, workingDir, environmentVariables, consoleSettings, (s) -> {
+      }, statementsToExecute);
       myPatchers = patchers;
     }