Skip to content

Commit 2c8e94f

Browse files
committed
8247403: JShell: No custom input (e.g. from GUI) possible with JavaShellToolBuilder
Reviewed-by: vromero
1 parent 64f0f68 commit 2c8e94f

File tree

14 files changed

+247
-29
lines changed

14 files changed

+247
-29
lines changed

src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4157,7 +4157,7 @@ private AttributedString expandPromptPattern(String pattern, int padToWidth,
41574157
} else
41584158
sb.append(ch);
41594159
}
4160-
if (padToWidth > cols) {
4160+
if (padToWidth > cols && padToWidth > 0) {
41614161
int padCharCols = WCWidth.wcwidth(padChar);
41624162
int padCount = (padToWidth - cols) / padCharCols;
41634163
sb = padPartString;

src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -47,6 +47,7 @@
4747
import java.util.ListIterator;
4848
import java.util.Map;
4949
import java.util.Optional;
50+
import java.util.function.Consumer;
5051
import java.util.function.Function;
5152
import java.util.stream.Collectors;
5253
import java.util.stream.Stream;
@@ -103,8 +104,8 @@ class ConsoleIOContext extends IOContext {
103104

104105
String prefix = "";
105106

106-
ConsoleIOContext(JShellTool repl, InputStream cmdin, PrintStream cmdout) throws Exception {
107-
this.allowIncompleteInputs = Boolean.getBoolean("jshell.test.allow.incomplete.inputs");
107+
ConsoleIOContext(JShellTool repl, InputStream cmdin, PrintStream cmdout,
108+
boolean interactive) throws Exception {
108109
this.repl = repl;
109110
Map<String, Object> variables = new HashMap<>();
110111
this.input = new StopDetectingInputStream(() -> repl.stop(),
@@ -116,15 +117,28 @@ public int readBuffered(byte[] b) throws IOException {
116117
}
117118
};
118119
Terminal terminal;
119-
if (System.getProperty("test.jdk") != null) {
120-
terminal = new TestTerminal(nonBlockingInput, cmdout);
120+
boolean allowIncompleteInputs = Boolean.getBoolean("jshell.test.allow.incomplete.inputs");
121+
Consumer<LineReaderImpl> setupReader = r -> {};
122+
if (cmdin != System.in) {
123+
if (System.getProperty("test.jdk") != null) {
124+
terminal = new TestTerminal(nonBlockingInput, cmdout);
125+
} else {
126+
Size size = null;
127+
terminal = new ProgrammaticInTerminal(nonBlockingInput, cmdout, interactive,
128+
size);
129+
if (!interactive) {
130+
setupReader = r -> r.unsetOpt(Option.BRACKETED_PASTE);
131+
allowIncompleteInputs = true;
132+
}
133+
}
121134
input.setInputStream(cmdin);
122135
} else {
123136
terminal = TerminalBuilder.builder().inputStreamWrapper(in -> {
124137
input.setInputStream(in);
125138
return nonBlockingInput;
126139
}).build();
127140
}
141+
this.allowIncompleteInputs = allowIncompleteInputs;
128142
originalAttributes = terminal.getAttributes();
129143
Attributes noIntr = new Attributes(originalAttributes);
130144
noIntr.setControlChar(ControlChar.VINTR, 0);
@@ -179,10 +193,11 @@ protected boolean insertCloseSquare() {
179193
}
180194
};
181195

196+
setupReader.accept(reader);
182197
reader.setOpt(Option.DISABLE_EVENT_EXPANSION);
183198

184199
reader.setParser((line, cursor, context) -> {
185-
if (!allowIncompleteInputs && !repl.isComplete(line)) {
200+
if (!ConsoleIOContext.this.allowIncompleteInputs && !repl.isComplete(line)) {
186201
int pendingBraces = countPendingOpenBraces(line);
187202
throw new EOFError(cursor, cursor, line, null, pendingBraces, null);
188203
}
@@ -230,7 +245,7 @@ public String readLine(String firstLinePrompt, String continuationPrompt,
230245
this.prefix = prefix;
231246
try {
232247
in.setVariable(LineReader.SECONDARY_PROMPT_PATTERN, continuationPrompt);
233-
return in.readLine(firstLinePrompt);
248+
return in.readLine(firstLine ? firstLinePrompt : continuationPrompt);
234249
} catch (UserInterruptException ex) {
235250
throw (InputInterruptedException) new InputInterruptedException().initCause(ex);
236251
} catch (EndOfFileException ex) {
@@ -1276,28 +1291,31 @@ private History getHistory() {
12761291
return in.getHistory();
12771292
}
12781293

1279-
private static final class TestTerminal extends LineDisciplineTerminal {
1294+
private static class ProgrammaticInTerminal extends LineDisciplineTerminal {
12801295

1281-
private static final int DEFAULT_HEIGHT = 24;
1296+
protected static final int DEFAULT_HEIGHT = 24;
12821297

12831298
private final NonBlockingReader inputReader;
1299+
private final Size bufferSize;
1300+
1301+
public ProgrammaticInTerminal(InputStream input, OutputStream output,
1302+
boolean interactive, Size size) throws Exception {
1303+
this(input, output, interactive ? "ansi" : "dumb",
1304+
size != null ? size : new Size(80, DEFAULT_HEIGHT),
1305+
size != null ? size
1306+
: interactive ? new Size(80, DEFAULT_HEIGHT)
1307+
: new Size(Integer.MAX_VALUE - 1, DEFAULT_HEIGHT));
1308+
}
12841309

1285-
public TestTerminal(InputStream input, OutputStream output) throws Exception {
1286-
super("test", "ansi", output, Charset.forName("UTF-8"));
1310+
protected ProgrammaticInTerminal(InputStream input, OutputStream output,
1311+
String terminal, Size size, Size bufferSize) throws Exception {
1312+
super("non-system-in", terminal, output, Charset.forName("UTF-8"));
12871313
this.inputReader = NonBlocking.nonBlocking(getName(), input, encoding());
12881314
Attributes a = new Attributes(getAttributes());
12891315
a.setLocalFlag(LocalFlag.ECHO, false);
12901316
setAttributes(attributes);
1291-
int h = DEFAULT_HEIGHT;
1292-
try {
1293-
String hp = System.getProperty("test.terminal.height");
1294-
if (hp != null && !hp.isEmpty()) {
1295-
h = Integer.parseInt(hp);
1296-
}
1297-
} catch (Throwable ex) {
1298-
// ignore
1299-
}
1300-
setSize(new Size(80, h));
1317+
setSize(size);
1318+
this.bufferSize = bufferSize;
13011319
}
13021320

13031321
@Override
@@ -1312,6 +1330,31 @@ protected void doClose() throws IOException {
13121330
inputReader.close();
13131331
}
13141332

1333+
@Override
1334+
public Size getBufferSize() {
1335+
return bufferSize;
1336+
}
1337+
}
1338+
1339+
private static final class TestTerminal extends ProgrammaticInTerminal {
1340+
private static Size computeSize() {
1341+
int h = DEFAULT_HEIGHT;
1342+
try {
1343+
String hp = System.getProperty("test.terminal.height");
1344+
if (hp != null && !hp.isEmpty() && System.getProperty("test.jdk") != null) {
1345+
h = Integer.parseInt(hp);
1346+
}
1347+
} catch (Throwable ex) {
1348+
// ignore
1349+
}
1350+
return new Size(80, h);
1351+
}
1352+
public TestTerminal(InputStream input, OutputStream output) throws Exception {
1353+
this(input, output, computeSize());
1354+
}
1355+
private TestTerminal(InputStream input, OutputStream output, Size size) throws Exception {
1356+
super(input, output, "ansi", size, size);
1357+
}
13151358
}
13161359

13171360
private static final class CompletionState {

src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@
138138
*/
139139
public class JShellTool implements MessageHandler {
140140

141+
private static String PROMPT = "\u0005";
142+
private static String CONTINUATION_PROMPT = "\u0006";
141143
private static final Pattern LINEBREAK = Pattern.compile("\\R");
142144
private static final Pattern ID = Pattern.compile("[se]?\\d+([-\\s].*)?");
143145
private static final Pattern RERUN_ID = Pattern.compile("/" + ID.pattern());
@@ -158,6 +160,7 @@ public class JShellTool implements MessageHandler {
158160
final PersistentStorage prefs;
159161
final Map<String, String> envvars;
160162
final Locale locale;
163+
final boolean interactiveTerminal;
161164

162165
final Feedback feedback = new Feedback();
163166

@@ -177,7 +180,8 @@ public class JShellTool implements MessageHandler {
177180
JShellTool(InputStream cmdin, PrintStream cmdout, PrintStream cmderr,
178181
PrintStream console,
179182
InputStream userin, PrintStream userout, PrintStream usererr,
180-
PersistentStorage prefs, Map<String, String> envvars, Locale locale) {
183+
PersistentStorage prefs, Map<String, String> envvars, Locale locale,
184+
boolean interactiveTerminal) {
181185
this.cmdin = cmdin;
182186
this.cmdout = cmdout;
183187
this.cmderr = cmderr;
@@ -193,6 +197,7 @@ public int read() throws IOException {
193197
this.prefs = prefs;
194198
this.envvars = envvars;
195199
this.locale = locale;
200+
this.interactiveTerminal = interactiveTerminal;
196201
}
197202

198203
private ResourceBundle versionRB = null;
@@ -974,7 +979,7 @@ public void run() {
974979
};
975980
Runtime.getRuntime().addShutdownHook(shutdownHook);
976981
// execute from user input
977-
try (IOContext in = new ConsoleIOContext(this, cmdin, console)) {
982+
try (IOContext in = new ConsoleIOContext(this, cmdin, console, interactiveTerminal)) {
978983
int indent;
979984
try {
980985
String indentValue = indent();
@@ -1256,12 +1261,12 @@ private String getInput(String initial) throws IOException{
12561261
return src;
12571262
}
12581263
String firstLinePrompt = interactive()
1259-
? testPrompt ? " \005"
1264+
? testPrompt ? PROMPT
12601265
: feedback.getPrompt(currentNameSpace.tidNext())
12611266
: "" // Non-interactive -- no prompt
12621267
;
12631268
String continuationPrompt = interactive()
1264-
? testPrompt ? " \006"
1269+
? testPrompt ? CONTINUATION_PROMPT
12651270
: feedback.getContinuationPrompt(currentNameSpace.tidNext())
12661271
: "" // Non-interactive -- no prompt
12671272
;

src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellToolBuilder.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public class JShellToolBuilder implements JavaShellToolBuilder {
5151
private PersistentStorage prefs = null;
5252
private Map<String, String> vars = null;
5353
private Locale locale = Locale.getDefault();
54+
private boolean interactiveTerminal;
5455
private boolean capturePrompt = false;
5556

5657
/**
@@ -208,6 +209,12 @@ public JavaShellToolBuilder promptCapture(boolean capture) {
208209
return this;
209210
}
210211

212+
@Override
213+
public JavaShellToolBuilder interactiveTerminal(boolean terminal) {
214+
this.interactiveTerminal = terminal;
215+
return this;
216+
}
217+
211218
/**
212219
* Create a tool instance for testing. Not in JavaShellToolBuilder.
213220
*
@@ -221,7 +228,7 @@ public JShellTool rawTool() {
221228
vars = System.getenv();
222229
}
223230
JShellTool sh = new JShellTool(cmdIn, cmdOut, cmdErr, console, userIn,
224-
userOut, userErr, prefs, vars, locale);
231+
userOut, userErr, prefs, vars, locale, interactiveTerminal);
225232
sh.testPrompt = capturePrompt;
226233
return sh;
227234
}

src/jdk.jshell/share/classes/jdk/jshell/tool/JavaShellToolBuilder.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,32 @@ static JavaShellToolBuilder builder() {
183183
*/
184184
JavaShellToolBuilder promptCapture(boolean capture);
185185

186+
/**
187+
* Set to true to specify the inputs and outputs are connected to an interactive terminal
188+
* that can interpret the ANSI escape codes. The characters sent to the output streams are
189+
* assumed to be interpreted by a terminal and shown to the user, and the exact order and nature
190+
* of characters sent to the outputs are unspecified.
191+
*
192+
* Set to false to specify a legacy simpler behavior whose output can be parsed by automatic
193+
* tools.
194+
*
195+
* When the input stream for this Java Shell is {@code System.in}, this value is ignored,
196+
* and the behavior is similar to specifying {@code true} in this method, but is more closely
197+
* following the specific terminal connected to {@code System.in}.
198+
*
199+
* @implSpec If this method is not called, the behavior should be
200+
* equivalent to calling {@code interactiveTerminal(false)}. The default implementation of
201+
* this method returns {@code this}.
202+
*
203+
* @param terminal if {@code true}, an terminal that can interpret the ANSI escape codes is
204+
* assumed to interpret the output. If {@code false}, a simpler output is selected.
205+
* @return the {@code JavaShellToolBuilder} instance
206+
* @since 17
207+
*/
208+
default JavaShellToolBuilder interactiveTerminal(boolean terminal) {
209+
return this;
210+
}
211+
186212
/**
187213
* Run an instance of the Java shell tool as configured by the other methods
188214
* in this interface. This call is not destructive, more than one call of

0 commit comments

Comments
 (0)