From 810c621d2187bef6530904464494daf760f4a679 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 21 Jun 2023 15:36:15 +0200 Subject: [PATCH] Backport d5a23099f5c820e0d45fe38578600d197a184b59 --- .../internal/org/jline/reader/Binding.java | 2 +- .../internal/org/jline/reader/Candidate.java | 51 ++- .../jline/reader/CompletingParsedLine.java | 2 +- .../org/jline/reader/CompletionMatcher.java | 2 +- .../org/jline/reader/Highlighter.java | 27 +- .../internal/org/jline/reader/History.java | 7 +- .../internal/org/jline/reader/LineReader.java | 7 +- .../org/jline/reader/LineReaderBuilder.java | 6 + .../jdk/internal/org/jline/reader/Parser.java | 10 +- .../reader/impl/CompletionMatcherImpl.java | 2 +- .../org/jline/reader/impl/DefaultParser.java | 146 ++++++- .../org/jline/reader/impl/LineReaderImpl.java | 59 +-- .../impl/completer/FileNameCompleter.java | 2 +- .../impl/completer/SystemCompleter.java | 2 +- .../reader/impl/history/DefaultHistory.java | 4 +- .../org/jline/terminal/TerminalBuilder.java | 402 +++++++++--------- .../org/jline/terminal/impl/AbstractPty.java | 5 - .../impl/AbstractWindowsTerminal.java | 33 +- .../org/jline/terminal/impl/Diag.java | 133 ++++++ .../org/jline/terminal/impl/ExecPty.java | 44 +- .../jline/terminal/impl/PosixPtyTerminal.java | 9 +- .../jline/terminal/impl/PosixSysTerminal.java | 10 +- .../impl/exec/ExecTerminalProvider.java | 133 ++++++ .../org/jline/terminal/spi/JansiSupport.java | 33 -- .../org/jline/terminal/spi/JnaSupport.java | 37 -- .../jline/terminal/spi/TerminalProvider.java | 84 ++++ .../jdk/internal/org/jline/utils/Colors.java | 2 +- .../jdk/internal/org/jline/utils/Curses.java | 12 +- .../jdk/internal/org/jline/utils/Display.java | 2 +- .../jdk/internal/org/jline/utils/InfoCmp.java | 2 +- .../internal/org/jline/utils/NonBlocking.java | 90 ++-- .../jline/utils/NonBlockingInputStream.java | 26 +- .../utils/NonBlockingInputStreamImpl.java | 13 +- .../utils/NonBlockingPumpInputStream.java | 39 +- .../jline/utils/NonBlockingPumpReader.java | 18 +- .../org/jline/utils/NonBlockingReader.java | 10 +- .../jline/utils/NonBlockingReaderImpl.java | 30 +- .../jdk/internal/org/jline/utils/OSUtils.java | 47 +- .../internal/org/jline/utils/PumpReader.java | 89 +++- .../org/jline/utils/StyleResolver.java | 2 +- .../jdk/internal/org/jline/utils/Timeout.java | 48 +++ .../jdk/internal/org/jline/utils/WCWidth.java | 5 +- .../internal/org/jline/utils/windows-vtp.caps | 2 +- .../share/classes/module-info.java | 3 - src/jdk.internal.le/share/legal/jline.md | 12 +- .../terminal/impl/jna/JnaSupportImpl.java | 77 ---- .../impl/jna/JnaTerminalProvider.java | 106 +++++ .../impl/jna/win/JnaWinConsoleWriter.java | 8 +- .../impl/jna/win/JnaWinSysTerminal.java | 72 ++-- .../jline/AbstractWindowsTerminalTest.java | 2 +- .../jdk/internal/jline/KeyConversionTest.java | 2 +- .../jdk/jdk/internal/jline/OSUtilsTest.java | 35 +- .../jdk/jshell/ExecPtyGetFlagsToSetTest.java | 8 +- 53 files changed, 1337 insertions(+), 677 deletions(-) create mode 100644 src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/Diag.java create mode 100644 src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/ExecTerminalProvider.java delete mode 100644 src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/JansiSupport.java delete mode 100644 src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/JnaSupport.java create mode 100644 src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/TerminalProvider.java create mode 100644 src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Timeout.java delete mode 100644 src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/JnaSupportImpl.java create mode 100644 src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/JnaTerminalProvider.java rename src/jdk.internal.le/windows/classes/module-info.java.extra => test/jdk/jdk/internal/jline/OSUtilsTest.java (53%) diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Binding.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Binding.java index ad10d1a773f..0070b55c22d 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Binding.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Binding.java @@ -14,7 +14,7 @@ * @see Macro * @see Reference * @see Widget - * @see org.jline.keymap.KeyMap + * @see jdk.internal.org.jline.keymap.KeyMap * * @author Guillaume Nodet */ diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Candidate.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Candidate.java index 51f449f20ee..0e95538492d 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Candidate.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Candidate.java @@ -24,6 +24,7 @@ public class Candidate implements Comparable { private final String suffix; private final String key; private final boolean complete; + private final int sort; /** * Simple constructor with only a single String as an argument. @@ -31,7 +32,7 @@ public class Candidate implements Comparable { * @param value the candidate */ public Candidate(String value) { - this(value, value, null, null, null, null, true); + this(value, value, null, null, null, null, true, 0); } /** @@ -44,8 +45,9 @@ public Candidate(String value) { * @param suffix the suffix * @param key the key * @param complete the complete flag + * @param sort the sort flag */ - public Candidate(String value, String displ, String group, String descr, String suffix, String key, boolean complete) { + public Candidate(String value, String displ, String group, String descr, String suffix, String key, boolean complete, int sort) { this.value = Objects.requireNonNull(value); this.displ = Objects.requireNonNull(displ); this.group = group; @@ -53,6 +55,22 @@ public Candidate(String value, String displ, String group, String descr, String this.suffix = suffix; this.key = key; this.complete = complete; + this.sort = sort; + } + + /** + * Constructs a new Candidate. + * + * @param value the value + * @param displ the display string + * @param group the group + * @param descr the description + * @param suffix the suffix + * @param key the key + * @param complete the complete flag + */ + public Candidate(String value, String displ, String group, String descr, String suffix, String key, boolean complete) { + this(value, displ, group, descr, suffix, key, complete, 0); } /** @@ -133,9 +151,36 @@ public boolean complete() { return complete; } + /** + * Integer used to override default sort logic. + * @return the sort int + */ + public int sort() { + return sort; + } + + @Override public int compareTo(Candidate o) { - return value.compareTo(o.value); + // If both candidates have same sort, use default behavior + if( sort == o.sort() ) { + return value.compareTo(o.value); + } else { + return Integer.compare(sort, o.sort()); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Candidate candidate = (Candidate) o; + return Objects.equals(value, candidate.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); } @Override diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletingParsedLine.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletingParsedLine.java index 1f98d6168f0..52a36b83896 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletingParsedLine.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletingParsedLine.java @@ -10,7 +10,7 @@ /** * An extension of {@link ParsedLine} that, being aware of the quoting and escaping rules - * of the {@link org.jline.reader.Parser} that produced it, knows if and how a completion candidate + * of the {@link jdk.internal.org.jline.reader.Parser} that produced it, knows if and how a completion candidate * should be escaped/quoted. * * @author Eric Bottard diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletionMatcher.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletionMatcher.java index 3cfd73fa19a..58117c8f213 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletionMatcher.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/CompletionMatcher.java @@ -29,7 +29,7 @@ void compile(Map options, boolean prefix, Completing /** * * @param candidates list of candidates - * @return a map of candidates that completion matcher matches + * @return a list of candidates that completion matcher matches */ List matches(List candidates); diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Highlighter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Highlighter.java index 81266c7cda5..136fb20324b 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Highlighter.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Highlighter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002-2019, the original author or authors. + * Copyright (c) 2002-2021, the original author or authors. * * This software is distributable under the BSD license. See the terms of the * BSD license in the documentation provided with this software. @@ -14,7 +14,28 @@ public interface Highlighter { + /** + * Highlight buffer + * @param reader LineReader + * @param buffer the buffer to be highlighted + * @return highlighted buffer + */ AttributedString highlight(LineReader reader, String buffer); - public void setErrorPattern(Pattern errorPattern); - public void setErrorIndex(int errorIndex); + + /** + * Refresh highlight configuration + */ + default void refresh(LineReader reader) {} + + /** + * Set error pattern to be highlighted + * @param errorPattern error pattern to be highlighted + */ + void setErrorPattern(Pattern errorPattern); + + /** + * Set error index to be highlighted + * @param errorIndex error index to be highlighted + */ + void setErrorIndex(int errorIndex); } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/History.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/History.java index 3dd078cc745..7a669edddb5 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/History.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/History.java @@ -61,12 +61,13 @@ public interface History extends Iterable void append(Path file, boolean incremental) throws IOException; /** - * Read history from the file. If incremental only the events that are not contained within the internal list are added. + * Read history from the file. If checkDuplicates is true only the events that + * are not contained within the internal list are added. * @param file History file - * @param incremental If true incremental read operation is performed. + * @param checkDuplicates If true, duplicate history entries will be discarded * @throws IOException if a problem occurs */ - void read(Path file, boolean incremental) throws IOException; + void read(Path file, boolean checkDuplicates) throws IOException; /** * Purge history. diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReader.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReader.java index 07b72668721..03729853ca5 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReader.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReader.java @@ -352,7 +352,7 @@ public interface LineReader { String AMBIGUOUS_BINDING = "ambiguous-binding"; /** - * Columns separated list of patterns that will not be saved in history. + * Colon separated list of patterns that will not be saved in history. */ String HISTORY_IGNORE = "history-ignore"; @@ -467,6 +467,9 @@ enum Option { /** Show command options tab completion candidates for zero length word */ EMPTY_WORD_OPTIONS(true), + + /** Disable the undo feature */ + DISABLE_UNDO ; private final boolean def; @@ -699,7 +702,7 @@ enum SuggestionType { void runMacro(String macro); /** - * Read a mouse event when the {@link org.jline.utils.InfoCmp.Capability#key_mouse} sequence + * Read a mouse event when the {@link jdk.internal.org.jline.utils.InfoCmp.Capability#key_mouse} sequence * has just been read on the input stream. * Compared to {@link Terminal#readMouseEvent()}, this method takes into account keys * that have been pushed back using {@link #runMacro(String)}. diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReaderBuilder.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReaderBuilder.java index 422561a86c5..14d5a0e8b1c 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReaderBuilder.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReaderBuilder.java @@ -118,6 +118,12 @@ public LineReader build() { throw new IOError(e); } } + + String appName = this.appName; + if (null == appName) { + appName = terminal.getName(); + } + LineReaderImpl reader = new LineReaderImpl(terminal, appName, variables); if (history != null) { reader.setHistory(history); diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Parser.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Parser.java index 2e2ad8c24bc..1f7df67d573 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Parser.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/Parser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002-2020, the original author or authors. + * Copyright (c) 2002-2021, the original author or authors. * * This software is distributable under the BSD license. See the terms of the * BSD license in the documentation provided with this software. @@ -12,8 +12,8 @@ import java.util.regex.Pattern; public interface Parser { - static final String REGEX_VARIABLE = "[a-zA-Z_]{1,}[a-zA-Z0-9_-]*"; - static final String REGEX_COMMAND = "[:]{0,1}[a-zA-Z]{1,}[a-zA-Z0-9_-]*"; + String REGEX_VARIABLE = "[a-zA-Z_]+[a-zA-Z0-9_-]*"; + String REGEX_COMMAND = "[:]?[a-zA-Z]+[a-zA-Z0-9_-]*"; ParsedLine parse(String line, int cursor, ParseContext context) throws SyntaxError; @@ -34,7 +34,7 @@ default boolean validVariableName(String name) { } default String getCommand(final String line) { - String out = ""; + String out; Pattern patternCommand = Pattern.compile("^\\s*" + REGEX_VARIABLE + "=(" + REGEX_COMMAND + ")(\\s+|$)"); Matcher matcher = patternCommand.matcher(line); if (matcher.find()) { @@ -68,7 +68,7 @@ enum ParseContext { /** Parsed words will have all characters present in input line * including quotes and escape chars. - * May throw EOFError in which case we have incomplete input. + * We should tolerate and ignore errors. */ SPLIT_LINE, diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/CompletionMatcherImpl.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/CompletionMatcherImpl.java index 0d4ead5743d..f829f13d51b 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/CompletionMatcherImpl.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/CompletionMatcherImpl.java @@ -54,7 +54,7 @@ public List matches(List candidates) { break; } } - return !matching.isEmpty() ? matching.entrySet().stream().flatMap(e -> e.getValue().stream()).collect(Collectors.toList()) + return !matching.isEmpty() ? matching.entrySet().stream().flatMap(e -> e.getValue().stream()).distinct().collect(Collectors.toList()) : new ArrayList<>(); } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultParser.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultParser.java index 4fb616f61dc..d9b3a9948d5 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultParser.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/DefaultParser.java @@ -27,6 +27,27 @@ public enum Bracket { ANGLE // <> } + public static class BlockCommentDelims { + private final String start; + private final String end; + public BlockCommentDelims(String start, String end) { + if (start == null || end == null + || start.isEmpty() || end.isEmpty() || start.equals(end)) { + throw new IllegalArgumentException("Bad block comment delimiter!"); + } + this.start = start; + this.end = end; + } + + public String getStart() { + return start; + } + + public String getEnd() { + return end; + } + } + private char[] quoteChars = {'\'', '"'}; private char[] escapeChars = {'\\'}; @@ -39,6 +60,10 @@ public enum Bracket { private char[] closingBrackets = null; + private String[] lineCommentDelims = null; + + private BlockCommentDelims blockCommentDelims = null; + private String regexVariable = "[a-zA-Z_]+[a-zA-Z0-9_-]*((\\.|\\['|\\[\"|\\[)[a-zA-Z0-9_-]*(|']|\"]|]))?"; private String regexCommand = "[:]?[a-zA-Z]+[a-zA-Z0-9_-]*"; private int commandGroup = 4; @@ -47,6 +72,16 @@ public enum Bracket { // Chainable setters // + public DefaultParser lineCommentDelims(final String[] lineCommentDelims) { + this.lineCommentDelims = lineCommentDelims; + return this; + } + + public DefaultParser blockCommentDelims(final BlockCommentDelims blockCommentDelims) { + this.blockCommentDelims = blockCommentDelims; + return this; + } + public DefaultParser quoteChars(final char[] chars) { this.quoteChars = chars; return this; @@ -107,6 +142,22 @@ public char[] getEscapeChars() { return this.escapeChars; } + public void setLineCommentDelims(String[] lineCommentDelims) { + this.lineCommentDelims = lineCommentDelims; + } + + public String[] getLineCommentDelims() { + return this.lineCommentDelims; + } + + public void setBlockCommentDelims(BlockCommentDelims blockCommentDelims) { + this.blockCommentDelims = blockCommentDelims; + } + + public BlockCommentDelims getBlockCommentDelims() { + return blockCommentDelims; + } + public void setEofOnUnclosedQuote(boolean eofOnUnclosedQuote) { this.eofOnUnclosedQuote = eofOnUnclosedQuote; } @@ -225,6 +276,11 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex int rawWordStart = 0; BracketChecker bracketChecker = new BracketChecker(cursor); boolean quotedWord = false; + boolean lineCommented = false; + boolean blockCommented = false; + boolean blockCommentInRightOrder = true; + final String blockCommentEnd = blockCommentDelims == null ? null : blockCommentDelims.end; + final String blockCommentStart = blockCommentDelims == null ? null : blockCommentDelims.start; for (int i = 0; (line != null) && (i < line.length()); i++) { // once we reach the cursor, set the @@ -237,7 +293,7 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex rawWordCursor = i - rawWordStart; } - if (quoteStart < 0 && isQuoteChar(line, i)) { + if (quoteStart < 0 && isQuoteChar(line, i) && !lineCommented && !blockCommented) { // Start a quote block quoteStart = i; if (current.length()==0) { @@ -258,17 +314,40 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex quoteStart = -1; quotedWord = false; } else if (quoteStart < 0 && isDelimiter(line, i)) { - // Delimiter - if (current.length() > 0) { - words.add(current.toString()); - current.setLength(0); // reset the arg - if (rawWordCursor >= 0 && rawWordLength < 0) { - rawWordLength = i - rawWordStart; + if (lineCommented) { + if (isCommentDelim(line, i, System.lineSeparator())) { + lineCommented = false; + } + } else if (blockCommented) { + if (isCommentDelim(line, i, blockCommentEnd)) { + blockCommented = false; } + } else { + // Delimiter + rawWordLength = handleDelimiterAndGetRawWordLength(current, words, rawWordStart, rawWordCursor, rawWordLength, i); + rawWordStart = i + 1; } - rawWordStart = i + 1; } else { - if (!isEscapeChar(line, i)) { + if (quoteStart < 0 && !blockCommented && (lineCommented || isLineCommentStarted(line, i))) { + lineCommented = true; + } else if (quoteStart < 0 && !lineCommented + && (blockCommented || isCommentDelim(line, i, blockCommentStart))) { + if (blockCommented) { + if (blockCommentEnd != null && isCommentDelim(line, i, blockCommentEnd)) { + blockCommented = false; + i += blockCommentEnd.length() - 1; + } + } else { + blockCommented = true; + rawWordLength = handleDelimiterAndGetRawWordLength(current, words, rawWordStart, rawWordCursor, rawWordLength, i); + i += blockCommentStart == null ? 0 : blockCommentStart.length() - 1; + rawWordStart = i + 1; + } + } else if (quoteStart < 0 && !lineCommented + && isCommentDelim(line, i, blockCommentEnd)) { + current.append(line.charAt(i)); + blockCommentInRightOrder = false; + } else if (!isEscapeChar(line, i)) { current.append(line.charAt(i)); if (quoteStart < 0) { bracketChecker.check(line, i); @@ -301,6 +380,14 @@ public ParsedLine parse(final String line, final int cursor, ParseContext contex throw new EOFError(-1, -1, "Missing closing quote", line.charAt(quoteStart) == '\'' ? "quote" : "dquote"); } + if (blockCommented) { + throw new EOFError(-1, -1, "Missing closing block comment delimiter", + "add: " + blockCommentEnd); + } + if (!blockCommentInRightOrder) { + throw new EOFError(-1, -1, "Missing opening block comment delimiter", + "missing: " + blockCommentStart); + } if (bracketChecker.isClosingBracketMissing() || bracketChecker.isOpeningBracketMissing()) { String message = null; String missing = null; @@ -333,6 +420,17 @@ public boolean isDelimiter(final CharSequence buffer, final int pos) { return !isQuoted(buffer, pos) && !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos); } + private int handleDelimiterAndGetRawWordLength(StringBuilder current, List words, int rawWordStart, int rawWordCursor, int rawWordLength, int pos) { + if (current.length() > 0) { + words.add(current.toString()); + current.setLength(0); // reset the arg + if (rawWordCursor >= 0 && rawWordLength < 0) { + return pos - rawWordStart; + } + } + return rawWordLength; + } + public boolean isQuoted(final CharSequence buffer, final int pos) { return false; } @@ -351,6 +449,36 @@ public boolean isQuoteChar(final CharSequence buffer, final int pos) { return false; } + private boolean isCommentDelim(final CharSequence buffer, final int pos, final String pattern) { + if (pos < 0) { + return false; + } + + if (pattern != null) { + final int length = pattern.length(); + if (length <= buffer.length() - pos) { + for (int i = 0; i < length; i++) { + if (pattern.charAt(i) != buffer.charAt(pos + i)) { + return false; + } + } + return true; + } + } + return false; + } + + public boolean isLineCommentStarted(final CharSequence buffer, final int pos) { + if (lineCommentDelims != null) { + for (String comment: lineCommentDelims) { + if (isCommentDelim(buffer, pos, comment)) { + return true; + } + } + } + return false; + } + @Override public boolean isEscapeChar(char ch) { if (escapeChars != null) { diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java index b32dde1d902..ee8a434e1a2 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002-2020, the original author or authors. + * Copyright (c) 2002-2022, the original author or authors. * * This software is distributable under the BSD license. See the terms of the * BSD license in the documentation provided with this software. @@ -281,7 +281,7 @@ protected enum BellType { int candidateStartPosition = 0; public LineReaderImpl(Terminal terminal) throws IOException { - this(terminal, null, null); + this(terminal, terminal.getName(), null); } public LineReaderImpl(Terminal terminal, String appName) throws IOException { @@ -633,7 +633,8 @@ public String readLine(String prompt, String rightPrompt, MaskingCallback maskin callWidget(CALLBACK_INIT); - undo.newState(buf.copy()); + if (!isSet(Option.DISABLE_UNDO)) + undo.newState(buf.copy()); // Draw initial prompt redrawLine(); @@ -679,7 +680,7 @@ public String readLine(String prompt, String rightPrompt, MaskingCallback maskin if (!w.apply()) { beep(); } - if (!isUndo && copy != null && buf.length() <= getInt(FEATURES_MAX_BUFFER_SIZE, DEFAULT_FEATURES_MAX_BUFFER_SIZE) + if (!isSet(Option.DISABLE_UNDO) && !isUndo && copy != null && buf.length() <= getInt(FEATURES_MAX_BUFFER_SIZE, DEFAULT_FEATURES_MAX_BUFFER_SIZE) && !copy.toString().equals(buf.toString())) { undo.newState(buf.copy()); } @@ -739,8 +740,8 @@ public String readLine(String prompt, String rightPrompt, MaskingCallback maskin } } finally { lock.unlock(); + startedReading.set(false); } - startedReading.set(false); } } @@ -1082,18 +1083,18 @@ public void editAndAddInBuffer(File file) throws Exception { if (isSet(Option.BRACKETED_PASTE)) { terminal.writer().write(BRACKETED_PASTE_OFF); } - Constructor ctor = Class.forName("org.jline.builtins.Nano").getConstructor(Terminal.class, File.class); + Constructor ctor = Class.forName("jdk.internal.org.jline.builtins.Nano").getConstructor(Terminal.class, File.class); Editor editor = (Editor) ctor.newInstance(terminal, new File(file.getParent())); editor.setRestricted(true); editor.open(Collections.singletonList(file.getName())); editor.run(); - BufferedReader br = new BufferedReader(new FileReader(file)); - String line; - commandsBuffer.clear(); - while ((line = br.readLine()) != null) { - commandsBuffer.add(line); + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + String line; + commandsBuffer.clear(); + while ((line = br.readLine()) != null) { + commandsBuffer.add(line); + } } - br.close(); } // @@ -3595,9 +3596,9 @@ protected boolean editAndExecute() { File file = null; try { file = File.createTempFile("jline-execute-", null); - FileWriter writer = new FileWriter(file); - writer.write(buf.toString()); - writer.close(); + try (FileWriter writer = new FileWriter(file)) { + writer.write(buf.toString()); + } editAndAddInBuffer(file); } catch (Exception e) { e.printStackTrace(terminal.writer()); @@ -3796,6 +3797,9 @@ protected void redisplay(boolean flush) { Status status = Status.getStatus(terminal, false); if (status != null) { + if (terminal.getType().startsWith(AbstractWindowsTerminal.TYPE_WINDOWS)) { + status.resize(); + } status.redraw(); } @@ -3947,7 +3951,8 @@ private String matchPreviousCommand(String buffer) { StringBuilder sb = new StringBuilder(); for (char c: buffer.replace("\\", "\\\\").toCharArray()) { if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '^' || c == '*' - || c == '$' || c == '.' || c == '?' || c == '+') { + || c == '$' || c == '.' || c == '?' || c == '+' || c == '|' || c == '<' || c == '>' || c == '!' + || c == '-') { sb.append('\\'); } sb.append(c); @@ -4520,7 +4525,7 @@ else if (isSet(Option.RECOGNIZE_EXACT)) { } } - private CompletingParsedLine wrap(ParsedLine line) { + protected static CompletingParsedLine wrap(ParsedLine line) { if (line instanceof CompletingParsedLine) { return (CompletingParsedLine) line; } else { @@ -4625,6 +4630,11 @@ private int displayRows(Status status) { return size.getRows() - (status != null ? status.size() : 0); } + private int visibleDisplayRows() { + Status status = Status.getStatus(terminal, false); + return terminal.getSize().getRows() - (status != null ? status.size() : 0); + } + private int promptLines() { AttributedString text = insertSecondaryPrompts(AttributedStringBuilder.append(prompt, buf.toString()), new ArrayList<>()); return text.columnSplitLength(size.getColumns(), false, display.delayLineWrap()).size(); @@ -5070,18 +5080,19 @@ protected PostResult computePost(List possible, Candidate selection, protected PostResult computePost(List possible, Candidate selection, List ordered, String completed, Function wcwidth, int width, boolean autoGroup, boolean groupName, boolean rowsFirst) { List strings = new ArrayList<>(); + boolean customOrder = possible.stream().anyMatch(c -> c.sort() != 0); if (groupName) { Comparator groupComparator = getGroupComparator(); - Map> sorted; + Map> sorted; sorted = groupComparator != null ? new TreeMap<>(groupComparator) : new LinkedHashMap<>(); for (Candidate cand : possible) { String group = cand.group(); sorted.computeIfAbsent(group != null ? group : "", s -> new LinkedHashMap<>()) - .put(cand.value(), cand); + .put((customOrder ? cand.sort() : cand.value()), cand); } - for (Map.Entry> entry : sorted.entrySet()) { + for (Map.Entry> entry : sorted.entrySet()) { String group = entry.getKey(); if (group.isEmpty() && sorted.size() > 1) { group = getOthersGroupName(); @@ -5096,13 +5107,13 @@ protected PostResult computePost(List possible, Candidate selection, } } else { Set groups = new LinkedHashSet<>(); - TreeMap sorted = new TreeMap<>(); + TreeMap sorted = new TreeMap<>(); for (Candidate cand : possible) { String group = cand.group(); if (group != null) { groups.add(group); } - sorted.put(cand.value(), cand); + sorted.put((customOrder ? cand.sort() : cand.value()), cand); } if (autoGroup) { strings.addAll(groups); @@ -5129,7 +5140,7 @@ public TerminalLine(String line, int startPos, int width) { this.startPos = startPos; endLine = line.substring(line.lastIndexOf('\n') + 1); boolean first = true; - while (endLine.length() + (first ? startPos : 0) > width) { + while (endLine.length() + (first ? startPos : 0) > width && width > 0) { if (first) { endLine = endLine.substring(width - startPos); } else { @@ -5207,7 +5218,7 @@ else if (item instanceof List) { AttributedStringBuilder sb = new AttributedStringBuilder(); if (listSize > 0) { if (isSet(Option.AUTO_MENU_LIST) - && listSize < Math.min(getInt(MENU_LIST_MAX, DEFAULT_MENU_LIST_MAX), displayRows() - promptLines())) { + && listSize < Math.min(getInt(MENU_LIST_MAX, DEFAULT_MENU_LIST_MAX), visibleDisplayRows() - promptLines())) { maxWidth = Math.max(maxWidth, MENU_LIST_WIDTH); sb.tabs(Math.max(Math.min(candidateStartPosition, width - maxWidth - 1), 1)); width = maxWidth + 2; diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/FileNameCompleter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/FileNameCompleter.java index 0ac3ad0b7e7..3d173f72ec7 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/FileNameCompleter.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/FileNameCompleter.java @@ -41,7 +41,7 @@ * @author Marc Prud'hommeaux * @author Jason Dillon * @since 2.3 - * @deprecated use org.jline.builtins.Completers$FileNameCompleter instead + * @deprecated use jdk.internal.org.jline.builtins.Completers$FileNameCompleter instead */ @Deprecated public class FileNameCompleter implements Completer diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/SystemCompleter.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/SystemCompleter.java index 691708e1389..a17a356bf0e 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/SystemCompleter.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/completer/SystemCompleter.java @@ -67,7 +67,7 @@ private String command(String cmd) { if (cmd != null) { if (completers.containsKey(cmd)) { out = cmd; - } else if (aliasCommand.containsKey(cmd)) { + } else { out = aliasCommand.get(cmd); } } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/history/DefaultHistory.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/history/DefaultHistory.java index a2a0f575136..cdd1bc520dd 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/history/DefaultHistory.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/history/DefaultHistory.java @@ -97,14 +97,14 @@ public void load() throws IOException { } @Override - public void read(Path file, boolean incremental) throws IOException { + public void read(Path file, boolean checkDuplicates) throws IOException { Path path = file != null ? file : getPath(); if (path != null) { try { if (Files.exists(path)) { Log.trace("Reading history from: ", path); try (BufferedReader reader = Files.newBufferedReader(path)) { - reader.lines().forEach(line -> addHistoryLine(path, line, incremental)); + reader.lines().forEach(line -> addHistoryLine(path, line, checkDuplicates)); setHistoryFileData(path, new HistoryFileData(items.size(), offset + items.size())); maybeResize(); } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/TerminalBuilder.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/TerminalBuilder.java index 88970b88592..8c047e37342 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/TerminalBuilder.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/TerminalBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002-2020, the original author or authors. + * Copyright (c) 2002-2021, the original author or authors. * * This software is distributable under the BSD license. See the terms of the * BSD license in the documentation provided with this software. @@ -16,22 +16,24 @@ import java.io.OutputStream; import java.lang.reflect.Method; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.ServiceLoader; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; import jdk.internal.org.jline.terminal.impl.AbstractPosixTerminal; import jdk.internal.org.jline.terminal.impl.AbstractTerminal; +import jdk.internal.org.jline.terminal.impl.AbstractWindowsTerminal; import jdk.internal.org.jline.terminal.impl.DumbTerminal; -import jdk.internal.org.jline.terminal.impl.ExecPty; -import jdk.internal.org.jline.terminal.impl.ExternalTerminal; -import jdk.internal.org.jline.terminal.impl.PosixPtyTerminal; -import jdk.internal.org.jline.terminal.impl.PosixSysTerminal; -import jdk.internal.org.jline.terminal.spi.JansiSupport; -import jdk.internal.org.jline.terminal.spi.JnaSupport; -import jdk.internal.org.jline.terminal.spi.Pty; +import jdk.internal.org.jline.terminal.spi.TerminalProvider; import jdk.internal.org.jline.utils.Log; import jdk.internal.org.jline.utils.OSUtils; @@ -52,6 +54,11 @@ public final class TerminalBuilder { public static final String PROP_EXEC = "org.jline.terminal.exec"; public static final String PROP_DUMB = "org.jline.terminal.dumb"; public static final String PROP_DUMB_COLOR = "org.jline.terminal.dumb.color"; + public static final String PROP_OUTPUT = "org.jline.terminal.output"; + public static final String PROP_OUTPUT_OUT = "out"; + public static final String PROP_OUTPUT_ERR = "err"; + public static final String PROP_OUTPUT_OUT_ERR = "out-err"; + public static final String PROP_OUTPUT_ERR_OUT = "err-out"; // // Other system properties controlling various jline parts @@ -61,6 +68,16 @@ public final class TerminalBuilder { public static final String PROP_COLOR_DISTANCE = "org.jline.utils.colorDistance"; public static final String PROP_DISABLE_ALTERNATE_CHARSET = "org.jline.utils.disableAlternateCharset"; + // + // Terminal output control + // + public enum SystemOutput { + SysOut, + SysErr, + SysOutOrSysErr, + SysErrOrSysOut + } + /** * Returns the default system terminal. * Terminals should be closed properly using the {@link Terminal#close()} @@ -97,6 +114,7 @@ public static TerminalBuilder builder() { private Charset encoding; private int codepage; private Boolean system; + private SystemOutput systemOutput; private Boolean jna; private Boolean jansi; private Boolean exec; @@ -128,6 +146,20 @@ public TerminalBuilder system(boolean system) { return this; } + /** + * Indicates which standard stream should be used when displaying to the terminal. + * The default is to use the system output stream. + * Building a system terminal will fail if one of the stream specified is not linked + * to the controlling terminal. + * + * @param systemOutput The mode to choose the output stream. + * @return The builder. + */ + public TerminalBuilder systemOutput(SystemOutput systemOutput) { + this.systemOutput = systemOutput; + return this; + } + public TerminalBuilder jna(boolean jna) { this.jna = jna; return this; @@ -298,11 +330,18 @@ private Terminal doBuild() throws IOException { encoding = Charset.forName(charsetName); } } - int codepage = this.codepage; - if (codepage <= 0) { - String str = System.getProperty(PROP_CODEPAGE); - if (str != null) { - codepage = Integer.parseInt(str); + if (encoding == null) { + int codepage = this.codepage; + if (codepage <= 0) { + String str = System.getProperty(PROP_CODEPAGE); + if (str != null) { + codepage = Integer.parseInt(str); + } + } + if (codepage >= 0) { + encoding = getCodepageCharset(codepage); + } else { + encoding = StandardCharsets.UTF_8; } } String type = this.type; @@ -328,102 +367,112 @@ private Terminal doBuild() throws IOException { if (dumb == null) { dumb = getBoolean(PROP_DUMB, null); } + IllegalStateException exception = new IllegalStateException("Unable to create a terminal"); + List providers = new ArrayList<>(); + if (jna) { + try { + TerminalProvider provider = TerminalProvider.load("jna"); + providers.add(provider); + } catch (Throwable t) { + Log.debug("Unable to load JNA support: ", t); + exception.addSuppressed(t); + } + } + if (jansi) { + try { + TerminalProvider provider = TerminalProvider.load("jansi"); + providers.add(provider); + } catch (Throwable t) { + Log.debug("Unable to load JANSI support: ", t); + exception.addSuppressed(t); + } + } + if (exec) + { + try { + TerminalProvider provider = TerminalProvider.load("exec"); + providers.add(provider); + } catch (Throwable t) { + Log.debug("Unable to load EXEC support: ", t); + exception.addSuppressed(t); + } + } + + Terminal terminal = null; if ((system != null && system) || (system == null && in == null && out == null)) { - if (system != null && ((in != null && !in.equals(System.in)) || (out != null && !out.equals(System.out)))) { + if (system != null && ((in != null && !in.equals(System.in)) || + (out != null && !out.equals(System.out) && !out.equals(System.err)))) { throw new IllegalArgumentException("Cannot create a system terminal using non System streams"); } - Terminal terminal = null; - IllegalStateException exception = new IllegalStateException("Unable to create a system terminal"); - TerminalBuilderSupport tbs = new TerminalBuilderSupport(jna, jansi); - if (tbs.isConsoleInput() && tbs.isConsoleOutput()) { + if (attributes != null || size != null) { + Log.warn("Attributes and size fields are ignored when creating a system terminal"); + } + if (out != null) { + if (out.equals(System.out)) { + systemOutput = SystemOutput.SysOut; + } else if (out.equals(System.err)) { + systemOutput = SystemOutput.SysErr; + } + } + if (systemOutput == null) { + String str = System.getProperty(PROP_OUTPUT); + if (str != null) { + switch (str.trim().toLowerCase(Locale.ROOT)) { + case PROP_OUTPUT_OUT: systemOutput = SystemOutput.SysOut; break; + case PROP_OUTPUT_ERR: systemOutput = SystemOutput.SysErr; break; + case PROP_OUTPUT_OUT_ERR: systemOutput = SystemOutput.SysOutOrSysErr; break; + case PROP_OUTPUT_ERR_OUT: systemOutput = SystemOutput.SysErrOrSysOut; break; + default: + Log.debug("Unsupported value for " + PROP_OUTPUT + ": " + str + ". Supported values are: " + + String.join(", ", PROP_OUTPUT_OUT, PROP_OUTPUT_ERR, PROP_OUTPUT_OUT_ERR,PROP_OUTPUT_ERR_OUT) + + "."); + } + } + } + if (systemOutput == null) { + systemOutput = SystemOutput.SysOutOrSysErr; + } + Map system = Stream.of(TerminalProvider.Stream.values()) + .collect(Collectors.toMap(stream -> stream, stream -> providers.stream().anyMatch(p -> p.isSystemStream(stream)))); + TerminalProvider.Stream console = select(system, systemOutput); + + if (system.get(TerminalProvider.Stream.Input) && console != null) { if (attributes != null || size != null) { Log.warn("Attributes and size fields are ignored when creating a system terminal"); } - if (OSUtils.IS_WINDOWS) { - if (!OSUtils.IS_CYGWIN && !OSUtils.IS_MSYSTEM) { - boolean ansiPassThrough = OSUtils.IS_CONEMU; - if (tbs.hasJnaSupport()) { - try { - terminal = tbs.getJnaSupport().winSysTerminal(name, type, ansiPassThrough, encoding, codepage - , nativeSignals, signalHandler, paused, inputStreamWrapper); - } catch (Throwable t) { - Log.debug("Error creating JNA based terminal: ", t.getMessage(), t); - exception.addSuppressed(t); - } - } - if (terminal == null && tbs.hasJansiSupport()) { - try { - terminal = tbs.getJansiSupport().winSysTerminal(name, type, ansiPassThrough, encoding, codepage - , nativeSignals, signalHandler, paused); - } catch (Throwable t) { - Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t); - exception.addSuppressed(t); - } - } - } else if (exec) { - // - // Cygwin support - // - try { - // Cygwin defaults to XTERM, but actually supports 256 colors, - // so if the value comes from the environment, change it to xterm-256color - if ("xterm".equals(type) && this.type == null && System.getProperty(PROP_TYPE) == null) { - type = "xterm-256color"; - } - Pty pty = tbs.getExecPty(); - terminal = new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler); - } catch (IOException e) { - // Ignore if not a tty - Log.debug("Error creating EXEC based terminal: ", e.getMessage(), e); - exception.addSuppressed(e); - } - } - if (terminal == null && !jna && !jansi && (dumb == null || !dumb)) { - throw new IllegalStateException("Unable to create a system terminal. On windows, either " - + "JNA or JANSI library is required. Make sure to add one of those in the classpath."); - } - } else { - if (tbs.hasJnaSupport()) { - try { - Pty pty = tbs.getJnaSupport().current(); - terminal = new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler); - } catch (Throwable t) { - // ignore - Log.debug("Error creating JNA based terminal: ", t.getMessage(), t); - exception.addSuppressed(t); - } - } - if (terminal == null && tbs.hasJansiSupport()) { - try { - Pty pty = tbs.getJansiSupport().current(); - terminal = new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler); - } catch (Throwable t) { - Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t); - exception.addSuppressed(t); - } - } - if (terminal == null && exec) { + boolean ansiPassThrough = OSUtils.IS_CONEMU; + // Cygwin defaults to XTERM, but actually supports 256 colors, + // so if the value comes from the environment, change it to xterm-256color + if ((OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) && "xterm".equals(type) + && this.type == null && System.getProperty(PROP_TYPE) == null) { + type = "xterm-256color"; + } + for ( TerminalProvider provider : providers) { + if (terminal == null) { try { - Pty pty = tbs.getExecPty(); - terminal = new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler); + terminal = provider.sysTerminal(name, type, ansiPassThrough, encoding, + nativeSignals, signalHandler, paused, console, inputStreamWrapper); } catch (Throwable t) { - // Ignore if not a tty - Log.debug("Error creating EXEC based terminal: ", t.getMessage(), t); + Log.debug("Error creating " + provider.name() + " based terminal: ", t.getMessage(), t); exception.addSuppressed(t); } } } - if (terminal instanceof AbstractTerminal) { - AbstractTerminal t = (AbstractTerminal) terminal; - if (SYSTEM_TERMINAL.compareAndSet(null, t)) { - t.setOnClose(() -> SYSTEM_TERMINAL.compareAndSet(t, null)); - } else { - exception.addSuppressed(new IllegalStateException("A system terminal is already running. " + - "Make sure to use the created system Terminal on the LineReaderBuilder if you're using one " + - "or that previously created system Terminals have been correctly closed.")); - terminal.close(); - terminal = null; - } + if (terminal == null && OSUtils.IS_WINDOWS && !jna && !jansi && (dumb == null || !dumb)) { + throw new IllegalStateException("Unable to create a system terminal. On windows, either " + + "JNA or JANSI library is required. Make sure to add one of those in the classpath."); + } + } + if (terminal instanceof AbstractTerminal) { + AbstractTerminal t = (AbstractTerminal) terminal; + if (SYSTEM_TERMINAL.compareAndSet(null, t)) { + t.setOnClose(() -> SYSTEM_TERMINAL.compareAndSet(t, null)); + } else { + exception.addSuppressed(new IllegalStateException("A system terminal is already running. " + + "Make sure to use the created system Terminal on the LineReaderBuilder if you're using one " + + "or that previously created system Terminals have been correctly closed.")); + terminal.close(); + terminal = null; } } if (terminal == null && (dumb == null || dumb)) { @@ -433,7 +482,8 @@ private Terminal doBuild() throws IOException { color = getBoolean(PROP_DUMB_COLOR, false); // detect emacs using the env variable if (!color) { - color = System.getenv("INSIDE_EMACS") != null; + String emacs = System.getenv("INSIDE_EMACS"); + color = emacs != null && emacs.contains("comint"); } // detect Intellij Idea if (!color) { @@ -441,12 +491,13 @@ private Terminal doBuild() throws IOException { color = command != null && command.contains("idea"); } if (!color) { - color = tbs.isConsoleOutput() && System.getenv("TERM") != null; + color = system.get(TerminalProvider.Stream.Output) && System.getenv("TERM") != null; } if (!color && dumb == null) { if (Log.isDebugEnabled()) { - Log.warn("input is tty: {}", tbs.isConsoleInput()); - Log.warn("output is tty: {}", tbs.isConsoleOutput()); + Log.warn("input is tty: {}", system.get(TerminalProvider.Stream.Input)); + Log.warn("output is tty: {}", system.get(TerminalProvider.Stream.Output)); + Log.warn("error is tty: {}", system.get(TerminalProvider.Stream.Error)); Log.warn("Creating a dumb terminal", exception); } else { Log.warn("Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)"); @@ -454,33 +505,49 @@ private Terminal doBuild() throws IOException { } } terminal = new DumbTerminal(name, color ? Terminal.TYPE_DUMB_COLOR : Terminal.TYPE_DUMB, - inputStreamWrapper.apply(new FileInputStream(FileDescriptor.in)), - new FileOutputStream(FileDescriptor.out), + new FileInputStream(FileDescriptor.in), + new FileOutputStream(console == TerminalProvider.Stream.Output ? FileDescriptor.out : FileDescriptor.err), encoding, signalHandler); } - if (terminal == null) { - throw exception; - } - return terminal; } else { - if (jna) { - try { - Pty pty = load(JnaSupport.class).open(attributes, size); - return new PosixPtyTerminal(name, type, pty, inputStreamWrapper.apply(in), out, encoding, signalHandler, paused); - } catch (Throwable t) { - Log.debug("Error creating JNA based terminal: ", t.getMessage(), t); + for ( TerminalProvider provider : providers) { + if (terminal == null) { + try { + terminal = provider.newTerminal(name, type, inputStreamWrapper.apply(in), out, encoding, signalHandler, paused, attributes, size); + } catch (Throwable t) { + Log.debug("Error creating " + provider.name() + " based terminal: ", t.getMessage(), t); + exception.addSuppressed(t); + } } } - if (jansi) { - try { - Pty pty = load(JansiSupport.class).open(attributes, size); - return new PosixPtyTerminal(name, type, pty, inputStreamWrapper.apply(in), out, encoding, signalHandler, paused); - } catch (Throwable t) { - Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t); - } + } + if (terminal == null) { + throw exception; + } + return terminal; + } + + private TerminalProvider.Stream select(Map system, SystemOutput systemOutput) { + switch (systemOutput) { + case SysOut: + return select(system, TerminalProvider.Stream.Output); + case SysErr: + return select(system, TerminalProvider.Stream.Error); + case SysOutOrSysErr: + return select(system, TerminalProvider.Stream.Output, TerminalProvider.Stream.Error); + case SysErrOrSysOut: + return select(system, TerminalProvider.Stream.Error, TerminalProvider.Stream.Output); + } + return null; + } + + private static TerminalProvider.Stream select(Map system, TerminalProvider.Stream... streams) { + for (TerminalProvider.Stream s : streams) { + if (system.get(s)) { + return s; } - return new ExternalTerminal(name, type, inputStreamWrapper.apply(in), out, encoding, signalHandler, paused, attributes, size); } + return null; } private static String getParentProcessCommand() { @@ -512,6 +579,24 @@ private static S load(Class clazz) { return ServiceLoader.load(clazz, clazz.getClassLoader()).iterator().next(); } + private static final int UTF8_CODE_PAGE = 65001; + + private static Charset getCodepageCharset(int codepage) { + //http://docs.oracle.com/javase/6/docs/technotes/guides/intl/encoding.doc.html + if (codepage == UTF8_CODE_PAGE) { + return StandardCharsets.UTF_8; + } + String charsetMS = "ms" + codepage; + if (Charset.isSupported(charsetMS)) { + return Charset.forName(charsetMS); + } + String charsetCP = "cp" + codepage; + if (Charset.isSupported(charsetCP)) { + return Charset.forName(charsetCP); + } + return Charset.defaultCharset(); + } + /** * Allows an application to override the result of {@link #build()}. The * intended use case is to allow a container or server application to control @@ -545,79 +630,4 @@ public static void setTerminalOverride(final Terminal terminal) { TERMINAL_OVERRIDE.set(terminal); } - private static class TerminalBuilderSupport { - private JansiSupport jansiSupport = null; - private JnaSupport jnaSupport = null; - private Pty pty = null; - private boolean consoleOutput; - - TerminalBuilderSupport(boolean jna, boolean jansi) { - if (jna) { - try { - jnaSupport = load(JnaSupport.class); - consoleOutput = jnaSupport.isConsoleOutput(); - } catch (Throwable e) { - jnaSupport = null; - Log.debug("jnaSupport.isConsoleOutput(): ", e); - } - } - if (jansi) { - try { - jansiSupport = load(JansiSupport.class); - consoleOutput = jansiSupport.isConsoleOutput(); - } catch (Throwable e) { - jansiSupport = null; - Log.debug("jansiSupport.isConsoleOutput(): ", e); - } - } - if (jnaSupport == null && jansiSupport == null) { - try { - pty = ExecPty.current(); - consoleOutput = true; - } catch (Exception e) { - Log.debug("ExecPty.current(): ", e); - } - } - } - - public boolean isConsoleOutput() { - return consoleOutput; - } - - public boolean isConsoleInput() { - if (pty != null) { - return true; - } else if (hasJnaSupport()) { - return jnaSupport.isConsoleInput(); - } else if (hasJansiSupport()) { - return jansiSupport.isConsoleInput(); - } else { - return false; - } - } - - public boolean hasJnaSupport() { - return jnaSupport != null; - } - - public boolean hasJansiSupport() { - return jansiSupport != null; - } - - public JnaSupport getJnaSupport() { - return jnaSupport; - } - - public JansiSupport getJansiSupport() { - return jansiSupport; - } - - public Pty getExecPty() throws IOException { - if (pty == null) { - pty = ExecPty.current(); - } - return pty; - } - - } } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractPty.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractPty.java index 9ed353f46d0..0feab84fc7e 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractPty.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractPty.java @@ -86,11 +86,6 @@ public int read(long timeout, boolean isPeek) throws IOException { } } - @Override - public int readBuffered(byte[] b) throws IOException { - return in.read(b); - } - private void setNonBlocking() { if (current == null || current.getControlChar(Attributes.ControlChar.VMIN) != 0 diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractWindowsTerminal.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractWindowsTerminal.java index 5e21f034530..57cfb5d50b4 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractWindowsTerminal.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/AbstractWindowsTerminal.java @@ -81,8 +81,8 @@ public abstract class AbstractWindowsTerminal extends AbstractTerminal { protected boolean focusTracking = false; private volatile boolean closing; - public AbstractWindowsTerminal(Writer writer, String name, String type, Charset encoding, int codepage, boolean nativeSignals, SignalHandler signalHandler, Function inputStreamWrapper) throws IOException { - super(name, type, selectCharset(encoding, codepage), signalHandler); + public AbstractWindowsTerminal(Writer writer, String name, String type, Charset encoding, boolean nativeSignals, SignalHandler signalHandler, Function inputStreamWrapper) throws IOException { + super(name, type, encoding, signalHandler); NonBlockingPumpReader reader = NonBlocking.nonBlockingPumpReader(); this.slaveInputPipe = reader.getWriter(); this.input = inputStreamWrapper.apply(NonBlocking.nonBlockingStream(reader, encoding())); @@ -116,35 +116,6 @@ public AbstractWindowsTerminal(Writer writer, String name, String type, Charset } } - private static Charset selectCharset(Charset encoding, int codepage) { - if (encoding != null) { - return encoding; - } - - if (codepage >= 0) { - return getCodepageCharset(codepage); - } - - // Use UTF-8 as default - return StandardCharsets.UTF_8; - } - - private static Charset getCodepageCharset(int codepage) { - //http://docs.oracle.com/javase/6/docs/technotes/guides/intl/encoding.doc.html - if (codepage == UTF8_CODE_PAGE) { - return StandardCharsets.UTF_8; - } - String charsetMS = "ms" + codepage; - if (Charset.isSupported(charsetMS)) { - return Charset.forName(charsetMS); - } - String charsetCP = "cp" + codepage; - if (Charset.isSupported(charsetCP)) { - return Charset.forName(charsetCP); - } - return Charset.defaultCharset(); - } - @Override public SignalHandler handle(Signal signal, SignalHandler handler) { SignalHandler prev = super.handle(signal, handler); diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/Diag.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/Diag.java new file mode 100644 index 00000000000..dfa784c29f3 --- /dev/null +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/Diag.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2022, the original author or authors. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package jdk.internal.org.jline.terminal.impl; + +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.ServiceLoader; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.TimeUnit; + +import jdk.internal.org.jline.terminal.Attributes; +import jdk.internal.org.jline.terminal.Terminal; +import jdk.internal.org.jline.terminal.spi.TerminalProvider; +import jdk.internal.org.jline.utils.OSUtils; + +public class Diag { + + public static void main(String[] args) { + diag(System.out); + } + + static void diag(PrintStream out) { + out.println("System properties"); + out.println("================="); + out.println("os.name = " + System.getProperty("os.name")); + out.println("OSTYPE = " + System.getenv("OSTYPE")); + out.println("MSYSTEM = " + System.getenv("MSYSTEM")); + out.println("PWD = " + System.getenv("PWD")); + out.println("ConEmuPID = " + System.getenv("ConEmuPID")); + out.println("WSL_DISTRO_NAME = " + System.getenv("WSL_DISTRO_NAME")); + out.println("WSL_INTEROP = " + System.getenv("WSL_INTEROP")); + out.println(); + + out.println("OSUtils"); + out.println("================="); + out.println("IS_WINDOWS = " + OSUtils.IS_WINDOWS); + out.println("IS_CYGWIN = " + OSUtils.IS_CYGWIN); + out.println("IS_MSYSTEM = " + OSUtils.IS_MSYSTEM); + out.println("IS_WSL = " + OSUtils.IS_WSL); + out.println("IS_WSL1 = " + OSUtils.IS_WSL1); + out.println("IS_WSL2 = " + OSUtils.IS_WSL2); + out.println("IS_CONEMU = " + OSUtils.IS_CONEMU); + out.println("IS_OSX = " + OSUtils.IS_OSX); + out.println(); + + out.println("JnaSupport"); + out.println("================="); + try { + TerminalProvider provider = TerminalProvider.load("jna"); + testProvider(out, provider); + } catch (Throwable t) { + out.println("JNA support not available: " + t); + } + out.println(); + + out.println("JansiSupport"); + out.println("================="); + try { + TerminalProvider provider = TerminalProvider.load("jansi"); + testProvider(out, provider); + } catch (Throwable t) { + out.println("Jansi support not available: " + t); + } + out.println(); + + // Exec + out.println("Exec Support"); + out.println("================="); + try { + TerminalProvider provider = TerminalProvider.load("exec"); + testProvider(out, provider); + } catch (Throwable t) { + out.println("Exec support not available: " + t); + } + } + + private static void testProvider(PrintStream out, TerminalProvider provider) { + try { + out.println("StdIn stream = " + provider.isSystemStream(TerminalProvider.Stream.Input)); + out.println("StdOut stream = " + provider.isSystemStream(TerminalProvider.Stream.Output)); + out.println("StdErr stream = " + provider.isSystemStream(TerminalProvider.Stream.Error)); + } catch (Throwable t2) { + out.println("Unable to check stream: " + t2); + } + try { + out.println("StdIn stream name = " + provider.systemStreamName(TerminalProvider.Stream.Input)); + out.println("StdOut stream name = " + provider.systemStreamName(TerminalProvider.Stream.Output)); + out.println("StdErr stream name = " + provider.systemStreamName(TerminalProvider.Stream.Error)); + } catch (Throwable t2) { + out.println("Unable to check stream names: " + t2); + } + try (Terminal terminal = provider.sysTerminal("diag", "xterm", false, StandardCharsets.UTF_8, + false, Terminal.SignalHandler.SIG_DFL, false, TerminalProvider.Stream.Output, input -> input) ) { + if (terminal != null) { + Attributes attr = terminal.enterRawMode(); + try { + out.println("Terminal size: " + terminal.getSize()); + ForkJoinTask t = new ForkJoinPool(1).submit(() -> terminal.reader().read(1) ); + int r = t.get(1000, TimeUnit.MILLISECONDS); + StringBuilder sb = new StringBuilder(); + sb.append("The terminal seems to work: "); + sb.append("terminal ").append(terminal.getClass().getName()); + if (terminal instanceof AbstractPosixTerminal) { + sb.append(" with pty ").append(((AbstractPosixTerminal) terminal).getPty().getClass().getName()); + } + out.println(sb); + } catch (Throwable t3) { + out.println("Unable to read from terminal: " + t3); + t3.printStackTrace(); + } finally { + terminal.setAttributes(attr); + } + } else { + out.println("Not supported by provider"); + } + } catch (Throwable t2) { + out.println("Unable to open terminal: " + t2); + t2.printStackTrace(); + } + } + + static S load(Class clazz) { + return ServiceLoader.load(clazz, clazz.getClassLoader()).iterator().next(); + } + +} diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ExecPty.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ExecPty.java index a40484939dd..5377b4acdb8 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ExecPty.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/ExecPty.java @@ -26,6 +26,7 @@ import jdk.internal.org.jline.terminal.Attributes.LocalFlag; import jdk.internal.org.jline.terminal.Attributes.OutputFlag; import jdk.internal.org.jline.terminal.Size; +import jdk.internal.org.jline.terminal.spi.TerminalProvider; import jdk.internal.org.jline.terminal.spi.Pty; import jdk.internal.org.jline.utils.OSUtils; @@ -34,20 +35,23 @@ public class ExecPty extends AbstractPty implements Pty { private final String name; - private final boolean system; + private final TerminalProvider.Stream console; - public static Pty current() throws IOException { + public static Pty current(TerminalProvider.Stream console) throws IOException { try { String result = exec(true, OSUtils.TTY_COMMAND); - return new ExecPty(result.trim(), true); + if (console != TerminalProvider.Stream.Output && console != TerminalProvider.Stream.Error) { + throw new IllegalArgumentException("console should be Output or Error: " + console); + } + return new ExecPty(result.trim(), console); } catch (IOException e) { throw new IOException("Not a tty", e); } } - protected ExecPty(String name, boolean system) { + protected ExecPty(String name, TerminalProvider.Stream console) { this.name = name; - this.system = system; + this.console = console; } @Override @@ -70,16 +74,18 @@ public OutputStream getMasterOutput() { @Override protected InputStream doGetSlaveInput() throws IOException { - return system + return console != null ? new FileInputStream(FileDescriptor.in) : new FileInputStream(getName()); } @Override public OutputStream getSlaveOutput() throws IOException { - return system + return console == TerminalProvider.Stream.Output ? new FileOutputStream(FileDescriptor.out) - : new FileOutputStream(getName()); + : console == TerminalProvider.Stream.Error + ? new FileOutputStream(FileDescriptor.err) + : new FileOutputStream(getName()); } @Override @@ -93,23 +99,11 @@ protected void doSetAttr(Attributes attr) throws IOException { List commands = getFlagsToSet(attr, getAttr()); if (!commands.isEmpty()) { commands.add(0, OSUtils.STTY_COMMAND); - if (!system) { + if (console == null) { commands.add(1, OSUtils.STTY_F_OPTION); commands.add(2, getName()); } - try { - exec(system, commands.toArray(new String[commands.size()])); - } catch (IOException e) { - // Handle partial failures with GNU stty, see #97 - if (e.toString().contains("unable to perform all requested operations")) { - commands = getFlagsToSet(attr, getAttr()); - if (!commands.isEmpty()) { - throw new IOException("Could not set the following flags: " + String.join(", ", commands), e); - } - } else { - throw e; - } - } + exec(console != null, commands.toArray(new String[0])); } } @@ -171,7 +165,7 @@ public Size getSize() throws IOException { } protected String doGetConfig() throws IOException { - return system + return console != null ? exec(true, OSUtils.STTY_COMMAND, "-a") : exec(false, OSUtils.STTY_COMMAND, OSUtils.STTY_F_OPTION, getName(), "-a"); } @@ -280,7 +274,7 @@ static int doGetInt(String name, String cfg) throws IOException { @Override public void setSize(Size size) throws IOException { - if (system) { + if (console != null) { exec(true, OSUtils.STTY_COMMAND, "columns", Integer.toString(size.getColumns()), @@ -296,7 +290,7 @@ OSUtils.STTY_F_OPTION, getName(), @Override public String toString() { - return "ExecPty[" + getName() + (system ? ", system]" : "]"); + return "ExecPty[" + getName() + (console != null ? ", system]" : "]"); } } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixPtyTerminal.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixPtyTerminal.java index 0dfcedf2700..f85e3fe0b0c 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixPtyTerminal.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixPtyTerminal.java @@ -15,7 +15,6 @@ import java.io.PrintWriter; import java.nio.charset.Charset; import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; import jdk.internal.org.jline.terminal.spi.Pty; import jdk.internal.org.jline.utils.ClosedException; @@ -143,10 +142,10 @@ public boolean paused() { } } - private class InputStreamWrapper extends NonBlockingInputStream { + private static class InputStreamWrapper extends NonBlockingInputStream { private final NonBlockingInputStream in; - private final AtomicBoolean closed = new AtomicBoolean(); + private volatile boolean closed; protected InputStreamWrapper(NonBlockingInputStream in) { this.in = in; @@ -154,7 +153,7 @@ protected InputStreamWrapper(NonBlockingInputStream in) { @Override public int read(long timeout, boolean isPeek) throws IOException { - if (closed.get()) { + if (closed) { throw new ClosedException(); } return in.read(timeout, isPeek); @@ -162,7 +161,7 @@ public int read(long timeout, boolean isPeek) throws IOException { @Override public void close() throws IOException { - closed.set(true); + closed = true; } } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixSysTerminal.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixSysTerminal.java index 2909032d59a..a55f3d4a748 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixSysTerminal.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/PosixSysTerminal.java @@ -16,6 +16,7 @@ import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; import jdk.internal.org.jline.utils.NonBlocking; import jdk.internal.org.jline.terminal.spi.Pty; @@ -34,11 +35,12 @@ public class PosixSysTerminal extends AbstractPosixTerminal { protected final Map nativeHandlers = new HashMap<>(); protected final Task closer; - public PosixSysTerminal(String name, String type, Pty pty, InputStream in, OutputStream out, Charset encoding, - boolean nativeSignals, SignalHandler signalHandler) throws IOException { + public PosixSysTerminal(String name, String type, Pty pty, Charset encoding, + boolean nativeSignals, SignalHandler signalHandler, + Function inputStreamWrapper) throws IOException { super(name, type, pty, encoding, signalHandler); - this.input = NonBlocking.nonBlocking(getName(), in); - this.output = out; + this.input = NonBlocking.nonBlocking(getName(), inputStreamWrapper.apply(pty.getSlaveInput())); + this.output = pty.getSlaveOutput(); this.reader = NonBlocking.nonBlocking(getName(), input, encoding()); this.writer = new PrintWriter(new OutputStreamWriter(output, encoding())); parseInfoCmp(); diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/ExecTerminalProvider.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/ExecTerminalProvider.java new file mode 100644 index 00000000000..8bdeede2e1b --- /dev/null +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/impl/exec/ExecTerminalProvider.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2022, the original author or authors. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package jdk.internal.org.jline.terminal.impl.exec; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.nio.charset.Charset; +import java.util.function.Function; + +import jdk.internal.org.jline.terminal.Attributes; +import jdk.internal.org.jline.terminal.Size; +import jdk.internal.org.jline.terminal.Terminal; +import jdk.internal.org.jline.terminal.impl.ExecPty; +import jdk.internal.org.jline.terminal.impl.ExternalTerminal; +import jdk.internal.org.jline.terminal.impl.PosixSysTerminal; +import jdk.internal.org.jline.terminal.spi.Pty; +import jdk.internal.org.jline.terminal.spi.TerminalProvider; +import jdk.internal.org.jline.utils.ExecHelper; +import jdk.internal.org.jline.utils.OSUtils; + +public class ExecTerminalProvider implements TerminalProvider +{ + + public String name() { + return "exec"; + } + + public Pty current(Stream consoleStream) throws IOException { + return ExecPty.current(consoleStream); + } + + @Override + public Terminal sysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, + boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, + Stream consoleStream, Function inputStreamWrapper) throws IOException { + if (OSUtils.IS_WINDOWS) { + return winSysTerminal(name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, consoleStream, inputStreamWrapper ); + } else { + return posixSysTerminal(name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, consoleStream, inputStreamWrapper ); + } + } + + public Terminal winSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, + boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, + Stream consoleStream, Function inputStreamWrapper ) throws IOException { + if (OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) { + Pty pty = current(consoleStream); + return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler, inputStreamWrapper); + } else { + return null; + } + } + + public Terminal posixSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, + boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, + Stream consoleStream, Function inputStreamWrapper) throws IOException { + Pty pty = current(consoleStream); + return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler, inputStreamWrapper); + } + + @Override + public Terminal newTerminal(String name, String type, InputStream in, OutputStream out, + Charset encoding, Terminal.SignalHandler signalHandler, boolean paused, + Attributes attributes, Size size) throws IOException + { + return new ExternalTerminal(name, type, in, out, encoding, signalHandler, paused, attributes, size); + } + + @Override + public boolean isSystemStream(Stream stream) { + try { + return isWindowsSystemStream(stream) || isPosixSystemStream(stream); + } catch (Throwable t) { + return false; + } + } + + public boolean isWindowsSystemStream(Stream stream) { + return systemStreamName( stream ) != null; + } + + public boolean isPosixSystemStream(Stream stream) { + try { + Process p = new ProcessBuilder(OSUtils.TEST_COMMAND, "-t", Integer.toString(stream.ordinal())) + .inheritIO().start(); + return p.waitFor() == 0; + } catch (Throwable t) { + // ignore + } + return false; + } + + @Override + public String systemStreamName(Stream stream) { + try { + ProcessBuilder.Redirect input = stream == Stream.Input + ? ProcessBuilder.Redirect.INHERIT + : getRedirect(stream == Stream.Output ? FileDescriptor.out : FileDescriptor.err); + Process p = new ProcessBuilder(OSUtils.TTY_COMMAND).redirectInput(input).start(); + String result = ExecHelper.waitAndCapture(p); + if (p.exitValue() == 0) { + return result.trim(); + } + } catch (Throwable t) { + // ignore + } + return null; + } + + private ProcessBuilder.Redirect getRedirect(FileDescriptor fd) throws ReflectiveOperationException { + // This is not really allowed, but this is the only way to redirect the output or error stream + // to the input. This is definitely not something you'd usually want to do, but in the case of + // the `tty` utility, it provides a way to get + Class rpi = Class.forName("java.lang.ProcessBuilder$RedirectPipeImpl"); + Constructor cns = rpi.getDeclaredConstructor(); + cns.setAccessible(true); + ProcessBuilder.Redirect input = (ProcessBuilder.Redirect) cns.newInstance(); + Field f = rpi.getDeclaredField("fd"); + f.setAccessible(true); + f.set(input, fd); + return input; + } +} diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/JansiSupport.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/JansiSupport.java deleted file mode 100644 index 750c8fc0712..00000000000 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/JansiSupport.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2002-2020, the original author or authors. - * - * This software is distributable under the BSD license. See the terms of the - * BSD license in the documentation provided with this software. - * - * https://opensource.org/licenses/BSD-3-Clause - */ -package jdk.internal.org.jline.terminal.spi; - -import jdk.internal.org.jline.terminal.Attributes; -import jdk.internal.org.jline.terminal.Size; -import jdk.internal.org.jline.terminal.Terminal; - -import java.io.IOException; -import java.nio.charset.Charset; - -public interface JansiSupport { - - Pty current() throws IOException; - - Pty open(Attributes attributes, Size size) throws IOException; - - Terminal winSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, int codepage, boolean nativeSignals, Terminal.SignalHandler signalHandler) throws IOException; - - Terminal winSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, int codepage, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused) throws IOException; - - boolean isWindowsConsole(); - - boolean isConsoleOutput(); - - boolean isConsoleInput(); -} diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/JnaSupport.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/JnaSupport.java deleted file mode 100644 index 35f71a603f8..00000000000 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/JnaSupport.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2002-2020, the original author or authors. - * - * This software is distributable under the BSD license. See the terms of the - * BSD license in the documentation provided with this software. - * - * https://opensource.org/licenses/BSD-3-Clause - */ -package jdk.internal.org.jline.terminal.spi; - -import jdk.internal.org.jline.terminal.Attributes; -import jdk.internal.org.jline.terminal.Size; -import jdk.internal.org.jline.terminal.Terminal; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.function.Function; - -public interface JnaSupport { - - Pty current() throws IOException; - - Pty open(Attributes attributes, Size size) throws IOException; - - Terminal winSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, int codepage, boolean nativeSignals, Terminal.SignalHandler signalHandler) throws IOException; - - Terminal winSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, int codepage, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused) throws IOException; - - Terminal winSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, int codepage, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, Function inputStreamWrapper) throws IOException; - - boolean isWindowsConsole(); - - boolean isConsoleOutput(); - - boolean isConsoleInput(); -} diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/TerminalProvider.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/TerminalProvider.java new file mode 100644 index 00000000000..69f353c5719 --- /dev/null +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/terminal/spi/TerminalProvider.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022, the original author or authors. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package jdk.internal.org.jline.terminal.spi; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.Properties; +import java.util.ServiceLoader; +import java.util.function.Function; + +import jdk.internal.org.jline.terminal.Attributes; +import jdk.internal.org.jline.terminal.Size; +import jdk.internal.org.jline.terminal.Terminal; +import jdk.internal.org.jline.terminal.impl.exec.ExecTerminalProvider; + +public interface TerminalProvider +{ + + enum Stream { + Input, + Output, + Error + } + + String name(); + + Terminal sysTerminal(String name, String type, boolean ansiPassThrough, + Charset encoding, boolean nativeSignals, + Terminal.SignalHandler signalHandler, boolean paused, + Stream consoleStream, Function inputStreamWrapper) throws IOException; + + Terminal newTerminal(String name, String type, + InputStream masterInput, OutputStream masterOutput, + Charset encoding, Terminal.SignalHandler signalHandler, + boolean paused, Attributes attributes, Size size) throws IOException; + + boolean isSystemStream(Stream stream); + + String systemStreamName(Stream stream); + + static TerminalProvider load(String name) throws IOException { + switch (name) { + case "exec": return new ExecTerminalProvider(); + case "jna": { + try { + return (TerminalProvider) Class.forName("jdk.internal.org.jline.terminal.impl.jna.JnaTerminalProvider").getConstructor().newInstance(); + } catch (ReflectiveOperationException t) { + throw new IOException(t); + } + } + } + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl == null) { + cl = ClassLoader.getSystemClassLoader(); + } + InputStream is = cl.getResourceAsStream( "META-INF/services/org/jline/terminal/provider/" + name); + if (is != null) { + Properties props = new Properties(); + try { + props.load(is); + String className = props.getProperty("class"); + if (className == null) { + throw new IOException("No class defined in terminal provider file " + name); + } + Class clazz = cl.loadClass( className ); + return (TerminalProvider) clazz.getConstructor().newInstance(); + } catch ( Exception e ) { + throw new IOException("Unable to load terminal provider " + name, e); + } + } else { + throw new IOException("Unable to find terminal provider " + name); + } + } + +} diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Colors.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Colors.java index 19550366944..dc452e10364 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Colors.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Colors.java @@ -534,7 +534,7 @@ private static double calculateH(double h) { H = 0; return H; } else { - throw new IllegalArgumentException("h outside assumed range 0..360: " + Double.toString(h)); + throw new IllegalArgumentException("h outside assumed range 0..360: " + h); } } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Curses.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Curses.java index 4589c6b79bd..bdfd4c37bc2 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Curses.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Curses.java @@ -12,7 +12,7 @@ import java.io.IOError; import java.io.IOException; import java.io.StringWriter; -import java.util.Stack; +import java.util.ArrayDeque; /** * Curses helper methods. @@ -21,8 +21,8 @@ */ public final class Curses { - private static Object[] sv = new Object[26]; - private static Object[] dv = new Object[26]; + private static final Object[] sv = new Object[26]; + private static final Object[] dv = new Object[26]; private static final int IFTE_NONE = 0; private static final int IFTE_IF = 1; @@ -68,7 +68,7 @@ private static void doTputs(Appendable out, String str, Object... params) throws int length = str.length(); int ifte = IFTE_NONE; boolean exec = true; - Stack stack = new Stack<>(); + ArrayDeque stack = new ArrayDeque<>(); while (index < length) { char ch = str.charAt(index++); switch (ch) { @@ -197,7 +197,7 @@ private static void doTputs(Appendable out, String str, Object... params) throws int start = index; while (str.charAt(index++) != '}') ; if (exec) { - int v = Integer.valueOf(str.substring(start, index - 1)); + int v = Integer.parseInt(str.substring(start, index - 1)); stack.push(v); } break; @@ -470,7 +470,7 @@ private static int toInteger(Object pop) { } else if (pop instanceof Boolean) { return (Boolean) pop ? 1 : 0; } else { - return Integer.valueOf(pop.toString()); + return Integer.parseInt(pop.toString()); } } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Display.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Display.java index 61e0736b5b4..aaeab9b71ff 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Display.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Display.java @@ -187,7 +187,7 @@ public void update(List newLines, int targetCursorPos, boolean int lineIndex = 0; int currentPos = 0; - int numLines = Math.max(oldLines.size(), newLines.size()); + int numLines = Math.min(rows, Math.max(oldLines.size(), newLines.size())); boolean wrapNeeded = false; while (lineIndex < numLines) { AttributedString oldLine = diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/InfoCmp.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/InfoCmp.java index 68f77f638b7..23a76071486 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/InfoCmp.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/InfoCmp.java @@ -503,7 +503,7 @@ public enum Capability { public String[] getNames() { return getCapabilitiesByName().entrySet().stream() .filter(e -> e.getValue() == this) - .map(Map.Entry::getValue) + .map(Map.Entry::getKey) .toArray(String[]::new); } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlocking.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlocking.java index 6d37a20083e..f8cb53489f9 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlocking.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlocking.java @@ -95,13 +95,9 @@ public void close() throws IOException { @Override public int read(long timeout, boolean isPeek) throws IOException { - boolean isInfinite = (timeout <= 0L); - while (!bytes.hasRemaining() && (isInfinite || timeout > 0L)) { - long start = 0; - if (!isInfinite) { - start = System.currentTimeMillis(); - } - int c = reader.read(timeout); + Timeout t = new Timeout(timeout); + while (!bytes.hasRemaining() && !t.elapsed()) { + int c = reader.read(t.timeout()); if (c == EOF) { return EOF; } @@ -117,9 +113,6 @@ public int read(long timeout, boolean isPeek) throws IOException { encoder.encode(chars, bytes, false); bytes.flip(); } - if (!isInfinite) { - timeout -= System.currentTimeMillis() - start; - } } if (bytes.hasRemaining()) { if (isPeek) { @@ -151,21 +144,17 @@ public NonBlockingInputStreamReader(NonBlockingInputStream inputStream, Charset public NonBlockingInputStreamReader(NonBlockingInputStream input, CharsetDecoder decoder) { this.input = input; this.decoder = decoder; - this.bytes = ByteBuffer.allocate(4); - this.chars = CharBuffer.allocate(2); + this.bytes = ByteBuffer.allocate(2048); + this.chars = CharBuffer.allocate(1024); this.bytes.limit(0); this.chars.limit(0); } @Override protected int read(long timeout, boolean isPeek) throws IOException { - boolean isInfinite = (timeout <= 0L); - while (!chars.hasRemaining() && (isInfinite || timeout > 0L)) { - long start = 0; - if (!isInfinite) { - start = System.currentTimeMillis(); - } - int b = input.read(timeout); + Timeout t = new Timeout(timeout); + while (!chars.hasRemaining() && !t.elapsed()) { + int b = input.read(t.timeout()); if (b == EOF) { return EOF; } @@ -181,10 +170,6 @@ protected int read(long timeout, boolean isPeek) throws IOException { decoder.decode(bytes, chars, false); chars.flip(); } - - if (!isInfinite) { - timeout -= System.currentTimeMillis() - start; - } } if (chars.hasRemaining()) { if (isPeek) { @@ -198,46 +183,37 @@ protected int read(long timeout, boolean isPeek) throws IOException { } @Override - public int readBuffered(char[] b) throws IOException { + public int readBuffered(char[] b, int off, int len, long timeout) throws IOException { if (b == null) { throw new NullPointerException(); - } else if (b.length == 0) { + } else if (off < 0 || len < 0 || off + len < b.length) { + throw new IllegalArgumentException(); + } else if (len == 0) { return 0; + } else if (chars.hasRemaining()) { + int r = Math.min(len, chars.remaining()); + chars.get(b, off, r); + return r; } else { - if (chars.hasRemaining()) { - int r = Math.min(b.length, chars.remaining()); - chars.get(b); - return r; - } else { - byte[] buf = new byte[b.length]; - int l = input.readBuffered(buf); - if (l < 0) { - return l; - } else { - ByteBuffer currentBytes; - if (bytes.hasRemaining()) { - int transfer = bytes.remaining(); - byte[] newBuf = new byte[l + transfer]; - bytes.get(newBuf, 0, transfer); - System.arraycopy(buf, 0, newBuf, transfer, l); - currentBytes = ByteBuffer.wrap(newBuf); - bytes.position(0); - bytes.limit(0); - } else { - currentBytes = ByteBuffer.wrap(buf, 0, l); - } - CharBuffer chars = CharBuffer.wrap(b); - decoder.decode(currentBytes, chars, false); - chars.flip(); - if (currentBytes.hasRemaining()) { - int pos = bytes.position(); - bytes.limit(bytes.limit() + currentBytes.remaining()); - bytes.put(currentBytes); - bytes.position(pos); - } - return chars.remaining(); + Timeout t = new Timeout(timeout); + while (!chars.hasRemaining() && !t.elapsed()) { + if (!bytes.hasRemaining()) { + bytes.position(0); + bytes.limit(0); + } + int nb = input.readBuffered(bytes.array(), bytes.limit(), + bytes.capacity() - bytes.limit(), t.timeout()); + if (nb < 0) { + return nb; } + bytes.limit(bytes.limit() + nb); + chars.clear(); + decoder.decode(bytes, chars, false); + chars.flip(); } + int nb = Math.min(len, chars.remaining()); + chars.get(b, off, nb); + return nb; } } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStream.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStream.java index 1fb53bf5ef8..a4283eb7006 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStream.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStream.java @@ -79,12 +79,34 @@ public int read(byte b[], int off, int len) throws IOException { } public int readBuffered(byte[] b) throws IOException { + return readBuffered(b, 0L); + } + + public int readBuffered(byte[] b, long timeout) throws IOException { + return readBuffered(b, 0, b.length, timeout); + } + + public int readBuffered(byte[] b, int off, int len, long timeout) throws IOException { if (b == null) { throw new NullPointerException(); - } else if (b.length == 0) { + } else if (off < 0 || len < 0 || off + len < b.length) { + throw new IllegalArgumentException(); + } else if (len == 0) { return 0; } else { - return super.read(b, 0, b.length); + Timeout t = new Timeout(timeout); + int nb = 0; + while (!t.elapsed()) { + int r = read(nb > 0 ? 1 : t.timeout()); + if (r < 0) { + return nb > 0 ? nb : r; + } + b[off + nb++] = (byte) r; + if (nb >= len || t.isInfinite()) { + break; + } + } + return nb; } } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStreamImpl.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStreamImpl.java index 94c831c361d..680047fbe60 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStreamImpl.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingInputStreamImpl.java @@ -123,20 +123,17 @@ else if (!isPeek && timeout <= 0L && !threadIsReading) { notifyAll(); } - boolean isInfinite = (timeout <= 0L); - /* * So the thread is currently doing the reading for us. So * now we play the waiting game. */ - while (isInfinite || timeout > 0L) { - long start = System.currentTimeMillis (); - + Timeout t = new Timeout(timeout); + while (!t.elapsed()) { try { if (Thread.interrupted()) { throw new InterruptedException(); } - wait(timeout); + wait(t.timeout()); } catch (InterruptedException e) { exception = (IOException) new InterruptedIOException().initCause(e); @@ -155,10 +152,6 @@ else if (!isPeek && timeout <= 0L && !threadIsReading) { assert exception == null; break; } - - if (!isInfinite) { - timeout -= System.currentTimeMillis() - start; - } } } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpInputStream.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpInputStream.java index a48be007e24..5a361344350 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpInputStream.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpInputStream.java @@ -45,24 +45,17 @@ public OutputStream getOutputStream() { } private int wait(ByteBuffer buffer, long timeout) throws IOException { - boolean isInfinite = (timeout <= 0L); - long end = 0; - if (!isInfinite) { - end = System.currentTimeMillis() + timeout; - } - while (!closed && !buffer.hasRemaining() && (isInfinite || timeout > 0L)) { + Timeout t = new Timeout(timeout); + while (!closed && !buffer.hasRemaining() && !t.elapsed()) { // Wake up waiting readers/writers notifyAll(); try { - wait(timeout); + wait(t.timeout()); checkIoException(); } catch (InterruptedException e) { checkIoException(); throw new InterruptedIOException(); } - if (!isInfinite) { - timeout = end - System.currentTimeMillis(); - } } return buffer.hasRemaining() ? 0 @@ -107,17 +100,25 @@ public synchronized int read(long timeout, boolean isPeek) throws IOException { } @Override - public synchronized int readBuffered(byte[] b) throws IOException { - checkIoException(); - int res = wait(readBuffer, 0L); - if (res >= 0) { - res = 0; - while (res < b.length && readBuffer.hasRemaining()) { - b[res++] = (byte) (readBuffer.get() & 0x00FF); + public synchronized int readBuffered(byte[] b, int off, int len, long timeout) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || off + len < b.length) { + throw new IllegalArgumentException(); + } else if (len == 0) { + return 0; + } else { + checkIoException(); + int res = wait(readBuffer, timeout); + if (res >= 0) { + res = 0; + while (res < len && readBuffer.hasRemaining()) { + b[off + res++] = (byte) (readBuffer.get() & 0x00FF); + } } + rewind(readBuffer, writeBuffer); + return res; } - rewind(readBuffer, writeBuffer); - return res; } public synchronized void setIoException(IOException exception) { diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpReader.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpReader.java index 5f152c16e64..f36423f03b8 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpReader.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingPumpReader.java @@ -106,10 +106,12 @@ protected int read(long timeout, boolean isPeek) throws IOException { } @Override - public int readBuffered(char[] b) throws IOException { + public int readBuffered(char[] b, int off, int len, long timeout) throws IOException { if (b == null) { throw new NullPointerException(); - } else if (b.length == 0) { + } else if (off < 0 || len < 0 || off + len < b.length) { + throw new IllegalArgumentException(); + } else if (len == 0) { return 0; } else { final ReentrantLock lock = this.lock; @@ -117,7 +119,13 @@ public int readBuffered(char[] b) throws IOException { try { if (!closed && count == 0) { try { - notEmpty.await(); + if (timeout > 0) { + if (!notEmpty.await(timeout, TimeUnit.MILLISECONDS)) { + throw new IOException( "Timeout reading" ); + } + } else { + notEmpty.await(); + } } catch (InterruptedException e) { throw (IOException) new InterruptedIOException().initCause(e); } @@ -127,9 +135,9 @@ public int readBuffered(char[] b) throws IOException { } else if (count == 0) { return READ_EXPIRED; } else { - int r = Math.min(b.length, count); + int r = Math.min(len, count); for (int i = 0; i < r; i++) { - b[i] = buffer[read++]; + b[off + i] = buffer[read++]; if (read == buffer.length) { read = 0; } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReader.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReader.java index 0dd3a59e5b0..e2f664f2999 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReader.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReader.java @@ -85,7 +85,15 @@ public int read(char[] b, int off, int len) throws IOException { return 1; } - public abstract int readBuffered(char[] b) throws IOException; + public int readBuffered(char[] b) throws IOException { + return readBuffered(b, 0L); + } + + public int readBuffered(char[] b, long timeout) throws IOException { + return readBuffered(b, 0, b.length, timeout); + } + + public abstract int readBuffered(char[] b, int off, int len, long timeout) throws IOException; public int available() { return 0; diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReaderImpl.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReaderImpl.java index 7a53d123e9c..d384cc9a0dc 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReaderImpl.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/NonBlockingReaderImpl.java @@ -91,10 +91,12 @@ public synchronized boolean ready() throws IOException { } @Override - public int readBuffered(char[] b) throws IOException { + public int readBuffered(char[] b, int off, int len, long timeout) throws IOException { if (b == null) { throw new NullPointerException(); - } else if (b.length == 0) { + } else if (off < 0 || len < 0 || off + len < b.length) { + throw new IllegalArgumentException(); + } else if (len == 0) { return 0; } else if (exception != null) { assert ch == READ_EXPIRED; @@ -105,15 +107,16 @@ public int readBuffered(char[] b) throws IOException { b[0] = (char) ch; ch = READ_EXPIRED; return 1; - } else if (!threadIsReading) { - return in.read(b); + } else if (!threadIsReading && timeout <= 0) { + return in.read(b, off, len); } else { - int c = read(-1, false); + // TODO: rework implementation to read as much as possible + int c = read(timeout, false); if (c >= 0) { - b[0] = (char) c; + b[off] = (char) c; return 1; } else { - return -1; + return c; } } } @@ -158,20 +161,17 @@ else if (!isPeek && timeout <= 0L && !threadIsReading) { notifyAll(); } - boolean isInfinite = (timeout <= 0L); - /* * So the thread is currently doing the reading for us. So * now we play the waiting game. */ - while (isInfinite || timeout > 0L) { - long start = System.currentTimeMillis (); - + Timeout t = new Timeout(timeout); + while (!t.elapsed()) { try { if (Thread.interrupted()) { throw new InterruptedException(); } - wait(timeout); + wait(t.timeout()); } catch (InterruptedException e) { exception = (IOException) new InterruptedIOException().initCause(e); @@ -190,10 +190,6 @@ else if (!isPeek && timeout <= 0L && !threadIsReading) { assert exception == null; break; } - - if (!isInfinite) { - timeout -= System.currentTimeMillis() - start; - } } } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/OSUtils.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/OSUtils.java index 839a08704f3..97487731852 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/OSUtils.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/OSUtils.java @@ -9,6 +9,8 @@ package jdk.internal.org.jline.utils; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; public class OSUtils { @@ -28,6 +30,12 @@ public class OSUtils { && (System.getenv("MSYSTEM").startsWith("MINGW") || System.getenv("MSYSTEM").equals("MSYS")); + public static final boolean IS_WSL = System.getenv("WSL_DISTRO_NAME") != null; + + public static final boolean IS_WSL1 = IS_WSL && System.getenv("WSL_INTEROP") == null; + + public static final boolean IS_WSL2 = IS_WSL && !IS_WSL1; + public static final boolean IS_CONEMU = IS_WINDOWS && System.getenv("ConEmuPID") != null; @@ -38,17 +46,20 @@ public class OSUtils { public static String STTY_COMMAND; public static String STTY_F_OPTION; public static String INFOCMP_COMMAND; + public static String TEST_COMMAND; static { String tty; String stty; String sttyfopt; String infocmp; + String test; if (OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) { - tty = "tty.exe"; - stty = "stty.exe"; + tty = null; + stty = null; sttyfopt = null; - infocmp = "infocmp.exe"; + infocmp = null; + test = null; String path = System.getenv("PATH"); if (path != null) { String[] paths = path.split(";"); @@ -62,23 +73,39 @@ public class OSUtils { if (infocmp == null && new File(p, "infocmp.exe").exists()) { infocmp = new File(p, "infocmp.exe").getAbsolutePath(); } + if (test == null && new File(p, "test.exe").exists()) { + test = new File(p, "test.exe").getAbsolutePath(); + } } } + if (tty == null) { + tty = "tty.exe"; + } + if (stty == null) { + stty = "stty.exe"; + } + if (infocmp == null) { + infocmp = "infocmp.exe"; + } + if (test == null) { + test = "test.exe"; + } } else { tty = "tty"; - stty = "stty"; + stty = IS_OSX ? "/bin/stty" : "stty"; + sttyfopt = IS_OSX ? "-f" : "-F"; infocmp = "infocmp"; - if (IS_OSX) { - sttyfopt = "-f"; - } - else { - sttyfopt = "-F"; - } + test = isTestCommandValid("/usr/bin/test") ? "/usr/bin/test" + : "/bin/test"; } TTY_COMMAND = tty; STTY_COMMAND = stty; STTY_F_OPTION = sttyfopt; INFOCMP_COMMAND = infocmp; + TEST_COMMAND = test; } + private static boolean isTestCommandValid(String command) { + return Files.isExecutable(Paths.get(command)); + } } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/PumpReader.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/PumpReader.java index fa40360a689..a6894e7672c 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/PumpReader.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/PumpReader.java @@ -36,7 +36,7 @@ public PumpReader() { } public PumpReader(int bufferSize) { - char[] buf = new char[bufferSize]; + char[] buf = new char[Math.max(bufferSize, 2)]; this.readBuffer = CharBuffer.wrap(buf); this.writeBuffer = CharBuffer.wrap(buf); this.writer = new Writer(this); @@ -53,13 +53,27 @@ public java.io.InputStream createInputStream(Charset charset) { return new InputStream(this, charset); } - private boolean wait(CharBuffer buffer) throws InterruptedIOException { - if (closed) { - return false; + /** + * Blocks until more input is available, even if {@link #readBuffer} already + * contains some chars; or until the reader is closed. + * + * @return true if more input is available, false if no additional input is + * available and the reader is closed + * @throws InterruptedIOException If {@link #wait()} is interrupted + */ + private boolean waitForMoreInput() throws InterruptedIOException { + if (!writeBuffer.hasRemaining()) { + throw new AssertionError("No space in write buffer"); } - while (!buffer.hasRemaining()) { - // Wake up waiting readers/writers + int oldRemaining = readBuffer.remaining(); + + do { + if (closed) { + return false; + } + + // Wake up waiting writers notifyAll(); try { @@ -67,19 +81,41 @@ private boolean wait(CharBuffer buffer) throws InterruptedIOException { } catch (InterruptedException e) { throw new InterruptedIOException(); } + } while (readBuffer.remaining() <= oldRemaining); + + return true; + } + /** + * Waits until {@code buffer.hasRemaining() == true}, or it is false and + * the reader is {@link #closed}. + * + * @return true if {@code buffer.hasRemaining() == true}; false otherwise + * when reader is closed + */ + private boolean wait(CharBuffer buffer) throws InterruptedIOException { + while (!buffer.hasRemaining()) { if (closed) { return false; } + + // Wake up waiting readers/writers + notifyAll(); + + try { + wait(); + } catch (InterruptedException e) { + throw new InterruptedIOException(); + } } return true; } /** - * Blocks until more input is available or the reader is closed. + * Blocks until input is available or the reader is closed. * - * @return true if more input is available, false if the reader is closed + * @return true if input is available, false if no input is available and the reader is closed * @throws InterruptedIOException If {@link #wait()} is interrupted */ private boolean waitForInput() throws InterruptedIOException { @@ -94,7 +130,8 @@ private boolean waitForInput() throws InterruptedIOException { * @throws ClosedException If the reader was closed */ private void waitForBufferSpace() throws InterruptedIOException, ClosedException { - if (!wait(writeBuffer)) { + // Check `closed` to throw even if writer buffer has space available + if (!wait(writeBuffer) || closed) { throw new ClosedException(); } } @@ -122,7 +159,9 @@ private static boolean rewind(CharBuffer buffer, CharBuffer other) { * @return If more input is available */ private boolean rewindReadBuffer() { - return rewind(readBuffer, writeBuffer) && readBuffer.hasRemaining(); + boolean rw = rewind(readBuffer, writeBuffer) && readBuffer.hasRemaining(); + notifyAll(); + return rw; } /** @@ -131,6 +170,7 @@ private boolean rewindReadBuffer() { */ private void rewindWriteBuffer() { rewind(writeBuffer, readBuffer); + notifyAll(); } @Override @@ -202,10 +242,33 @@ public synchronized int read(CharBuffer target) throws IOException { } private void encodeBytes(CharsetEncoder encoder, ByteBuffer output) throws IOException { + int oldPos = output.position(); CoderResult result = encoder.encode(readBuffer, output, false); - if (rewindReadBuffer() && result.isUnderflow()) { - encoder.encode(readBuffer, output, false); + int encodedCount = output.position() - oldPos; + + if (result.isUnderflow()) { + boolean hasMoreInput = rewindReadBuffer(); + boolean reachedEndOfInput = false; + + // If encoding did not make any progress must block for more input + if (encodedCount == 0 && !hasMoreInput) { + reachedEndOfInput = !waitForMoreInput(); + } + + result = encoder.encode(readBuffer, output, reachedEndOfInput); + if (result.isError()) { + result.throwException(); + } + if (!reachedEndOfInput && output.position() - oldPos == 0) { + throw new AssertionError("Failed to encode any chars"); + } rewindReadBuffer(); + } else if (result.isOverflow()) { + if (encodedCount == 0) { + throw new AssertionError("Output buffer has not enough space"); + } + } else { + result.throwException(); } } @@ -334,7 +397,7 @@ private InputStream(PumpReader reader, Charset charset) { this.encoder = charset.newEncoder() .onUnmappableCharacter(CodingErrorAction.REPLACE) .onMalformedInput(CodingErrorAction.REPLACE); - this.buffer = ByteBuffer.allocate((int) Math.ceil(encoder.maxBytesPerChar())); + this.buffer = ByteBuffer.allocate((int) Math.ceil(encoder.maxBytesPerChar() * 2)); // No input available after initialization buffer.limit(0); diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/StyleResolver.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/StyleResolver.java index f2cc61e80a5..be1659957b4 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/StyleResolver.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/StyleResolver.java @@ -241,7 +241,7 @@ private AttributedStyle applyReference(final AttributedStyle style, final String if (spec.length() == 1) { // log.warning("Invalid style-reference; missing discriminator: " + spec); } else { - String name = spec.substring(1, spec.length()); + String name = spec.substring(1); String resolvedSpec = source.apply(name); if (resolvedSpec != null) { return apply(style, resolvedSpec); diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Timeout.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Timeout.java new file mode 100644 index 00000000000..edea89cf2d9 --- /dev/null +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/Timeout.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2002-2018, the original author or authors. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package jdk.internal.org.jline.utils; + +/** + * Helper class ti use during I/O operations with an eventual timeout. + */ +public class Timeout { + + private final long timeout; + private long cur = 0; + private long end = Long.MAX_VALUE; + + public Timeout(long timeout) { + this.timeout = timeout; + } + + public boolean isInfinite() { + return timeout <= 0; + } + + public boolean isFinite() { + return timeout > 0; + } + + public boolean elapsed() { + if (timeout > 0) { + cur = System.currentTimeMillis(); + if (end == Long.MAX_VALUE) { + end = cur + timeout; + } + return cur >= end; + } else { + return false; + } + } + + public long timeout() { + return timeout > 0 ? Math.max(1, end - cur) : timeout; + } + +} diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/WCWidth.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/WCWidth.java index d20b2e89785..db3a8d3d990 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/WCWidth.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/WCWidth.java @@ -70,6 +70,7 @@ public static int wcwidth(int ucs) (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x1f000 && ucs <= 0x1feee) || (ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd))) ? 1 : 0); } @@ -123,8 +124,8 @@ public static int wcwidth(int ucs) new Interval( 0x10A01, 0x10A03 ), new Interval( 0x10A05, 0x10A06 ), new Interval( 0x10A0C, 0x10A0F ), new Interval( 0x10A38, 0x10A3A ), new Interval( 0x10A3F, 0x10A3F ), new Interval( 0x1D167, 0x1D169 ), new Interval( 0x1D173, 0x1D182 ), new Interval( 0x1D185, 0x1D18B ), new Interval( 0x1D1AA, 0x1D1AD ), - new Interval( 0x1D242, 0x1D244 ), new Interval( 0xE0001, 0xE0001 ), new Interval( 0xE0020, 0xE007F ), - new Interval( 0xE0100, 0xE01EF ) + new Interval( 0x1D242, 0x1D244 ), new Interval( 0x1F3FB, 0x1F3FF ), new Interval( 0xE0001, 0xE0001 ), + new Interval( 0xE0020, 0xE007F ), new Interval( 0xE0100, 0xE01EF ) }; private static class Interval { diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/windows-vtp.caps b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/windows-vtp.caps index 39e0623eef3..4ce80158751 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/windows-vtp.caps +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/windows-vtp.caps @@ -2,7 +2,7 @@ windows-vtp|windows with virtual terminal processing, am, mc5i, mir, msgr, colors#256, cols#80, it#8, lines#24, ncv#3, pairs#64, bel=^G, blink=\E[5m, bold=\E[1m, cbt=\E[Z, clear=\E[H\E[J, - cr=^M, cub=\E[%p1%dD, cub1=\E[D, cud=\E[%p1%dB, cud1=\E[B, + cr=^M, cub=\E[%p1%dD, cub1=\E[D, cud=\E[%p1%dB, cud1=\n, cuf=\E[%p1%dC, cuf1=\E[C, cup=\E[%i%p1%d;%p2%dH, cuu=\E[%p1%dA, cuu1=\E[A, il=\E[%p1%dL, il1=\E[L, diff --git a/src/jdk.internal.le/share/classes/module-info.java b/src/jdk.internal.le/share/classes/module-info.java index 1ece112f1e3..c51ca6ce5cd 100644 --- a/src/jdk.internal.le/share/classes/module-info.java +++ b/src/jdk.internal.le/share/classes/module-info.java @@ -56,8 +56,5 @@ exports jdk.internal.org.jline.terminal.spi to jdk.scripting.nashorn.shell, jdk.jshell; - - uses jdk.internal.org.jline.terminal.spi.JnaSupport; - } diff --git a/src/jdk.internal.le/share/legal/jline.md b/src/jdk.internal.le/share/legal/jline.md index dc3c460b7ea..4e5d344b4d1 100644 --- a/src/jdk.internal.le/share/legal/jline.md +++ b/src/jdk.internal.le/share/legal/jline.md @@ -1,4 +1,4 @@ -## JLine v3.20.0 +## JLine v3.22.0 ### JLine License
@@ -41,10 +41,10 @@ OF THE POSSIBILITY OF SUCH DAMAGE.
 
 4th Party Dependency
 =============
-org.fusesource.jansi version 1.17.1
-org.apache.sshd 2.1 to 3
-org.apache.felix.gogo.runtime 1.1.2
-org.apache.felix.gogo.jline 1.1.4
+org.fusesource.jansi version 2.4.0
+org.apache.sshd 2.9.2
+org.apache.felix.gogo.runtime 1.1.6
+org.apache.felix.gogo.jline 1.1.8
 =============
 Apache License
                           Version 2.0, January 2004
@@ -262,7 +262,7 @@ slf4j
 SLF4J source code and binaries are distributed under the MIT license.
 
 
-Copyright (c) 2004-2017 QOS.ch
+Copyright (c) 2004-2023 QOS.ch
 All rights reserved.
 
 Permission is hereby granted, free of charge, to any person obtaining
diff --git a/src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/JnaSupportImpl.java b/src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/JnaSupportImpl.java
deleted file mode 100644
index 061952063cf..00000000000
--- a/src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/JnaSupportImpl.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (c) 2002-2019, the original author or authors.
- *
- * This software is distributable under the BSD license. See the terms of the
- * BSD license in the documentation provided with this software.
- *
- * https://opensource.org/licenses/BSD-3-Clause
- */
-package jdk.internal.org.jline.terminal.impl.jna;
-
-import jdk.internal.org.jline.terminal.Attributes;
-import jdk.internal.org.jline.terminal.Size;
-import jdk.internal.org.jline.terminal.Terminal;
-import jdk.internal.org.jline.terminal.impl.jna.win.JnaWinSysTerminal;
-import jdk.internal.org.jline.terminal.spi.JnaSupport;
-import jdk.internal.org.jline.terminal.spi.Pty;
-import jdk.internal.org.jline.utils.OSUtils;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.Charset;
-import java.util.function.Function;
-
-public class JnaSupportImpl implements JnaSupport {
-    @Override
-    public Pty current() throws IOException {
-//        return JnaNativePty.current();
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public Pty open(Attributes attributes, Size size) throws IOException {
-//        return JnaNativePty.open(attributes, size);
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public Terminal winSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, int codepage, boolean nativeSignals, Terminal.SignalHandler signalHandler) throws IOException {
-        return winSysTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, false);
-    }
-
-    @Override
-    public Terminal winSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, int codepage, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused) throws IOException {
-        return winSysTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, paused, input -> input);
-    }
-
-    @Override
-    public Terminal winSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, int codepage, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, Function inputStreamWrapper) throws IOException {
-        return JnaWinSysTerminal.createTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, paused, inputStreamWrapper);
-    }
-
-    @Override
-    public boolean isWindowsConsole() {
-        return JnaWinSysTerminal.isWindowsConsole();
-    }
-
-    @Override
-    public boolean isConsoleOutput() {
-        if (OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) {
-            throw new UnsupportedOperationException();
-        } else if (OSUtils.IS_WINDOWS) {
-            return JnaWinSysTerminal.isConsoleOutput();
-        }
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isConsoleInput() {
-        if (OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) {
-            throw new UnsupportedOperationException();
-        } else if (OSUtils.IS_WINDOWS) {
-            return JnaWinSysTerminal.isConsoleInput();
-        }
-        throw new UnsupportedOperationException();
-    }
-
-}
diff --git a/src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/JnaTerminalProvider.java b/src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/JnaTerminalProvider.java
new file mode 100644
index 00000000000..b820ce2187e
--- /dev/null
+++ b/src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/JnaTerminalProvider.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2002-2020, the original author or authors.
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+package jdk.internal.org.jline.terminal.impl.jna;
+
+import jdk.internal.org.jline.terminal.Attributes;
+import jdk.internal.org.jline.terminal.Size;
+import jdk.internal.org.jline.terminal.Terminal;
+import jdk.internal.org.jline.terminal.impl.PosixPtyTerminal;
+import jdk.internal.org.jline.terminal.impl.PosixSysTerminal;
+import jdk.internal.org.jline.terminal.impl.jna.win.JnaWinSysTerminal;
+import jdk.internal.org.jline.terminal.spi.TerminalProvider;
+import jdk.internal.org.jline.terminal.spi.Pty;
+import jdk.internal.org.jline.utils.OSUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.function.Function;
+
+public class JnaTerminalProvider implements TerminalProvider
+{
+    @Override
+    public String name() {
+        return "jna";
+    }
+
+//    public Pty current(TerminalProvider.Stream console) throws IOException {
+//        return JnaNativePty.current(console);
+//    }
+//
+//    public Pty open(Attributes attributes, Size size) throws IOException {
+//        return JnaNativePty.open(attributes, size);
+//    }
+
+    @Override
+    public Terminal sysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding,
+                                boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused,
+                                Stream consoleStream, Function inputStreamWrapper) throws IOException {
+        if (OSUtils.IS_WINDOWS) {
+            return winSysTerminal(name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, consoleStream, inputStreamWrapper );
+        } else {
+            return null;
+        }
+    }
+
+    public Terminal winSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding,
+                                   boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused,
+                                   Stream console, Function inputStreamWrapper) throws IOException {
+        return JnaWinSysTerminal.createTerminal(name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, console, inputStreamWrapper);
+    }
+
+//    public Terminal posixSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding,
+//                                     boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused,
+//                                     Stream consoleStream) throws IOException {
+//        Pty pty = current(consoleStream);
+//        return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler);
+//    }
+
+    @Override
+    public Terminal newTerminal(String name, String type, InputStream in, OutputStream out,
+                                Charset encoding, Terminal.SignalHandler signalHandler, boolean paused,
+                                Attributes attributes, Size size) throws IOException
+    {
+//        Pty pty = open(attributes, size);
+//        return new PosixPtyTerminal(name, type, pty, in, out, encoding, signalHandler, paused);
+        return null;
+    }
+
+    @Override
+    public boolean isSystemStream(Stream stream) {
+        try {
+            if (OSUtils.IS_WINDOWS) {
+                return isWindowsSystemStream(stream);
+            } else {
+//                return isPosixSystemStream(stream);
+                return false;
+            }
+        } catch (Throwable t) {
+            return false;
+        }
+    }
+
+    public boolean isWindowsSystemStream(Stream stream) {
+        return JnaWinSysTerminal.isWindowsSystemStream(stream);
+    }
+
+//    public boolean isPosixSystemStream(Stream stream) {
+//        return JnaNativePty.isPosixSystemStream(stream);
+//    }
+
+    @Override
+    public String systemStreamName(Stream stream) {
+//        if (OSUtils.IS_WINDOWS) {
+            return null;
+//        } else {
+//            return JnaNativePty.posixSystemStreamName(stream);
+//        }
+    }
+}
diff --git a/src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/win/JnaWinConsoleWriter.java b/src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/win/JnaWinConsoleWriter.java
index d2dc21286ac..a4469003c16 100644
--- a/src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/win/JnaWinConsoleWriter.java
+++ b/src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/win/JnaWinConsoleWriter.java
@@ -17,17 +17,17 @@
 
 class JnaWinConsoleWriter extends AbstractWindowsConsoleWriter {
 
-    private final Pointer consoleHandle;
+    private final Pointer console;
     private final IntByReference writtenChars = new IntByReference();
 
-    JnaWinConsoleWriter(Pointer consoleHandle) {
-        this.consoleHandle = consoleHandle;
+    JnaWinConsoleWriter(Pointer console) {
+        this.console = console;
     }
 
     @Override
     protected void writeConsole(char[] text, int len) throws IOException {
         try {
-            Kernel32.INSTANCE.WriteConsoleW(this.consoleHandle, text, len, this.writtenChars, null);
+            Kernel32.INSTANCE.WriteConsoleW(this.console, text, len, this.writtenChars, null);
         } catch (LastErrorException e) {
             throw new IOException("Failed to write to console", e);
         }
diff --git a/src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/win/JnaWinSysTerminal.java b/src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/win/JnaWinSysTerminal.java
index 39e4d219f17..5ffc5d714ed 100644
--- a/src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/win/JnaWinSysTerminal.java
+++ b/src/jdk.internal.le/windows/classes/jdk/internal/org/jline/terminal/impl/jna/win/JnaWinSysTerminal.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002-2019, the original author or authors.
+ * Copyright (c) 2002-2020, the original author or authors.
  *
  * This software is distributable under the BSD license. See the terms of the
  * BSD license in the documentation provided with this software.
@@ -19,11 +19,10 @@
 //import com.sun.jna.LastErrorException;
 //import com.sun.jna.Pointer;
 //import com.sun.jna.ptr.IntByReference;
-
 import jdk.internal.org.jline.terminal.Cursor;
 import jdk.internal.org.jline.terminal.Size;
-import jdk.internal.org.jline.terminal.Terminal;
 import jdk.internal.org.jline.terminal.impl.AbstractWindowsTerminal;
+import jdk.internal.org.jline.terminal.spi.TerminalProvider;
 import jdk.internal.org.jline.utils.InfoCmp;
 import jdk.internal.org.jline.utils.OSUtils;
 
@@ -31,38 +30,50 @@ public class JnaWinSysTerminal extends AbstractWindowsTerminal {
 
     private static final Pointer consoleIn = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_INPUT_HANDLE);
     private static final Pointer consoleOut = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_OUTPUT_HANDLE);
-
-    public static JnaWinSysTerminal createTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, int codepage, boolean nativeSignals, SignalHandler signalHandler, boolean paused, Function inputStreamWrapper) throws IOException {
+    private static final Pointer consoleErr = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_ERROR_HANDLE);
+
+    public static JnaWinSysTerminal createTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, boolean nativeSignals, SignalHandler signalHandler, boolean paused, TerminalProvider.Stream consoleStream, Function inputStreamWrapper) throws IOException {
+        Pointer console;
+        switch (consoleStream) {
+            case Output:
+                console = JnaWinSysTerminal.consoleOut;
+                break;
+            case Error:
+                console = JnaWinSysTerminal.consoleErr;
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupport stream for console: " + consoleStream);
+        }
         Writer writer;
         if (ansiPassThrough) {
             if (type == null) {
                 type = OSUtils.IS_CONEMU ? TYPE_WINDOWS_CONEMU : TYPE_WINDOWS;
             }
-            writer = new JnaWinConsoleWriter(consoleOut);
+            writer = new JnaWinConsoleWriter(console);
         } else {
             IntByReference mode = new IntByReference();
-            Kernel32.INSTANCE.GetConsoleMode(consoleOut, mode);
+            Kernel32.INSTANCE.GetConsoleMode(console, mode);
             try {
-                Kernel32.INSTANCE.SetConsoleMode(consoleOut, mode.getValue() | AbstractWindowsTerminal.ENABLE_VIRTUAL_TERMINAL_PROCESSING);
+                Kernel32.INSTANCE.SetConsoleMode(console, mode.getValue() | AbstractWindowsTerminal.ENABLE_VIRTUAL_TERMINAL_PROCESSING);
                 if (type == null) {
                     type = TYPE_WINDOWS_VTP;
                 }
-                writer = new JnaWinConsoleWriter(consoleOut);
+                writer = new JnaWinConsoleWriter(console);
             } catch (LastErrorException e) {
                 if (OSUtils.IS_CONEMU) {
                     if (type == null) {
                         type = TYPE_WINDOWS_CONEMU;
                     }
-                    writer = new JnaWinConsoleWriter(consoleOut);
+                    writer = new JnaWinConsoleWriter(console);
                 } else {
                     if (type == null) {
                         type = TYPE_WINDOWS;
                     }
-                    writer = new WindowsAnsiWriter(new BufferedWriter(new JnaWinConsoleWriter(consoleOut)), consoleOut);
+                    writer = new WindowsAnsiWriter(new BufferedWriter(new JnaWinConsoleWriter(console)), console);
                 }
             }
         }
-        JnaWinSysTerminal terminal = new JnaWinSysTerminal(writer, name, type, encoding, codepage, nativeSignals, signalHandler, inputStreamWrapper);
+        JnaWinSysTerminal terminal = new JnaWinSysTerminal(writer, name, type, encoding, nativeSignals, signalHandler, inputStreamWrapper);
         // Start input pump thread
         if (!paused) {
             terminal.resume();
@@ -70,39 +81,26 @@ public static JnaWinSysTerminal createTerminal(String name, String type, boolean
         return terminal;
     }
 
-    public static boolean isWindowsConsole() {
+    public static boolean isWindowsSystemStream(TerminalProvider.Stream stream) {
         try {
             IntByReference mode = new IntByReference();
-            Kernel32.INSTANCE.GetConsoleMode(consoleOut, mode);
-            Kernel32.INSTANCE.GetConsoleMode(consoleIn, mode);
-            return true;
-        } catch (LastErrorException e) {
-            return false;
-        }
-    }
-
-    public static boolean isConsoleOutput() {
-        try {
-            IntByReference mode = new IntByReference();
-            Kernel32.INSTANCE.GetConsoleMode(consoleOut, mode);
-            return true;
-        } catch (LastErrorException e) {
-            return false;
-        }
-    }
-
-    public static boolean isConsoleInput() {
-        try {
-            IntByReference mode = new IntByReference();
-            Kernel32.INSTANCE.GetConsoleMode(consoleIn, mode);
+            Pointer console;
+            switch (stream) {
+                case Input: console = consoleIn; break;
+                case Output: console = consoleOut; break;
+                case Error: console = consoleErr; break;
+                default: return false;
+            }
+            Kernel32.INSTANCE.GetConsoleMode(console, mode);
             return true;
         } catch (LastErrorException e) {
             return false;
         }
     }
 
-    JnaWinSysTerminal(Writer writer, String name, String type, Charset encoding, int codepage, boolean nativeSignals, SignalHandler signalHandler, Function inputStreamWrapper) throws IOException {
-        super(writer, name, type, encoding, codepage, nativeSignals, signalHandler, inputStreamWrapper);
+    JnaWinSysTerminal(Writer writer, String name, String type, Charset encoding, boolean nativeSignals, SignalHandler signalHandler,
+            Function inputStreamWrapper) throws IOException {
+        super(writer, name, type, encoding, nativeSignals, signalHandler, inputStreamWrapper);
         strings.put(InfoCmp.Capability.key_mouse, "\\E[M");
     }
 
diff --git a/test/jdk/jdk/internal/jline/AbstractWindowsTerminalTest.java b/test/jdk/jdk/internal/jline/AbstractWindowsTerminalTest.java
index fa4f44162db..d53c9b20309 100644
--- a/test/jdk/jdk/internal/jline/AbstractWindowsTerminalTest.java
+++ b/test/jdk/jdk/internal/jline/AbstractWindowsTerminalTest.java
@@ -56,7 +56,7 @@ public int read() throws IOException {
                 return is.read();
             }
         };
-        var t = new AbstractWindowsTerminal(out, "test", "vt100", null, -1, false, SignalHandler.SIG_DFL, isWrapper) {
+        var t = new AbstractWindowsTerminal(out, "test", "vt100", null, false, SignalHandler.SIG_DFL, isWrapper) {
             @Override
             protected int getConsoleMode() {
                 return -1;
diff --git a/test/jdk/jdk/internal/jline/KeyConversionTest.java b/test/jdk/jdk/internal/jline/KeyConversionTest.java
index 4eadb9a1bf3..aed9a726715 100644
--- a/test/jdk/jdk/internal/jline/KeyConversionTest.java
+++ b/test/jdk/jdk/internal/jline/KeyConversionTest.java
@@ -59,7 +59,7 @@ void run() throws Exception {
     void checkKeyConversion(KeyEvent event, String expected) throws IOException {
         StringBuilder result = new StringBuilder();
         new AbstractWindowsTerminal(new StringWriter(), "", "windows", Charset.forName("UTF-8"),
-                                    0, true, SignalHandler.SIG_DFL, in -> in) {
+                                    true, SignalHandler.SIG_DFL, in -> in) {
             @Override
             protected int getConsoleMode() {
                 return 0;
diff --git a/src/jdk.internal.le/windows/classes/module-info.java.extra b/test/jdk/jdk/internal/jline/OSUtilsTest.java
similarity index 53%
rename from src/jdk.internal.le/windows/classes/module-info.java.extra
rename to test/jdk/jdk/internal/jline/OSUtilsTest.java
index 3e791de0b36..0494ac24d90 100644
--- a/src/jdk.internal.le/windows/classes/module-info.java.extra
+++ b/test/jdk/jdk/internal/jline/OSUtilsTest.java
@@ -1,12 +1,10 @@
 /*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
+ * published by the Free Software Foundation.
  *
  * This code is distributed in the hope that it will be useful, but WITHOUT
  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
@@ -23,5 +21,32 @@
  * questions.
  */
 
+/**
+ * @test
+ * @bug 8304498
+ * @summary Verify the OSUtils class is initialized properly
+ * @modules jdk.internal.le/jdk.internal.org.jline.utils
+ */
+
+import jdk.internal.org.jline.utils.OSUtils;
+
+public class OSUtilsTest {
+    public static void main(String... args) throws Exception {
+        new OSUtilsTest().run();
+    }
+
+    void run() throws Exception {
+        runTestTest();
+    }
+
+    void runTestTest() throws Exception {
+        if (OSUtils.IS_WINDOWS) {
+            return ; //skip on Windows
+        }
 
-provides jdk.internal.org.jline.terminal.spi.JnaSupport with jdk.internal.org.jline.terminal.impl.jna.JnaSupportImpl;
+        Process p = new ProcessBuilder(OSUtils.TEST_COMMAND, "-z", "").inheritIO().start();
+        if (p.waitFor() != 0) {
+            throw new AssertionError("Unexpected result!");
+        }
+    }
+}
diff --git a/test/langtools/jdk/jshell/ExecPtyGetFlagsToSetTest.java b/test/langtools/jdk/jshell/ExecPtyGetFlagsToSetTest.java
index 62eac6bd32f..36f96644961 100644
--- a/test/langtools/jdk/jshell/ExecPtyGetFlagsToSetTest.java
+++ b/test/langtools/jdk/jshell/ExecPtyGetFlagsToSetTest.java
@@ -27,6 +27,7 @@
  * @summary Control Char  check for pty
  * @modules jdk.internal.le/jdk.internal.org.jline.terminal
  *          jdk.internal.le/jdk.internal.org.jline.terminal.impl
+ *          jdk.internal.le/jdk.internal.org.jline.terminal.spi
  * @requires (os.family == "linux") | (os.family == "aix")
  */
 
@@ -35,10 +36,11 @@
 import jdk.internal.org.jline.terminal.Attributes.ControlChar;
 import jdk.internal.org.jline.terminal.Attributes.LocalFlag;
 import jdk.internal.org.jline.terminal.impl.ExecPty;
+import jdk.internal.org.jline.terminal.spi.TerminalProvider;
 
 public class ExecPtyGetFlagsToSetTest extends ExecPty {
-    public ExecPtyGetFlagsToSetTest(String name, boolean system) {
-        super(name, system);
+    public ExecPtyGetFlagsToSetTest(String name, TerminalProvider.Stream stream) {
+        super(name, stream);
     }
 
     @Override
@@ -48,7 +50,7 @@ protected List getFlagsToSet(Attributes attr, Attributes current) {
 
     public static void main(String[] args) {
         ExecPtyGetFlagsToSetTest testPty =
-            new ExecPtyGetFlagsToSetTest("stty", true);
+            new ExecPtyGetFlagsToSetTest("stty", TerminalProvider.Stream.Output);
 
         Attributes attr = new Attributes();
         Attributes current = new Attributes();