console: send \r on Enter key pressed for PTY based processes (GO-9805)
[idea/community.git] / platform / lang-impl / src / com / intellij / execution / impl / ConsoleViewRunningState.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.execution.impl;
3
4 import com.google.common.base.Ascii;
5 import com.intellij.execution.ExecutionBundle;
6 import com.intellij.execution.process.*;
7 import com.intellij.execution.ui.ConsoleViewContentType;
8 import com.intellij.openapi.util.Key;
9 import com.intellij.openapi.util.SystemInfo;
10 import com.intellij.openapi.vfs.encoding.EncodingManager;
11 import com.pty4j.PtyProcess;
12 import org.jetbrains.annotations.NotNull;
13 import org.jetbrains.annotations.Nullable;
14 import org.jetbrains.annotations.TestOnly;
15
16 import java.io.IOException;
17 import java.io.OutputStream;
18 import java.io.OutputStreamWriter;
19 import java.io.Writer;
20 import java.nio.charset.Charset;
21
22 public class ConsoleViewRunningState extends ConsoleState {
23   private final ConsoleViewImpl myConsole;
24   private final ProcessHandler myProcessHandler;
25   private final ConsoleState myFinishedStated;
26   private final Writer myUserInputWriter;
27   private final ProcessStreamsSynchronizer myStreamsSynchronizer;
28
29   private final ProcessAdapter myProcessListener = new ProcessAdapter() {
30     @Override
31     public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) {
32       if (outputType instanceof ProcessOutputType) {
33         myStreamsSynchronizer.doWhenStreamsSynchronized(event.getText(), (ProcessOutputType)outputType, () -> {
34           print(event.getText(), outputType);
35         });
36       }
37       else {
38         print(event.getText(), outputType);
39       }
40     }
41   };
42
43   public ConsoleViewRunningState(@NotNull ConsoleViewImpl console,
44                                  @NotNull ProcessHandler processHandler,
45                                  @NotNull ConsoleState finishedStated,
46                                  boolean attachToStdOut,
47                                  boolean attachToStdIn) {
48     myConsole = console;
49     myProcessHandler = processHandler;
50     myFinishedStated = finishedStated;
51     myStreamsSynchronizer = attachToStdOut ? new ProcessStreamsSynchronizer(console) : null;
52
53     // attach to process stdout
54     if (attachToStdOut) {
55       processHandler.addProcessListener(myProcessListener);
56     }
57
58     // attach to process stdin
59     if (attachToStdIn) {
60       final OutputStream processInput = myProcessHandler.getProcessInput();
61       myUserInputWriter = processInput == null ? null : createOutputStreamWriter(processInput, processHandler);
62     }
63     else {
64       myUserInputWriter = null;
65     }
66   }
67
68   private static OutputStreamWriter createOutputStreamWriter(OutputStream processInput, ProcessHandler processHandler) {
69     Charset charset = null;
70     if (processHandler instanceof OSProcessHandler) {
71       charset = ((OSProcessHandler)processHandler).getCharset();
72     }
73     if (charset == null) {
74       charset = EncodingManager.getInstance().getDefaultCharset();
75     }
76     return new OutputStreamWriter(processInput, charset);
77   }
78
79   private void print(@NotNull String text, @NotNull Key<?> outputType) {
80     myConsole.print(text, ConsoleViewContentType.getConsoleViewType(outputType));
81   }
82
83   @Override
84   @NotNull
85   public ConsoleState dispose() {
86     if (myProcessHandler != null) {
87       myProcessHandler.removeProcessListener(myProcessListener);
88     }
89     return myFinishedStated;
90   }
91
92   @Override
93   public boolean isCommandLine(@NotNull String line) {
94     return myProcessHandler instanceof BaseProcessHandler && line.equals(((BaseProcessHandler)myProcessHandler).getCommandLine());
95   }
96
97   @Override
98   public boolean isFinished() {
99     return myProcessHandler == null || myProcessHandler.isProcessTerminated();
100   }
101
102   @Override
103   public boolean isRunning() {
104     return myProcessHandler != null && !myProcessHandler.isProcessTerminated();
105   }
106
107   @Override
108   public void sendUserInput(@NotNull final String input) throws IOException {
109     if (myUserInputWriter == null) {
110       throw new IOException(ExecutionBundle.message("no.user.process.input.error.message"));
111     }
112     char enterKeyCode = getEnterKeyCode();
113     String inputToSend = input.replace((char)Ascii.LF, enterKeyCode);
114     myUserInputWriter.write(inputToSend);
115     myUserInputWriter.flush();
116   }
117
118   private char getEnterKeyCode() {
119     if (SystemInfo.isWindows &&
120         myProcessHandler instanceof OSProcessHandler &&
121         ((OSProcessHandler)myProcessHandler).getProcess() instanceof PtyProcess) {
122       // pty4j expects \r as Enter key code
123       // https://github.com/JetBrains/pty4j/blob/0.9.4/test/com/pty4j/PtyTest.java#L54
124       return Ascii.CR;
125     }
126     return Ascii.LF;
127   }
128
129   @NotNull
130   @Override
131   public ConsoleState attachTo(@NotNull final ConsoleViewImpl console, final @NotNull ProcessHandler processHandler) {
132     return dispose().attachTo(console, processHandler);
133   }
134
135   @TestOnly
136   @Nullable
137   ProcessStreamsSynchronizer getStreamsSynchronizer() {
138     return myStreamsSynchronizer;
139   }
140
141   @Override
142   public String toString() {
143     return "Running state";
144   }
145 }