Skip to content

Commit 41e381f

Browse files
committed
Initialize JLine during readline import
1 parent 950eba8 commit 41e381f

File tree

9 files changed

+155
-212
lines changed

9 files changed

+155
-212
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Args = --add-exports=org.graalvm.py.launcher/com.oracle.graal.python.shell=org.graalvm.truffle
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[
2+
{
3+
"name": "com.oracle.graal.python.shell.JLineConsoleHandler",
4+
"methods": [
5+
{
6+
"name": "initializeReadline",
7+
"parameterTypes": [
8+
"org.graalvm.polyglot.Value"
9+
]
10+
},
11+
{
12+
"name": "readLine",
13+
"parameterTypes": [
14+
"java.lang.String"
15+
]
16+
}
17+
]
18+
}
19+
]

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

Lines changed: 3 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -40,60 +40,30 @@
4040
*/
4141
package com.oracle.graal.python.shell;
4242

43-
import java.io.IOException;
4443
import java.io.InputStream;
4544
import java.nio.charset.StandardCharsets;
46-
import java.util.List;
47-
import java.util.function.BiConsumer;
48-
import java.util.function.BooleanSupplier;
49-
import java.util.function.Consumer;
50-
import java.util.function.Function;
51-
import java.util.function.IntConsumer;
52-
import java.util.function.IntFunction;
53-
import java.util.function.IntSupplier;
54-
55-
import org.graalvm.polyglot.Context;
5645

