Skip to content

Commit ea577a0

Browse files
committed
Fix running with TTY but without readline initialized
1 parent d46f6fa commit ea577a0

File tree

3 files changed

+71
-5
lines changed

3 files changed

+71
-5
lines changed

graalpython/com.oracle.graal.python.shell/src/com/oracle/graal/python/shell/JLineConsoleHandler.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
import java.io.IOException;
4444
import java.io.InputStream;
4545
import java.io.OutputStream;
46+
import java.io.OutputStreamWriter;
47+
import java.io.Reader;
48+
import java.io.Writer;
49+
import java.nio.charset.Charset;
4650
import java.nio.file.Path;
4751
import java.time.Instant;
4852
import java.util.Collections;
@@ -69,11 +73,14 @@
6973
import org.graalvm.shadowed.org.jline.reader.impl.DefaultParser;
7074
import org.graalvm.shadowed.org.jline.terminal.Terminal;
7175
import org.graalvm.shadowed.org.jline.terminal.TerminalBuilder;
76+
import org.graalvm.shadowed.org.jline.utils.InputStreamReader;
7277

7378
public class JLineConsoleHandler extends ConsoleHandler {
7479
private final InputStream inputStream;
7580
private final OutputStream outputStream;
7681
private LineReader reader;
82+
private Reader fallbackReader;
83+
private Writer fallbackWriter;
7784
private final LinkedList<String> lineBuffer = new LinkedList<>();
7885
private CompletionState completionState;
7986

@@ -175,6 +182,49 @@ public static class CompletionState {
175182
// Used via interop
176183
@Override
177184
public String readLine(String prompt) {
185+
if (reader == null) {
186+
if (fallbackReader == null) {
187+
Charset charset;
188+
try {
189+
charset = Charset.forName(System.getProperty("stdin.encoding"));
190+
} catch (Exception e) {
191+
charset = Charset.defaultCharset();
192+
}
193+
// Note: using InputStreamReader from jline.utils for truly unbuffered reads
194+
fallbackReader = new InputStreamReader(inputStream, charset);
195+
}
196+
try {
197+
if (prompt != null && !prompt.isEmpty()) {
198+
if (fallbackWriter == null) {
199+
fallbackWriter = new OutputStreamWriter(outputStream);
200+
}
201+
fallbackWriter.write(prompt);
202+
fallbackWriter.flush();
203+
}
204+
StringBuilder sb = new StringBuilder();
205+
while (true) {
206+
int c = inputStream.read();
207+
if (c < 0) {
208+
// EOF
209+
if (!sb.isEmpty()) {
210+
break;
211+
}
212+
if (prompt != null) {
213+
fallbackWriter.write("\n");
214+
fallbackWriter.flush();
215+
}
216+
return null;
217+
} else if (c == '\n') {
218+
break;
219+
} else if (c != '\r') {
220+
sb.append((char) c);
221+
}
222+
}
223+
return sb.toString();
224+
} catch (IOException e) {
225+
throw new RuntimeException(e);
226+
}
227+
}
178228
if (lineBuffer.isEmpty()) {
179229
try {
180230
String lines = reader.readLine(prompt);

graalpython/com.oracle.graal.python.test/src/tests/test_repl.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def validate_repl(stdin, python_args=(), ignore_preamble=True):
8585
import termios
8686
termios.tcsetwinsize(pty_parent, (60, 80))
8787
proc = subprocess.Popen(
88-
[sys.executable, '-I', *python_args],
88+
[sys.executable, '-s', '-E', '-P', *python_args],
8989
env=env,
9090
stdin=pty_child,
9191
stdout=pty_child,
@@ -151,6 +151,18 @@ def test_basic_repl():
151151
"""))
152152

153153

154+
def test_basic_repl_no_readline():
155+
validate_repl(dedent("""\
156+
>>> 1023 + 1
157+
1024
158+
>>> None
159+
>>> "hello"
160+
'hello'
161+
>>> _
162+
'hello'
163+
"""), python_args=['-I'])
164+
165+
154166
def test_continuation():
155167
validate_repl(dedent(r'''\
156168
>>> def foo():

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ReadlineModuleBuiltins.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import com.oracle.graal.python.runtime.GilNode;
7575
import com.oracle.graal.python.runtime.PythonContext;
7676
import com.oracle.graal.python.runtime.exception.PythonErrorType;
77+
import com.oracle.graal.python.util.PythonUtils;
7778
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
7879
import com.oracle.truffle.api.TruffleLogger;
7980
import com.oracle.truffle.api.dsl.Bind;
@@ -128,7 +129,10 @@ public void postInitialize(Python3Core core) {
128129
public static Object getConsoleHandlerIfInitialized(PythonContext context) {
129130
PythonModule module = context.lookupBuiltinModule(T_READLINE);
130131
if (module != null) {
131-
return module.getModuleState(LocalData.class).consoleHandler;
132+
LocalData moduleState = module.getModuleState(LocalData.class);
133+
if (moduleState != null) {
134+
return moduleState.consoleHandler;
135+
}
132136
}
133137
return null;
134138
}
@@ -403,7 +407,7 @@ static Object getCompletionStateMember(PythonModule self, String member) {
403407
}
404408
}
405409
} catch (InteropException e) {
406-
// Fall through
410+
LOGGER.fine(() -> PythonUtils.formatJString("Exception in getting completion state member %s: %s", member, e));
407411
}
408412
}
409413
return null;
@@ -416,7 +420,7 @@ static int getCompletionStateIntMember(PythonModule self, String member, int def
416420
try {
417421
return InteropLibrary.getUncached().asInt(idx);
418422
} catch (InteropException e) {
419-
// Fall through
423+
LOGGER.fine(() -> PythonUtils.formatJString("Exception in getting completion state member %s: %s", member, e));
420424
}
421425
}
422426
return defaultValue;
@@ -433,7 +437,7 @@ static Object get(PythonModule self) {
433437
try {
434438
return InteropLibrary.getUncached().asTruffleString(lineObj);
435439
} catch (InteropException e) {
436-
// Fall through
440+
LOGGER.fine(() -> PythonUtils.formatJString("Exception in getting completion state member %s: %s", "line", e));
437441
}
438442
}
439443
return T_EMPTY_STRING;

0 commit comments

Comments
 (0)