5746
/**
5847
* The interface to a source of input/output for the context, which may have different
5948
* implementations for different contexts.
6049
*/
6150
public abstract class ConsoleHandler {
6251

63-
/**
64-
* Read a line of input, newline is <b>NOT</b> included in result.
65-
*/
66-
public final String readLine() {
67-
return readLine(true);
68-
}
69-
70-
public abstract String readLine(boolean prompt);
71-
72-
public abstract void setPrompt(String prompt);
73-
74-
public void setContext(@SuppressWarnings("unused") Context context) {
75-
// ignore by default
76-
}
77-
78-
@SuppressWarnings("unused")
79-
public void setupReader(BooleanSupplier shouldRecord, IntSupplier getSize, Consumer<String> addItem, IntFunction<String> getItem, BiConsumer<Integer, String> setItem, IntConsumer removeItem,
80-
Runnable clear, Function<String, List<String>> completer) {
81-
// ignore by default
82-
}
52+
public abstract String readLine(String prompt);
8353

8454
public InputStream createInputStream() {
8555
return new InputStream() {
8656
byte[] buffer = null;
8757
int pos = 0;
8858

8959
@Override
90-
public int read() throws IOException {
60+
public int read() {
9161
if (pos < 0) {
9262
pos = 0;
9363
return -1;
9464
} else if (buffer == null) {
9565
assert pos == 0;
96-
String line = readLine(false);
66+
String line = readLine(null);
9767
if (line == null) {
9868
return -1;
9969
}
@@ -109,14 +79,4 @@ public int read() throws IOException {
10979
}
11080
};
11181
}
112-
113-
public int getTerminalWidth() {
114-
// default value
115-
return 80;
116-
}
117-
118-
public int getTerminalHeight() {
119-
// default value
120-
return 25;
121-
}
12282
}

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -54,15 +54,12 @@ class DefaultConsoleHandler extends ConsoleHandler {
5454
}
5555

5656
@Override
57-
public String readLine(boolean showPrompt) {
57+
public String readLine(String prompt) {
5858
try {
5959
return in.readLine();
6060
} catch (IOException e) {
6161
throw new RuntimeException(e);
6262
}
6363
}
6464

65-
@Override
66-
public void setPrompt(String prompt) {
67-
}
6865
}

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

Lines changed: 29 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import java.util.Map;
4848
import java.util.Set;
4949
import java.util.UUID;
50-
import java.util.function.Function;
5150
import java.util.function.Predicate;
5251

5352
import org.graalvm.launcher.AbstractLanguageLauncher;
@@ -801,9 +800,8 @@ protected void launch(Builder contextBuilder) {
801800
ConsoleHandler consoleHandler = createConsoleHandler(System.in, System.out);
802801
contextBuilder.arguments(getLanguageId(), programArgs.toArray(new String[programArgs.size()]));
803802
contextBuilder.in(consoleHandler.createInputStream());
804-
contextBuilder.option("python.TerminalIsInteractive", Boolean.toString(isTTY()));
805-
contextBuilder.option("python.TerminalWidth", Integer.toString(consoleHandler.getTerminalWidth()));
806-
contextBuilder.option("python.TerminalHeight", Integer.toString(consoleHandler.getTerminalHeight()));
803+
boolean tty = isTTY();
804+
contextBuilder.option("python.TerminalIsInteractive", Boolean.toString(tty));
807805

808806
contextBuilder.option("python.CheckHashPycsMode", checkHashPycsMode);
809807
contextBuilder.option("python.RunViaLauncher", "true");
@@ -841,15 +839,20 @@ protected void launch(Builder contextBuilder) {
841839
evalInternal(context, "__graalpython__.startup_wall_clock_ts = " + startupWallClockTime + "; __graalpython__.startup_nano = " + startupNanoTime);
842840
}
843841

844-
if (!quietFlag && (verboseFlag || (commandString == null && inputFile == null && isTTY()))) {
845-
print("Python " + evalInternal(context, "import sys; sys.version + ' on ' + sys.platform").asString());
842+
Value sysModule = evalInternal(context, "import sys; sys");
843+
if (!quietFlag && (verboseFlag || (commandString == null && inputFile == null && tty))) {
844+
print("Python " + sysModule.getMember("version").asString() + " on " + sysModule.getMember("platform"));
846845
if (!noSite) {
847846
print("Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.");
848847
}
849848
}
850-
consoleHandler.setContext(context);
851849

852-
if (commandString != null || inputFile != null || !isTTY()) {
850+
sysModule.putMember("_console_handler", consoleHandler);
851+
if (tty && !isolateFlag && (inspectFlag || (commandString == null && inputFile == null))) {
852+
evalInternal(context, "import readline, rlcompleter");
853+
}
854+
855+
if (commandString != null || inputFile != null || !tty) {
853856
try {
854857
evalNonInteractive(context, consoleHandler);
855858
rc = 0;
@@ -865,7 +868,7 @@ protected void launch(Builder contextBuilder) {
865868
}
866869
if ((commandString == null && inputFile == null) || inspectFlag) {
867870
inspectFlag = false;
868-
rc = readEvalPrint(context, consoleHandler);
871+
rc = readEvalPrint(context, consoleHandler, sysModule);
869872
}
870873
} catch (RuntimeException e) {
871874
if (e.getMessage() != null && e.getMessage().contains("did not complete all polyglot threads")) {
@@ -878,7 +881,6 @@ protected void launch(Builder contextBuilder) {
878881
// brings up getaddrinfo which may just block in native for an arbitrary amount of
879882
// time and prevent us from shutting down the thread.
880883
if (!verboseFlag) {
881-
tryToResetConsoleHandler(consoleHandler);
882884
System.exit(rc);
883885
}
884886
} else {
@@ -887,20 +889,10 @@ protected void launch(Builder contextBuilder) {
887889
} catch (IOException e) {
888890
rc = 1;
889891
e.printStackTrace();
890-
} finally {
891-
tryToResetConsoleHandler(consoleHandler);
892892
}
893893
System.exit(rc);
894894
}
895895

896-
private static void tryToResetConsoleHandler(ConsoleHandler consoleHandler) {
897-
try {
898-
consoleHandler.setContext(null);
899-
} catch (Throwable e) {
900-
// pass
901-
}
902-
}
903-
904896
private static boolean getBoolEnv(String var) {
905897
return getEnv(var) != null;
906898
}
@@ -1083,10 +1075,6 @@ private static void printFileNotFoundException(NoSuchFileException e) {
10831075
}
10841076

10851077
private void evalNonInteractive(Context context, ConsoleHandler consoleHandler) throws IOException {
1086-
// We need to setup the terminal even when not running the REPL because code may request
1087-
// input from the terminal.
1088-
setupTerminal(consoleHandler);
1089-
10901078
Source src;
10911079
if (commandString != null) {
10921080
src = Source.newBuilder(getLanguageId(), commandString, "<string>").build();
@@ -1189,7 +1177,7 @@ private static ConsoleHandler createConsoleHandler(InputStream inStream, OutputS
11891177
if (!isTTY()) {
11901178
return new DefaultConsoleHandler(inStream);
11911179
} else {
1192-
return new JLineConsoleHandler(inStream, outStream, false);
1180+
return new JLineConsoleHandler(inStream, outStream);
11931181
}
11941182
}
11951183

@@ -1203,19 +1191,24 @@ private static ConsoleHandler createConsoleHandler(InputStream inStream, OutputS
12031191
* In case 2, we must implicitly execute a {@code quit("default, 0L, TRUE} command before
12041192
* exiting. So,in either case, we never return.
12051193
*/
1206-
private int readEvalPrint(Context context, ConsoleHandler consoleHandler) {
1194+
private int readEvalPrint(Context context, ConsoleHandler consoleHandler, Value sysModule) {
12071195
int lastStatus = 0;
12081196
try {
1209-
setupREPL(context, consoleHandler);
1210-
Value sys = evalInternal(context, "import sys; sys");
1211-
sys.putMember("_console_handler", consoleHandler);
1212-
1197+
Value hook = null;
1198+
try {
1199+
hook = sysModule.getMember("__interactivehook__");
1200+
} catch (PolyglotException e) {
1201+
// ignore
1202+
}
1203+
if (hook != null) {
1204+
hook.execute();
1205+
}
12131206
while (true) { // processing inputs
12141207
boolean doEcho = doEcho(context);
1215-
consoleHandler.setPrompt(doEcho ? sys.getMember("ps1").asString() : null);
1208+
String ps1 = doEcho ? sysModule.getMember("ps1").asString() : null;
12161209

12171210
try {
1218-
String input = consoleHandler.readLine();
1211+
String input = consoleHandler.readLine(ps1);
12191212
if (input == null) {
12201213
throw new EOFException();
12211214
}
@@ -1224,26 +1217,25 @@ private int readEvalPrint(Context context, ConsoleHandler consoleHandler) {
12241217
continue;
12251218
}
12261219

1227-
String continuePrompt = null;
1220+
String ps2 = null;
12281221
StringBuilder sb = new StringBuilder(input).append('\n');
12291222
while (true) { // processing subsequent lines while input is incomplete
12301223
lastStatus = 0;
12311224
try {
12321225
context.eval(Source.newBuilder(getLanguageId(), sb.toString(), "<stdin>").interactive(true).buildLiteral());
12331226
} catch (PolyglotException e) {
1234-
if (continuePrompt == null) {
1235-
continuePrompt = doEcho ? sys.getMember("ps2").asString() : null;
1227+
if (ps2 == null) {
1228+
ps2 = doEcho ? sysModule.getMember("ps2").asString() : null;
12361229
}
12371230
if (e.isIncompleteSource()) {
12381231
// read more input until we get an empty line
1239-
consoleHandler.setPrompt(continuePrompt);
12401232
String additionalInput;
12411233
boolean isIncompleteCode = false; // this for cases like empty lines
12421234
// in tripplecode, where the
12431235
// additional input can be empty,
12441236
// but we should still continue
12451237
do {
1246-
additionalInput = consoleHandler.readLine();
1238+
additionalInput = consoleHandler.readLine(ps2);
12471239
sb.append(additionalInput).append('\n');
12481240
try {
12491241
// We try to parse every time, when an additional input is
@@ -1335,52 +1327,6 @@ private Value evalInternal(Context context, String code) {
13351327
return context.eval(Source.newBuilder(getLanguageId(), code, "<internal>").internal(true).buildLiteral());
13361328
}
13371329

1338-
private void setupREPL(Context context, ConsoleHandler consoleHandler) {
1339-
// Then we can get the readline module and see if any completers were registered and use its
1340-
// history feature
1341-
evalInternal(context, "import sys\ngetattr(sys, '__interactivehook__', lambda: None)()\n");
1342-
final Value readline = evalInternal(context, "import readline; readline");
1343-
final Value getCompleter = readline.getMember("get_completer").execute();
1344-
final Value shouldRecord = readline.getMember("get_auto_history");
1345-
final Value addHistory = readline.getMember("add_history");
1346-
final Value getHistoryItem = readline.getMember("get_history_item");
1347-
final Value setHistoryItem = readline.getMember("replace_history_item");
1348-
final Value deleteHistoryItem = readline.getMember("remove_history_item");
1349-
final Value clearHistory = readline.getMember("clear_history");
1350-
final Value getHistorySize = readline.getMember("get_current_history_length");
1351-
1352-
Function<String, List<String>> completer = null;
1353-
if (getCompleter.canExecute()) {
1354-
completer = (buffer) -> {
1355-
List<String> candidates = new ArrayList<>();
1356-
Value candidate = getCompleter.execute(buffer, candidates.size());
1357-
while (candidate.isString()) {
1358-
candidates.add(candidate.asString());
1359-
candidate = getCompleter.execute(buffer, candidates.size());
1360-
}
1361-
return candidates;
1362-
};
1363-
}
1364-
consoleHandler.setupReader(
1365-
() -> shouldRecord.execute().asBoolean(),
1366-
() -> getHistorySize.execute().asInt(),
1367-
(item) -> addHistory.execute(item),
1368-
(pos) -> getHistoryItem.execute(pos).asString(),
1369-
(pos, item) -> setHistoryItem.execute(pos, item),
1370-
(pos) -> deleteHistoryItem.execute(pos),
1371-
() -> clearHistory.execute(),
1372-
completer);
1373-
1374-
}
1375-
1376-
private static void setupTerminal(ConsoleHandler consoleHandler) {
1377-
consoleHandler.setupReader(() -> false, () -> 0, (item) -> {
1378-
}, (pos) -> null, (pos, item) -> {
1379-
}, (pos) -> {
1380-
}, () -> {
1381-
}, null);
1382-
}
1383-
13841330
/**
13851331
* Some system properties have already been read at this point, so to change them, we just
13861332
* re-execute the process with the additional options.

0 commit comments

Comments
 (0)