8297587: Upgrade JLine to 3.22.0

Reviewed-by: vromero
This commit is contained in:
Jan Lahoda 2023-03-02 08:27:44 +00:00
parent 99f5687eb1
commit 4619e8bae8
54 changed files with 1320 additions and 717 deletions

View File

@ -14,7 +14,7 @@ package jdk.internal.org.jline.reader;
* @see Macro * @see Macro
* @see Reference * @see Reference
* @see Widget * @see Widget
* @see org.jline.keymap.KeyMap * @see jdk.internal.org.jline.keymap.KeyMap
* *
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a> * @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
*/ */

View File

@ -24,6 +24,7 @@ public class Candidate implements Comparable<Candidate> {
private final String suffix; private final String suffix;
private final String key; private final String key;
private final boolean complete; private final boolean complete;
private final int sort;
/** /**
* Simple constructor with only a single String as an argument. * Simple constructor with only a single String as an argument.
@ -31,7 +32,30 @@ public class Candidate implements Comparable<Candidate> {
* @param value the candidate * @param value the candidate
*/ */
public Candidate(String value) { public Candidate(String value) {
this(value, value, null, null, null, null, true); this(value, value, null, null, null, null, true, 0);
}
/**
* 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
* @param sort the sort flag
*/
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;
this.descr = descr;
this.suffix = suffix;
this.key = key;
this.complete = complete;
this.sort = sort;
} }
/** /**
@ -46,13 +70,7 @@ public class Candidate implements Comparable<Candidate> {
* @param complete the complete flag * @param complete the complete 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) {
this.value = Objects.requireNonNull(value); this(value, displ, group, descr, suffix, key, complete, 0);
this.displ = Objects.requireNonNull(displ);
this.group = group;
this.descr = descr;
this.suffix = suffix;
this.key = key;
this.complete = complete;
} }
/** /**
@ -133,9 +151,36 @@ public class Candidate implements Comparable<Candidate> {
return complete; return complete;
} }
/**
* Integer used to override default sort logic.
* @return the sort int
*/
public int sort() {
return sort;
}
@Override @Override
public int compareTo(Candidate o) { 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 @Override

View File

@ -10,7 +10,7 @@ package jdk.internal.org.jline.reader;
/** /**
* An extension of {@link ParsedLine} that, being aware of the quoting and escaping rules * 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. * should be escaped/quoted.
* *
* @author Eric Bottard * @author Eric Bottard

View File

@ -29,7 +29,7 @@ public interface CompletionMatcher {
/** /**
* *
* @param candidates list of candidates * @param candidates list of candidates
* @return a map of candidates that completion matcher matches * @return a list of candidates that completion matcher matches
*/ */
List<Candidate> matches(List<Candidate> candidates); List<Candidate> matches(List<Candidate> candidates);

View File

@ -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 * This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software. * BSD license in the documentation provided with this software.
@ -14,7 +14,28 @@ import jdk.internal.org.jline.utils.AttributedString;
public interface Highlighter { public interface Highlighter {
/**
* Highlight buffer
* @param reader LineReader
* @param buffer the buffer to be highlighted
* @return highlighted buffer
*/
AttributedString highlight(LineReader reader, String 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);
} }

View File

@ -61,12 +61,13 @@ public interface History extends Iterable<History.Entry>
void append(Path file, boolean incremental) throws IOException; 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 <code>true</code> only the events that
* are not contained within the internal list are added.
* @param file History file * @param file History file
* @param incremental If true incremental read operation is performed. * @param checkDuplicates If <code>true</code>, duplicate history entries will be discarded
* @throws IOException if a problem occurs * @throws IOException if a problem occurs
*/ */
void read(Path file, boolean incremental) throws IOException; void read(Path file, boolean checkDuplicates) throws IOException;
/** /**
* Purge history. * Purge history.

View File

@ -352,7 +352,7 @@ public interface LineReader {
String AMBIGUOUS_BINDING = "ambiguous-binding"; 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"; String HISTORY_IGNORE = "history-ignore";
@ -467,6 +467,9 @@ public interface LineReader {
/** Show command options tab completion candidates for zero length word */ /** Show command options tab completion candidates for zero length word */
EMPTY_WORD_OPTIONS(true), EMPTY_WORD_OPTIONS(true),
/** Disable the undo feature */
DISABLE_UNDO
; ;
private final boolean def; private final boolean def;
@ -699,7 +702,7 @@ public interface LineReader {
void runMacro(String macro); 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. * has just been read on the input stream.
* Compared to {@link Terminal#readMouseEvent()}, this method takes into account keys * Compared to {@link Terminal#readMouseEvent()}, this method takes into account keys
* that have been pushed back using {@link #runMacro(String)}. * that have been pushed back using {@link #runMacro(String)}.

View File

@ -118,6 +118,12 @@ public final class LineReaderBuilder {
throw new IOError(e); throw new IOError(e);
} }
} }
String appName = this.appName;
if (null == appName) {
appName = terminal.getName();
}
LineReaderImpl reader = new LineReaderImpl(terminal, appName, variables); LineReaderImpl reader = new LineReaderImpl(terminal, appName, variables);
if (history != null) { if (history != null) {
reader.setHistory(history); reader.setHistory(history);

View File

@ -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 * This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software. * BSD license in the documentation provided with this software.
@ -12,8 +12,8 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public interface Parser { public interface Parser {
static final String REGEX_VARIABLE = "[a-zA-Z_]{1,}[a-zA-Z0-9_-]*"; String REGEX_VARIABLE = "[a-zA-Z_]+[a-zA-Z0-9_-]*";
static final String REGEX_COMMAND = "[:]{0,1}[a-zA-Z]{1,}[a-zA-Z0-9_-]*"; String REGEX_COMMAND = "[:]?[a-zA-Z]+[a-zA-Z0-9_-]*";
ParsedLine parse(String line, int cursor, ParseContext context) throws SyntaxError; ParsedLine parse(String line, int cursor, ParseContext context) throws SyntaxError;
@ -34,7 +34,7 @@ public interface Parser {
} }
default String getCommand(final String line) { default String getCommand(final String line) {
String out = ""; String out;
Pattern patternCommand = Pattern.compile("^\\s*" + REGEX_VARIABLE + "=(" + REGEX_COMMAND + ")(\\s+|$)"); Pattern patternCommand = Pattern.compile("^\\s*" + REGEX_VARIABLE + "=(" + REGEX_COMMAND + ")(\\s+|$)");
Matcher matcher = patternCommand.matcher(line); Matcher matcher = patternCommand.matcher(line);
if (matcher.find()) { if (matcher.find()) {
@ -68,7 +68,7 @@ public interface Parser {
/** Parsed words will have all characters present in input line /** Parsed words will have all characters present in input line
* including quotes and escape chars. * including quotes and escape chars.
* May throw EOFError in which case we have incomplete input. * We should tolerate and ignore errors.
*/ */
SPLIT_LINE, SPLIT_LINE,

View File

@ -54,7 +54,7 @@ public class CompletionMatcherImpl implements CompletionMatcher {
break; 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<>(); : new ArrayList<>();
} }

View File

@ -27,6 +27,27 @@ public class DefaultParser implements Parser {
ANGLE // <> 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[] quoteChars = {'\'', '"'};
private char[] escapeChars = {'\\'}; private char[] escapeChars = {'\\'};
@ -39,6 +60,10 @@ public class DefaultParser implements Parser {
private char[] closingBrackets = null; 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 regexVariable = "[a-zA-Z_]+[a-zA-Z0-9_-]*((\\.|\\['|\\[\"|\\[)[a-zA-Z0-9_-]*(|']|\"]|]))?";
private String regexCommand = "[:]?[a-zA-Z]+[a-zA-Z0-9_-]*"; private String regexCommand = "[:]?[a-zA-Z]+[a-zA-Z0-9_-]*";
private int commandGroup = 4; private int commandGroup = 4;
@ -47,6 +72,16 @@ public class DefaultParser implements Parser {
// Chainable setters // 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) { public DefaultParser quoteChars(final char[] chars) {
this.quoteChars = chars; this.quoteChars = chars;
return this; return this;
@ -107,6 +142,22 @@ public class DefaultParser implements Parser {
return this.escapeChars; 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) { public void setEofOnUnclosedQuote(boolean eofOnUnclosedQuote) {
this.eofOnUnclosedQuote = eofOnUnclosedQuote; this.eofOnUnclosedQuote = eofOnUnclosedQuote;
} }
@ -225,6 +276,11 @@ public class DefaultParser implements Parser {
int rawWordStart = 0; int rawWordStart = 0;
BracketChecker bracketChecker = new BracketChecker(cursor); BracketChecker bracketChecker = new BracketChecker(cursor);
boolean quotedWord = false; 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++) { for (int i = 0; (line != null) && (i < line.length()); i++) {
// once we reach the cursor, set the // once we reach the cursor, set the
@ -237,7 +293,7 @@ public class DefaultParser implements Parser {
rawWordCursor = i - rawWordStart; rawWordCursor = i - rawWordStart;
} }
if (quoteStart < 0 && isQuoteChar(line, i)) { if (quoteStart < 0 && isQuoteChar(line, i) && !lineCommented && !blockCommented) {
// Start a quote block // Start a quote block
quoteStart = i; quoteStart = i;
if (current.length()==0) { if (current.length()==0) {
@ -258,17 +314,40 @@ public class DefaultParser implements Parser {
quoteStart = -1; quoteStart = -1;
quotedWord = false; quotedWord = false;
} else if (quoteStart < 0 && isDelimiter(line, i)) { } else if (quoteStart < 0 && isDelimiter(line, i)) {
// Delimiter if (lineCommented) {
if (current.length() > 0) { if (isCommentDelim(line, i, System.lineSeparator())) {
words.add(current.toString()); lineCommented = false;
current.setLength(0); // reset the arg
if (rawWordCursor >= 0 && rawWordLength < 0) {
rawWordLength = i - rawWordStart;
} }
} 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 { } 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)); current.append(line.charAt(i));
if (quoteStart < 0) { if (quoteStart < 0) {
bracketChecker.check(line, i); bracketChecker.check(line, i);
@ -301,6 +380,14 @@ public class DefaultParser implements Parser {
throw new EOFError(-1, -1, "Missing closing quote", line.charAt(quoteStart) == '\'' throw new EOFError(-1, -1, "Missing closing quote", line.charAt(quoteStart) == '\''
? "quote" : "dquote"); ? "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()) { if (bracketChecker.isClosingBracketMissing() || bracketChecker.isOpeningBracketMissing()) {
String message = null; String message = null;
String missing = null; String missing = null;
@ -333,6 +420,17 @@ public class DefaultParser implements Parser {
return !isQuoted(buffer, pos) && !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos); return !isQuoted(buffer, pos) && !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos);
} }
private int handleDelimiterAndGetRawWordLength(StringBuilder current, List<String> 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) { public boolean isQuoted(final CharSequence buffer, final int pos) {
return false; return false;
} }
@ -351,6 +449,36 @@ public class DefaultParser implements Parser {
return false; 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 @Override
public boolean isEscapeChar(char ch) { public boolean isEscapeChar(char ch) {
if (escapeChars != null) { if (escapeChars != null) {

View File

@ -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 * This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software. * BSD license in the documentation provided with this software.
@ -281,7 +281,7 @@ public class LineReaderImpl implements LineReader, Flushable
int candidateStartPosition = 0; int candidateStartPosition = 0;
public LineReaderImpl(Terminal terminal) throws IOException { public LineReaderImpl(Terminal terminal) throws IOException {
this(terminal, null, null); this(terminal, terminal.getName(), null);
} }
public LineReaderImpl(Terminal terminal, String appName) throws IOException { public LineReaderImpl(Terminal terminal, String appName) throws IOException {
@ -633,7 +633,8 @@ public class LineReaderImpl implements LineReader, Flushable
callWidget(CALLBACK_INIT); callWidget(CALLBACK_INIT);
undo.newState(buf.copy()); if (!isSet(Option.DISABLE_UNDO))
undo.newState(buf.copy());
// Draw initial prompt // Draw initial prompt
redrawLine(); redrawLine();
@ -679,7 +680,7 @@ public class LineReaderImpl implements LineReader, Flushable
if (!w.apply()) { if (!w.apply()) {
beep(); 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())) { && !copy.toString().equals(buf.toString())) {
undo.newState(buf.copy()); undo.newState(buf.copy());
} }
@ -739,8 +740,8 @@ public class LineReaderImpl implements LineReader, Flushable
} }
} finally { } finally {
lock.unlock(); lock.unlock();
startedReading.set(false);
} }
startedReading.set(false);
} }
} }
@ -1082,18 +1083,18 @@ public class LineReaderImpl implements LineReader, Flushable
if (isSet(Option.BRACKETED_PASTE)) { if (isSet(Option.BRACKETED_PASTE)) {
terminal.writer().write(BRACKETED_PASTE_OFF); 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 editor = (Editor) ctor.newInstance(terminal, new File(file.getParent()));
editor.setRestricted(true); editor.setRestricted(true);
editor.open(Collections.singletonList(file.getName())); editor.open(Collections.singletonList(file.getName()));
editor.run(); editor.run();
BufferedReader br = new BufferedReader(new FileReader(file)); try (BufferedReader br = new BufferedReader(new FileReader(file))) {
String line; String line;
commandsBuffer.clear(); commandsBuffer.clear();
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
commandsBuffer.add(line); commandsBuffer.add(line);
}
} }
br.close();
} }
// //
@ -3595,9 +3596,9 @@ public class LineReaderImpl implements LineReader, Flushable
File file = null; File file = null;
try { try {
file = File.createTempFile("jline-execute-", null); file = File.createTempFile("jline-execute-", null);
FileWriter writer = new FileWriter(file); try (FileWriter writer = new FileWriter(file)) {
writer.write(buf.toString()); writer.write(buf.toString());
writer.close(); }
editAndAddInBuffer(file); editAndAddInBuffer(file);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(terminal.writer()); e.printStackTrace(terminal.writer());
@ -3796,6 +3797,9 @@ public class LineReaderImpl implements LineReader, Flushable
Status status = Status.getStatus(terminal, false); Status status = Status.getStatus(terminal, false);
if (status != null) { if (status != null) {
if (terminal.getType().startsWith(AbstractWindowsTerminal.TYPE_WINDOWS)) {
status.resize();
}
status.redraw(); status.redraw();
} }
@ -3947,7 +3951,8 @@ public class LineReaderImpl implements LineReader, Flushable
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (char c: buffer.replace("\\", "\\\\").toCharArray()) { for (char c: buffer.replace("\\", "\\\\").toCharArray()) {
if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '^' || c == '*' 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('\\');
} }
sb.append(c); sb.append(c);
@ -4520,7 +4525,7 @@ public class LineReaderImpl implements LineReader, Flushable
} }
} }
private CompletingParsedLine wrap(ParsedLine line) { protected static CompletingParsedLine wrap(ParsedLine line) {
if (line instanceof CompletingParsedLine) { if (line instanceof CompletingParsedLine) {
return (CompletingParsedLine) line; return (CompletingParsedLine) line;
} else { } else {
@ -4625,6 +4630,11 @@ public class LineReaderImpl implements LineReader, Flushable
return size.getRows() - (status != null ? status.size() : 0); 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() { private int promptLines() {
AttributedString text = insertSecondaryPrompts(AttributedStringBuilder.append(prompt, buf.toString()), new ArrayList<>()); AttributedString text = insertSecondaryPrompts(AttributedStringBuilder.append(prompt, buf.toString()), new ArrayList<>());
return text.columnSplitLength(size.getColumns(), false, display.delayLineWrap()).size(); return text.columnSplitLength(size.getColumns(), false, display.delayLineWrap()).size();
@ -5070,18 +5080,19 @@ public class LineReaderImpl implements LineReader, Flushable
protected PostResult computePost(List<Candidate> possible, Candidate selection, List<Candidate> ordered, String completed, Function<String, Integer> wcwidth, int width, boolean autoGroup, boolean groupName, boolean rowsFirst) { protected PostResult computePost(List<Candidate> possible, Candidate selection, List<Candidate> ordered, String completed, Function<String, Integer> wcwidth, int width, boolean autoGroup, boolean groupName, boolean rowsFirst) {
List<Object> strings = new ArrayList<>(); List<Object> strings = new ArrayList<>();
boolean customOrder = possible.stream().anyMatch(c -> c.sort() != 0);
if (groupName) { if (groupName) {
Comparator<String> groupComparator = getGroupComparator(); Comparator<String> groupComparator = getGroupComparator();
Map<String, Map<String, Candidate>> sorted; Map<String, Map<Object, Candidate>> sorted;
sorted = groupComparator != null sorted = groupComparator != null
? new TreeMap<>(groupComparator) ? new TreeMap<>(groupComparator)
: new LinkedHashMap<>(); : new LinkedHashMap<>();
for (Candidate cand : possible) { for (Candidate cand : possible) {
String group = cand.group(); String group = cand.group();
sorted.computeIfAbsent(group != null ? group : "", s -> new LinkedHashMap<>()) sorted.computeIfAbsent(group != null ? group : "", s -> new LinkedHashMap<>())
.put(cand.value(), cand); .put((customOrder ? cand.sort() : cand.value()), cand);
} }
for (Map.Entry<String, Map<String, Candidate>> entry : sorted.entrySet()) { for (Map.Entry<String, Map<Object, Candidate>> entry : sorted.entrySet()) {
String group = entry.getKey(); String group = entry.getKey();
if (group.isEmpty() && sorted.size() > 1) { if (group.isEmpty() && sorted.size() > 1) {
group = getOthersGroupName(); group = getOthersGroupName();
@ -5096,13 +5107,13 @@ public class LineReaderImpl implements LineReader, Flushable
} }
} else { } else {
Set<String> groups = new LinkedHashSet<>(); Set<String> groups = new LinkedHashSet<>();
TreeMap<String, Candidate> sorted = new TreeMap<>(); TreeMap<Object, Candidate> sorted = new TreeMap<>();
for (Candidate cand : possible) { for (Candidate cand : possible) {
String group = cand.group(); String group = cand.group();
if (group != null) { if (group != null) {
groups.add(group); groups.add(group);
} }
sorted.put(cand.value(), cand); sorted.put((customOrder ? cand.sort() : cand.value()), cand);
} }
if (autoGroup) { if (autoGroup) {
strings.addAll(groups); strings.addAll(groups);
@ -5129,7 +5140,7 @@ public class LineReaderImpl implements LineReader, Flushable
this.startPos = startPos; this.startPos = startPos;
endLine = line.substring(line.lastIndexOf('\n') + 1); endLine = line.substring(line.lastIndexOf('\n') + 1);
boolean first = true; boolean first = true;
while (endLine.length() + (first ? startPos : 0) > width) { while (endLine.length() + (first ? startPos : 0) > width && width > 0) {
if (first) { if (first) {
endLine = endLine.substring(width - startPos); endLine = endLine.substring(width - startPos);
} else { } else {
@ -5207,7 +5218,7 @@ public class LineReaderImpl implements LineReader, Flushable
AttributedStringBuilder sb = new AttributedStringBuilder(); AttributedStringBuilder sb = new AttributedStringBuilder();
if (listSize > 0) { if (listSize > 0) {
if (isSet(Option.AUTO_MENU_LIST) 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); maxWidth = Math.max(maxWidth, MENU_LIST_WIDTH);
sb.tabs(Math.max(Math.min(candidateStartPosition, width - maxWidth - 1), 1)); sb.tabs(Math.max(Math.min(candidateStartPosition, width - maxWidth - 1), 1));
width = maxWidth + 2; width = maxWidth + 2;

View File

@ -41,7 +41,7 @@ import jdk.internal.org.jline.utils.AttributedStyle;
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a> * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3 * @since 2.3
* @deprecated use <code>org.jline.builtins.Completers$FileNameCompleter</code> instead * @deprecated use <code>jdk.internal.org.jline.builtins.Completers$FileNameCompleter</code> instead
*/ */
@Deprecated @Deprecated
public class FileNameCompleter implements Completer public class FileNameCompleter implements Completer

View File

@ -67,7 +67,7 @@ public class SystemCompleter implements Completer {
if (cmd != null) { if (cmd != null) {
if (completers.containsKey(cmd)) { if (completers.containsKey(cmd)) {
out = cmd; out = cmd;
} else if (aliasCommand.containsKey(cmd)) { } else {
out = aliasCommand.get(cmd); out = aliasCommand.get(cmd);
} }
} }

View File

@ -97,14 +97,14 @@ public class DefaultHistory implements History {
} }
@Override @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(); Path path = file != null ? file : getPath();
if (path != null) { if (path != null) {
try { try {
if (Files.exists(path)) { if (Files.exists(path)) {
Log.trace("Reading history from: ", path); Log.trace("Reading history from: ", path);
try (BufferedReader reader = Files.newBufferedReader(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())); setHistoryFileData(path, new HistoryFileData(items.size(), offset + items.size()));
maybeResize(); maybeResize();
} }

View File

@ -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 * This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software. * BSD license in the documentation provided with this software.
@ -16,22 +16,24 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException; 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.Optional;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function; 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.AbstractPosixTerminal;
import jdk.internal.org.jline.terminal.impl.AbstractTerminal; 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.DumbTerminal;
import jdk.internal.org.jline.terminal.impl.ExecPty; import jdk.internal.org.jline.terminal.spi.TerminalProvider;
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.utils.Log; import jdk.internal.org.jline.utils.Log;
import jdk.internal.org.jline.utils.OSUtils; 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_EXEC = "org.jline.terminal.exec";
public static final String PROP_DUMB = "org.jline.terminal.dumb"; 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_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 // 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_COLOR_DISTANCE = "org.jline.utils.colorDistance";
public static final String PROP_DISABLE_ALTERNATE_CHARSET = "org.jline.utils.disableAlternateCharset"; 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. * Returns the default system terminal.
* Terminals should be closed properly using the {@link Terminal#close()} * Terminals should be closed properly using the {@link Terminal#close()}
@ -97,6 +114,7 @@ public final class TerminalBuilder {
private Charset encoding; private Charset encoding;
private int codepage; private int codepage;
private Boolean system; private Boolean system;
private SystemOutput systemOutput;
private Boolean jna; private Boolean jna;
private Boolean jansi; private Boolean jansi;
private Boolean exec; private Boolean exec;
@ -128,6 +146,20 @@ public final class TerminalBuilder {
return this; 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) { public TerminalBuilder jna(boolean jna) {
this.jna = jna; this.jna = jna;
return this; return this;
@ -298,11 +330,18 @@ public final class TerminalBuilder {
encoding = Charset.forName(charsetName); encoding = Charset.forName(charsetName);
} }
} }
int codepage = this.codepage; if (encoding == null) {
if (codepage <= 0) { int codepage = this.codepage;
String str = System.getProperty(PROP_CODEPAGE); if (codepage <= 0) {
if (str != null) { String str = System.getProperty(PROP_CODEPAGE);
codepage = Integer.parseInt(str); if (str != null) {
codepage = Integer.parseInt(str);
}
}
if (codepage >= 0) {
encoding = getCodepageCharset(codepage);
} else {
encoding = StandardCharsets.UTF_8;
} }
} }
String type = this.type; String type = this.type;
@ -328,102 +367,112 @@ public final class TerminalBuilder {
if (dumb == null) { if (dumb == null) {
dumb = getBoolean(PROP_DUMB, null); dumb = getBoolean(PROP_DUMB, null);
} }
IllegalStateException exception = new IllegalStateException("Unable to create a terminal");
List<TerminalProvider> 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 && 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"); throw new IllegalArgumentException("Cannot create a system terminal using non System streams");
} }
Terminal terminal = null; if (attributes != null || size != null) {
IllegalStateException exception = new IllegalStateException("Unable to create a system terminal"); Log.warn("Attributes and size fields are ignored when creating a system terminal");
TerminalBuilderSupport tbs = new TerminalBuilderSupport(jna, jansi); }
if (tbs.isConsoleInput() && tbs.isConsoleOutput()) { 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<TerminalProvider.Stream, Boolean> 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) { if (attributes != null || size != null) {
Log.warn("Attributes and size fields are ignored when creating a system terminal"); Log.warn("Attributes and size fields are ignored when creating a system terminal");
} }
if (OSUtils.IS_WINDOWS) { boolean ansiPassThrough = OSUtils.IS_CONEMU;
if (!OSUtils.IS_CYGWIN && !OSUtils.IS_MSYSTEM) { // Cygwin defaults to XTERM, but actually supports 256 colors,
boolean ansiPassThrough = OSUtils.IS_CONEMU; // so if the value comes from the environment, change it to xterm-256color
if (tbs.hasJnaSupport()) { if ((OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) && "xterm".equals(type)
try { && this.type == null && System.getProperty(PROP_TYPE) == null) {
terminal = tbs.getJnaSupport().winSysTerminal(name, type, ansiPassThrough, encoding, codepage type = "xterm-256color";
, nativeSignals, signalHandler, paused, inputStreamWrapper); }
} catch (Throwable t) { for ( TerminalProvider provider : providers) {
Log.debug("Error creating JNA based terminal: ", t.getMessage(), t); if (terminal == null) {
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 { try {
// Cygwin defaults to XTERM, but actually supports 256 colors, terminal = provider.sysTerminal(name, type, ansiPassThrough, encoding,
// so if the value comes from the environment, change it to xterm-256color nativeSignals, signalHandler, paused, console, inputStreamWrapper);
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) { } catch (Throwable t) {
// ignore Log.debug("Error creating " + provider.name() + " based terminal: ", t.getMessage(), t);
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) {
try {
Pty pty = tbs.getExecPty();
terminal = new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (Throwable t) {
// Ignore if not a tty
Log.debug("Error creating EXEC based terminal: ", t.getMessage(), t);
exception.addSuppressed(t); exception.addSuppressed(t);
} }
} }
} }
if (terminal instanceof AbstractTerminal) { if (terminal == null && OSUtils.IS_WINDOWS && !jna && !jansi && (dumb == null || !dumb)) {
AbstractTerminal t = (AbstractTerminal) terminal; throw new IllegalStateException("Unable to create a system terminal. On windows, either "
if (SYSTEM_TERMINAL.compareAndSet(null, t)) { + "JNA or JANSI library is required. Make sure to add one of those in the classpath.");
t.setOnClose(() -> SYSTEM_TERMINAL.compareAndSet(t, null)); }
} else { }
exception.addSuppressed(new IllegalStateException("A system terminal is already running. " + if (terminal instanceof AbstractTerminal) {
"Make sure to use the created system Terminal on the LineReaderBuilder if you're using one " + AbstractTerminal t = (AbstractTerminal) terminal;
"or that previously created system Terminals have been correctly closed.")); if (SYSTEM_TERMINAL.compareAndSet(null, t)) {
terminal.close(); t.setOnClose(() -> SYSTEM_TERMINAL.compareAndSet(t, null));
terminal = 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)) { if (terminal == null && (dumb == null || dumb)) {
@ -433,7 +482,8 @@ public final class TerminalBuilder {
color = getBoolean(PROP_DUMB_COLOR, false); color = getBoolean(PROP_DUMB_COLOR, false);
// detect emacs using the env variable // detect emacs using the env variable
if (!color) { if (!color) {
color = System.getenv("INSIDE_EMACS") != null; String emacs = System.getenv("INSIDE_EMACS");
color = emacs != null && emacs.contains("comint");
} }
// detect Intellij Idea // detect Intellij Idea
if (!color) { if (!color) {
@ -441,12 +491,13 @@ public final class TerminalBuilder {
color = command != null && command.contains("idea"); color = command != null && command.contains("idea");
} }
if (!color) { if (!color) {
color = tbs.isConsoleOutput() && System.getenv("TERM") != null; color = system.get(TerminalProvider.Stream.Output) && System.getenv("TERM") != null;
} }
if (!color && dumb == null) { if (!color && dumb == null) {
if (Log.isDebugEnabled()) { if (Log.isDebugEnabled()) {
Log.warn("input is tty: {}", tbs.isConsoleInput()); Log.warn("input is tty: {}", system.get(TerminalProvider.Stream.Input));
Log.warn("output is tty: {}", tbs.isConsoleOutput()); 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); Log.warn("Creating a dumb terminal", exception);
} else { } else {
Log.warn("Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)"); Log.warn("Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)");
@ -454,33 +505,49 @@ public final class TerminalBuilder {
} }
} }
terminal = new DumbTerminal(name, color ? Terminal.TYPE_DUMB_COLOR : Terminal.TYPE_DUMB, terminal = new DumbTerminal(name, color ? Terminal.TYPE_DUMB_COLOR : Terminal.TYPE_DUMB,
inputStreamWrapper.apply(new FileInputStream(FileDescriptor.in)), new FileInputStream(FileDescriptor.in),
new FileOutputStream(FileDescriptor.out), new FileOutputStream(console == TerminalProvider.Stream.Output ? FileDescriptor.out : FileDescriptor.err),
encoding, signalHandler); encoding, signalHandler);
} }
if (terminal == null) {
throw exception;
}
return terminal;
} else { } else {
if (jna) { for ( TerminalProvider provider : providers) {
try { if (terminal == null) {
Pty pty = load(JnaSupport.class).open(attributes, size); try {
return new PosixPtyTerminal(name, type, pty, inputStreamWrapper.apply(in), out, encoding, signalHandler, paused); terminal = provider.newTerminal(name, type, inputStreamWrapper.apply(in), out, encoding, signalHandler, paused, attributes, size);
} catch (Throwable t) { } catch (Throwable t) {
Log.debug("Error creating JNA based terminal: ", t.getMessage(), 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);
}
}
return new ExternalTerminal(name, type, inputStreamWrapper.apply(in), out, encoding, signalHandler, paused, attributes, size);
} }
if (terminal == null) {
throw exception;
}
return terminal;
}
private TerminalProvider.Stream select(Map<TerminalProvider.Stream, Boolean> 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<TerminalProvider.Stream, Boolean> system, TerminalProvider.Stream... streams) {
for (TerminalProvider.Stream s : streams) {
if (system.get(s)) {
return s;
}
}
return null;
} }
private static String getParentProcessCommand() { private static String getParentProcessCommand() {
@ -512,6 +579,24 @@ public final class TerminalBuilder {
return ServiceLoader.load(clazz, clazz.getClassLoader()).iterator().next(); 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 * Allows an application to override the result of {@link #build()}. The
* intended use case is to allow a container or server application to control * intended use case is to allow a container or server application to control
@ -521,7 +606,7 @@ public final class TerminalBuilder {
* build tool uses a <code>LineReader</code> to implement an interactive shell. * build tool uses a <code>LineReader</code> to implement an interactive shell.
* One of its supported commands is <code>console</code> which invokes * One of its supported commands is <code>console</code> which invokes
* the scala REPL. The scala REPL also uses a <code>LineReader</code> and it * the scala REPL. The scala REPL also uses a <code>LineReader</code> and it
* is necessary to override the {@link Terminal} used by the REPL to * is necessary to override the {@link Terminal} used by the the REPL to
* share the same {@link Terminal} instance used by sbt. * share the same {@link Terminal} instance used by sbt.
* *
* <p> * <p>
@ -545,79 +630,4 @@ public final class TerminalBuilder {
TERMINAL_OVERRIDE.set(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;
}
}
} }

View File

@ -86,11 +86,6 @@ public abstract class AbstractPty implements Pty {
} }
} }
@Override
public int readBuffered(byte[] b) throws IOException {
return in.read(b);
}
private void setNonBlocking() { private void setNonBlocking() {
if (current == null if (current == null
|| current.getControlChar(Attributes.ControlChar.VMIN) != 0 || current.getControlChar(Attributes.ControlChar.VMIN) != 0

View File

@ -81,8 +81,8 @@ public abstract class AbstractWindowsTerminal extends AbstractTerminal {
protected boolean focusTracking = false; protected boolean focusTracking = false;
private volatile boolean closing; private volatile boolean closing;
public AbstractWindowsTerminal(Writer writer, String name, String type, Charset encoding, int codepage, boolean nativeSignals, SignalHandler signalHandler, Function<InputStream, InputStream> inputStreamWrapper) throws IOException { public AbstractWindowsTerminal(Writer writer, String name, String type, Charset encoding, boolean nativeSignals, SignalHandler signalHandler, Function<InputStream, InputStream> inputStreamWrapper) throws IOException {
super(name, type, selectCharset(encoding, codepage), signalHandler); super(name, type, encoding, signalHandler);
NonBlockingPumpReader reader = NonBlocking.nonBlockingPumpReader(); NonBlockingPumpReader reader = NonBlocking.nonBlockingPumpReader();
this.slaveInputPipe = reader.getWriter(); this.slaveInputPipe = reader.getWriter();
this.input = inputStreamWrapper.apply(NonBlocking.nonBlockingStream(reader, encoding())); this.input = inputStreamWrapper.apply(NonBlocking.nonBlockingStream(reader, encoding()));
@ -116,35 +116,6 @@ public abstract class AbstractWindowsTerminal extends AbstractTerminal {
} }
} }
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 @Override
public SignalHandler handle(Signal signal, SignalHandler handler) { public SignalHandler handle(Signal signal, SignalHandler handler) {
SignalHandler prev = super.handle(signal, handler); SignalHandler prev = super.handle(signal, handler);

View File

@ -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<Integer> 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> S load(Class<S> clazz) {
return ServiceLoader.load(clazz, clazz.getClassLoader()).iterator().next();
}
}

View File

@ -26,6 +26,7 @@ import jdk.internal.org.jline.terminal.Attributes.InputFlag;
import jdk.internal.org.jline.terminal.Attributes.LocalFlag; import jdk.internal.org.jline.terminal.Attributes.LocalFlag;
import jdk.internal.org.jline.terminal.Attributes.OutputFlag; import jdk.internal.org.jline.terminal.Attributes.OutputFlag;
import jdk.internal.org.jline.terminal.Size; 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.terminal.spi.Pty;
import jdk.internal.org.jline.utils.OSUtils; import jdk.internal.org.jline.utils.OSUtils;
@ -34,20 +35,23 @@ import static jdk.internal.org.jline.utils.ExecHelper.exec;
public class ExecPty extends AbstractPty implements Pty { public class ExecPty extends AbstractPty implements Pty {
private final String name; 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 { try {
String result = exec(true, OSUtils.TTY_COMMAND); 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) { } catch (IOException e) {
throw new IOException("Not a tty", 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.name = name;
this.system = system; this.console = console;
} }
@Override @Override
@ -70,16 +74,18 @@ public class ExecPty extends AbstractPty implements Pty {
@Override @Override
protected InputStream doGetSlaveInput() throws IOException { protected InputStream doGetSlaveInput() throws IOException {
return system return console != null
? new FileInputStream(FileDescriptor.in) ? new FileInputStream(FileDescriptor.in)
: new FileInputStream(getName()); : new FileInputStream(getName());
} }
@Override @Override
public OutputStream getSlaveOutput() throws IOException { public OutputStream getSlaveOutput() throws IOException {
return system return console == TerminalProvider.Stream.Output
? new FileOutputStream(FileDescriptor.out) ? new FileOutputStream(FileDescriptor.out)
: new FileOutputStream(getName()); : console == TerminalProvider.Stream.Error
? new FileOutputStream(FileDescriptor.err)
: new FileOutputStream(getName());
} }
@Override @Override
@ -93,23 +99,11 @@ public class ExecPty extends AbstractPty implements Pty {
List<String> commands = getFlagsToSet(attr, getAttr()); List<String> commands = getFlagsToSet(attr, getAttr());
if (!commands.isEmpty()) { if (!commands.isEmpty()) {
commands.add(0, OSUtils.STTY_COMMAND); commands.add(0, OSUtils.STTY_COMMAND);
if (!system) { if (console == null) {
commands.add(1, OSUtils.STTY_F_OPTION); commands.add(1, OSUtils.STTY_F_OPTION);
commands.add(2, getName()); commands.add(2, getName());
} }
try { exec(console != null, commands.toArray(new String[0]));
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;
}
}
} }
} }
@ -171,7 +165,7 @@ public class ExecPty extends AbstractPty implements Pty {
} }
protected String doGetConfig() throws IOException { protected String doGetConfig() throws IOException {
return system return console != null
? exec(true, OSUtils.STTY_COMMAND, "-a") ? exec(true, OSUtils.STTY_COMMAND, "-a")
: exec(false, OSUtils.STTY_COMMAND, OSUtils.STTY_F_OPTION, getName(), "-a"); : exec(false, OSUtils.STTY_COMMAND, OSUtils.STTY_F_OPTION, getName(), "-a");
} }
@ -280,7 +274,7 @@ public class ExecPty extends AbstractPty implements Pty {
@Override @Override
public void setSize(Size size) throws IOException { public void setSize(Size size) throws IOException {
if (system) { if (console != null) {
exec(true, exec(true,
OSUtils.STTY_COMMAND, OSUtils.STTY_COMMAND,
"columns", Integer.toString(size.getColumns()), "columns", Integer.toString(size.getColumns()),
@ -296,7 +290,7 @@ public class ExecPty extends AbstractPty implements Pty {
@Override @Override
public String toString() { public String toString() {
return "ExecPty[" + getName() + (system ? ", system]" : "]"); return "ExecPty[" + getName() + (console != null ? ", system]" : "]");
} }
} }

View File

@ -15,7 +15,6 @@ import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import jdk.internal.org.jline.terminal.spi.Pty; import jdk.internal.org.jline.terminal.spi.Pty;
import jdk.internal.org.jline.utils.ClosedException; import jdk.internal.org.jline.utils.ClosedException;
@ -143,10 +142,10 @@ public class PosixPtyTerminal extends AbstractPosixTerminal {
} }
} }
private class InputStreamWrapper extends NonBlockingInputStream { private static class InputStreamWrapper extends NonBlockingInputStream {
private final NonBlockingInputStream in; private final NonBlockingInputStream in;
private final AtomicBoolean closed = new AtomicBoolean(); private volatile boolean closed;
protected InputStreamWrapper(NonBlockingInputStream in) { protected InputStreamWrapper(NonBlockingInputStream in) {
this.in = in; this.in = in;
@ -154,7 +153,7 @@ public class PosixPtyTerminal extends AbstractPosixTerminal {
@Override @Override
public int read(long timeout, boolean isPeek) throws IOException { public int read(long timeout, boolean isPeek) throws IOException {
if (closed.get()) { if (closed) {
throw new ClosedException(); throw new ClosedException();
} }
return in.read(timeout, isPeek); return in.read(timeout, isPeek);
@ -162,7 +161,7 @@ public class PosixPtyTerminal extends AbstractPosixTerminal {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
closed.set(true); closed = true;
} }
} }

View File

@ -16,6 +16,7 @@ import java.io.PrintWriter;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import jdk.internal.org.jline.utils.NonBlocking; import jdk.internal.org.jline.utils.NonBlocking;
import jdk.internal.org.jline.terminal.spi.Pty; import jdk.internal.org.jline.terminal.spi.Pty;
@ -34,11 +35,12 @@ public class PosixSysTerminal extends AbstractPosixTerminal {
protected final Map<Signal, Object> nativeHandlers = new HashMap<>(); protected final Map<Signal, Object> nativeHandlers = new HashMap<>();
protected final Task closer; protected final Task closer;
public PosixSysTerminal(String name, String type, Pty pty, InputStream in, OutputStream out, Charset encoding, public PosixSysTerminal(String name, String type, Pty pty, Charset encoding,
boolean nativeSignals, SignalHandler signalHandler) throws IOException { boolean nativeSignals, SignalHandler signalHandler,
Function<InputStream, InputStream> inputStreamWrapper) throws IOException {
super(name, type, pty, encoding, signalHandler); super(name, type, pty, encoding, signalHandler);
this.input = NonBlocking.nonBlocking(getName(), in); this.input = NonBlocking.nonBlocking(getName(), inputStreamWrapper.apply(pty.getSlaveInput()));
this.output = out; this.output = pty.getSlaveOutput();
this.reader = NonBlocking.nonBlocking(getName(), input, encoding()); this.reader = NonBlocking.nonBlocking(getName(), input, encoding());
this.writer = new PrintWriter(new OutputStreamWriter(output, encoding())); this.writer = new PrintWriter(new OutputStreamWriter(output, encoding()));
parseInfoCmp(); parseInfoCmp();

View File

@ -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<InputStream, InputStream> 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<InputStream, InputStream> 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<InputStream, InputStream> 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;
}
}

View File

@ -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();
}

View File

@ -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<InputStream, InputStream> inputStreamWrapper) throws IOException;
boolean isWindowsConsole();
boolean isConsoleOutput();
boolean isConsoleInput();
}

View File

@ -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<InputStream, InputStream> 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);
}
}
}

View File

@ -534,7 +534,7 @@ public class Colors {
H = 0; H = 0;
return H; return H;
} else { } else {
throw new IllegalArgumentException("h outside assumed range 0..360: " + Double.toString(h)); throw new IllegalArgumentException("h outside assumed range 0..360: " + h);
} }
} }

View File

@ -12,7 +12,7 @@ import java.io.Flushable;
import java.io.IOError; import java.io.IOError;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Stack; import java.util.ArrayDeque;
/** /**
* Curses helper methods. * Curses helper methods.
@ -21,8 +21,8 @@ import java.util.Stack;
*/ */
public final class Curses { public final class Curses {
private static Object[] sv = new Object[26]; private static final Object[] sv = new Object[26];
private static Object[] dv = new Object[26]; private static final Object[] dv = new Object[26];
private static final int IFTE_NONE = 0; private static final int IFTE_NONE = 0;
private static final int IFTE_IF = 1; private static final int IFTE_IF = 1;
@ -68,7 +68,7 @@ public final class Curses {
int length = str.length(); int length = str.length();
int ifte = IFTE_NONE; int ifte = IFTE_NONE;
boolean exec = true; boolean exec = true;
Stack<Object> stack = new Stack<>(); ArrayDeque<Object> stack = new ArrayDeque<>();
while (index < length) { while (index < length) {
char ch = str.charAt(index++); char ch = str.charAt(index++);
switch (ch) { switch (ch) {
@ -197,7 +197,7 @@ public final class Curses {
int start = index; int start = index;
while (str.charAt(index++) != '}') ; while (str.charAt(index++) != '}') ;
if (exec) { if (exec) {
int v = Integer.valueOf(str.substring(start, index - 1)); int v = Integer.parseInt(str.substring(start, index - 1));
stack.push(v); stack.push(v);
} }
break; break;
@ -470,7 +470,7 @@ public final class Curses {
} else if (pop instanceof Boolean) { } else if (pop instanceof Boolean) {
return (Boolean) pop ? 1 : 0; return (Boolean) pop ? 1 : 0;
} else { } else {
return Integer.valueOf(pop.toString()); return Integer.parseInt(pop.toString());
} }
} }

View File

@ -187,7 +187,7 @@ public class Display {
int lineIndex = 0; int lineIndex = 0;
int currentPos = 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; boolean wrapNeeded = false;
while (lineIndex < numLines) { while (lineIndex < numLines) {
AttributedString oldLine = AttributedString oldLine =

View File

@ -503,7 +503,7 @@ public final class InfoCmp {
public String[] getNames() { public String[] getNames() {
return getCapabilitiesByName().entrySet().stream() return getCapabilitiesByName().entrySet().stream()
.filter(e -> e.getValue() == this) .filter(e -> e.getValue() == this)
.map(Map.Entry::getValue) .map(Map.Entry::getKey)
.toArray(String[]::new); .toArray(String[]::new);
} }

View File

@ -95,13 +95,9 @@ public class NonBlocking {
@Override @Override
public int read(long timeout, boolean isPeek) throws IOException { public int read(long timeout, boolean isPeek) throws IOException {
boolean isInfinite = (timeout <= 0L); Timeout t = new Timeout(timeout);
while (!bytes.hasRemaining() && (isInfinite || timeout > 0L)) { while (!bytes.hasRemaining() && !t.elapsed()) {
long start = 0; int c = reader.read(t.timeout());
if (!isInfinite) {
start = System.currentTimeMillis();
}
int c = reader.read(timeout);
if (c == EOF) { if (c == EOF) {
return EOF; return EOF;
} }
@ -117,9 +113,6 @@ public class NonBlocking {
encoder.encode(chars, bytes, false); encoder.encode(chars, bytes, false);
bytes.flip(); bytes.flip();
} }
if (!isInfinite) {
timeout -= System.currentTimeMillis() - start;
}
} }
if (bytes.hasRemaining()) { if (bytes.hasRemaining()) {
if (isPeek) { if (isPeek) {
@ -151,21 +144,17 @@ public class NonBlocking {
public NonBlockingInputStreamReader(NonBlockingInputStream input, CharsetDecoder decoder) { public NonBlockingInputStreamReader(NonBlockingInputStream input, CharsetDecoder decoder) {
this.input = input; this.input = input;
this.decoder = decoder; this.decoder = decoder;
this.bytes = ByteBuffer.allocate(4); this.bytes = ByteBuffer.allocate(2048);
this.chars = CharBuffer.allocate(2); this.chars = CharBuffer.allocate(1024);
this.bytes.limit(0); this.bytes.limit(0);
this.chars.limit(0); this.chars.limit(0);
} }
@Override @Override
protected int read(long timeout, boolean isPeek) throws IOException { protected int read(long timeout, boolean isPeek) throws IOException {
boolean isInfinite = (timeout <= 0L); Timeout t = new Timeout(timeout);
while (!chars.hasRemaining() && (isInfinite || timeout > 0L)) { while (!chars.hasRemaining() && !t.elapsed()) {
long start = 0; int b = input.read(t.timeout());
if (!isInfinite) {
start = System.currentTimeMillis();
}
int b = input.read(timeout);
if (b == EOF) { if (b == EOF) {
return EOF; return EOF;
} }
@ -181,10 +170,6 @@ public class NonBlocking {
decoder.decode(bytes, chars, false); decoder.decode(bytes, chars, false);
chars.flip(); chars.flip();
} }
if (!isInfinite) {
timeout -= System.currentTimeMillis() - start;
}
} }
if (chars.hasRemaining()) { if (chars.hasRemaining()) {
if (isPeek) { if (isPeek) {
@ -198,46 +183,37 @@ public class NonBlocking {
} }
@Override @Override
public int readBuffered(char[] b) throws IOException { public int readBuffered(char[] b, int off, int len, long timeout) throws IOException {
if (b == null) { if (b == null) {
throw new NullPointerException(); 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; return 0;
} else if (chars.hasRemaining()) {
int r = Math.min(len, chars.remaining());
chars.get(b, off, r);
return r;
} else { } else {
if (chars.hasRemaining()) { Timeout t = new Timeout(timeout);
int r = Math.min(b.length, chars.remaining()); while (!chars.hasRemaining() && !t.elapsed()) {
chars.get(b); if (!bytes.hasRemaining()) {
return r; bytes.position(0);
} else { bytes.limit(0);
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();
} }
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;
} }
} }

View File

@ -79,12 +79,34 @@ public abstract class NonBlockingInputStream extends InputStream {
} }
public int readBuffered(byte[] b) 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) { if (b == null) {
throw new NullPointerException(); 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; return 0;
} else { } 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;
} }
} }

View File

@ -123,20 +123,17 @@ public class NonBlockingInputStreamImpl
notifyAll(); notifyAll();
} }
boolean isInfinite = (timeout <= 0L);
/* /*
* So the thread is currently doing the reading for us. So * So the thread is currently doing the reading for us. So
* now we play the waiting game. * now we play the waiting game.
*/ */
while (isInfinite || timeout > 0L) { Timeout t = new Timeout(timeout);
long start = System.currentTimeMillis (); while (!t.elapsed()) {
try { try {
if (Thread.interrupted()) { if (Thread.interrupted()) {
throw new InterruptedException(); throw new InterruptedException();
} }
wait(timeout); wait(t.timeout());
} }
catch (InterruptedException e) { catch (InterruptedException e) {
exception = (IOException) new InterruptedIOException().initCause(e); exception = (IOException) new InterruptedIOException().initCause(e);
@ -155,10 +152,6 @@ public class NonBlockingInputStreamImpl
assert exception == null; assert exception == null;
break; break;
} }
if (!isInfinite) {
timeout -= System.currentTimeMillis() - start;
}
} }
} }

View File

@ -45,24 +45,17 @@ public class NonBlockingPumpInputStream extends NonBlockingInputStream {
} }
private int wait(ByteBuffer buffer, long timeout) throws IOException { private int wait(ByteBuffer buffer, long timeout) throws IOException {
boolean isInfinite = (timeout <= 0L); Timeout t = new Timeout(timeout);
long end = 0; while (!closed && !buffer.hasRemaining() && !t.elapsed()) {
if (!isInfinite) {
end = System.currentTimeMillis() + timeout;
}
while (!closed && !buffer.hasRemaining() && (isInfinite || timeout > 0L)) {
// Wake up waiting readers/writers // Wake up waiting readers/writers
notifyAll(); notifyAll();
try { try {
wait(timeout); wait(t.timeout());
checkIoException(); checkIoException();
} catch (InterruptedException e) { } catch (InterruptedException e) {
checkIoException(); checkIoException();
throw new InterruptedIOException(); throw new InterruptedIOException();
} }
if (!isInfinite) {
timeout = end - System.currentTimeMillis();
}
} }
return buffer.hasRemaining() return buffer.hasRemaining()
? 0 ? 0
@ -107,17 +100,25 @@ public class NonBlockingPumpInputStream extends NonBlockingInputStream {
} }
@Override @Override
public synchronized int readBuffered(byte[] b) throws IOException { public synchronized int readBuffered(byte[] b, int off, int len, long timeout) throws IOException {
checkIoException(); if (b == null) {
int res = wait(readBuffer, 0L); throw new NullPointerException();
if (res >= 0) { } else if (off < 0 || len < 0 || off + len < b.length) {
res = 0; throw new IllegalArgumentException();
while (res < b.length && readBuffer.hasRemaining()) { } else if (len == 0) {
b[res++] = (byte) (readBuffer.get() & 0x00FF); 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) { public synchronized void setIoException(IOException exception) {

View File

@ -106,10 +106,12 @@ public class NonBlockingPumpReader extends NonBlockingReader {
} }
@Override @Override
public int readBuffered(char[] b) throws IOException { public int readBuffered(char[] b, int off, int len, long timeout) throws IOException {
if (b == null) { if (b == null) {
throw new NullPointerException(); 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; return 0;
} else { } else {
final ReentrantLock lock = this.lock; final ReentrantLock lock = this.lock;
@ -117,7 +119,13 @@ public class NonBlockingPumpReader extends NonBlockingReader {
try { try {
if (!closed && count == 0) { if (!closed && count == 0) {
try { try {
notEmpty.await(); if (timeout > 0) {
if (!notEmpty.await(timeout, TimeUnit.MILLISECONDS)) {
throw new IOException( "Timeout reading" );
}
} else {
notEmpty.await();
}
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw (IOException) new InterruptedIOException().initCause(e); throw (IOException) new InterruptedIOException().initCause(e);
} }
@ -127,9 +135,9 @@ public class NonBlockingPumpReader extends NonBlockingReader {
} else if (count == 0) { } else if (count == 0) {
return READ_EXPIRED; return READ_EXPIRED;
} else { } else {
int r = Math.min(b.length, count); int r = Math.min(len, count);
for (int i = 0; i < r; i++) { for (int i = 0; i < r; i++) {
b[i] = buffer[read++]; b[off + i] = buffer[read++];
if (read == buffer.length) { if (read == buffer.length) {
read = 0; read = 0;
} }

View File

@ -85,7 +85,15 @@ public abstract class NonBlockingReader extends Reader {
return 1; 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() { public int available() {
return 0; return 0;

View File

@ -91,10 +91,12 @@ public class NonBlockingReaderImpl
} }
@Override @Override
public int readBuffered(char[] b) throws IOException { public int readBuffered(char[] b, int off, int len, long timeout) throws IOException {
if (b == null) { if (b == null) {
throw new NullPointerException(); 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; return 0;
} else if (exception != null) { } else if (exception != null) {
assert ch == READ_EXPIRED; assert ch == READ_EXPIRED;
@ -105,15 +107,16 @@ public class NonBlockingReaderImpl
b[0] = (char) ch; b[0] = (char) ch;
ch = READ_EXPIRED; ch = READ_EXPIRED;
return 1; return 1;
} else if (!threadIsReading) { } else if (!threadIsReading && timeout <= 0) {
return in.read(b); return in.read(b, off, len);
} else { } else {
int c = read(-1, false); // TODO: rework implementation to read as much as possible
int c = read(timeout, false);
if (c >= 0) { if (c >= 0) {
b[0] = (char) c; b[off] = (char) c;
return 1; return 1;
} else { } else {
return -1; return c;
} }
} }
} }
@ -158,20 +161,17 @@ public class NonBlockingReaderImpl
notifyAll(); notifyAll();
} }
boolean isInfinite = (timeout <= 0L);
/* /*
* So the thread is currently doing the reading for us. So * So the thread is currently doing the reading for us. So
* now we play the waiting game. * now we play the waiting game.
*/ */
while (isInfinite || timeout > 0L) { Timeout t = new Timeout(timeout);
long start = System.currentTimeMillis (); while (!t.elapsed()) {
try { try {
if (Thread.interrupted()) { if (Thread.interrupted()) {
throw new InterruptedException(); throw new InterruptedException();
} }
wait(timeout); wait(t.timeout());
} }
catch (InterruptedException e) { catch (InterruptedException e) {
exception = (IOException) new InterruptedIOException().initCause(e); exception = (IOException) new InterruptedIOException().initCause(e);
@ -190,10 +190,6 @@ public class NonBlockingReaderImpl
assert exception == null; assert exception == null;
break; break;
} }
if (!isInfinite) {
timeout -= System.currentTimeMillis() - start;
}
} }
} }

View File

@ -28,6 +28,12 @@ public class OSUtils {
&& (System.getenv("MSYSTEM").startsWith("MINGW") && (System.getenv("MSYSTEM").startsWith("MINGW")
|| System.getenv("MSYSTEM").equals("MSYS")); || 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 public static final boolean IS_CONEMU = IS_WINDOWS
&& System.getenv("ConEmuPID") != null; && System.getenv("ConEmuPID") != null;
@ -38,17 +44,20 @@ public class OSUtils {
public static String STTY_COMMAND; public static String STTY_COMMAND;
public static String STTY_F_OPTION; public static String STTY_F_OPTION;
public static String INFOCMP_COMMAND; public static String INFOCMP_COMMAND;
public static String TEST_COMMAND;
static { static {
String tty; String tty;
String stty; String stty;
String sttyfopt; String sttyfopt;
String infocmp; String infocmp;
String test;
if (OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) { if (OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) {
tty = "tty.exe"; tty = null;
stty = "stty.exe"; stty = null;
sttyfopt = null; sttyfopt = null;
infocmp = "infocmp.exe"; infocmp = null;
test = null;
String path = System.getenv("PATH"); String path = System.getenv("PATH");
if (path != null) { if (path != null) {
String[] paths = path.split(";"); String[] paths = path.split(";");
@ -62,23 +71,35 @@ public class OSUtils {
if (infocmp == null && new File(p, "infocmp.exe").exists()) { if (infocmp == null && new File(p, "infocmp.exe").exists()) {
infocmp = new File(p, "infocmp.exe").getAbsolutePath(); 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 { } else {
tty = "tty"; tty = "tty";
stty = "stty"; stty = IS_OSX ? "/bin/stty" : "stty";
sttyfopt = IS_OSX ? "-f" : "-F";
infocmp = "infocmp"; infocmp = "infocmp";
if (IS_OSX) { test = "/bin/test";
sttyfopt = "-f";
}
else {
sttyfopt = "-F";
}
} }
TTY_COMMAND = tty; TTY_COMMAND = tty;
STTY_COMMAND = stty; STTY_COMMAND = stty;
STTY_F_OPTION = sttyfopt; STTY_F_OPTION = sttyfopt;
INFOCMP_COMMAND = infocmp; INFOCMP_COMMAND = infocmp;
TEST_COMMAND = test;
} }
} }

View File

@ -36,7 +36,7 @@ public class PumpReader extends Reader {
} }
public PumpReader(int bufferSize) { public PumpReader(int bufferSize) {
char[] buf = new char[bufferSize]; char[] buf = new char[Math.max(bufferSize, 2)];
this.readBuffer = CharBuffer.wrap(buf); this.readBuffer = CharBuffer.wrap(buf);
this.writeBuffer = CharBuffer.wrap(buf); this.writeBuffer = CharBuffer.wrap(buf);
this.writer = new Writer(this); this.writer = new Writer(this);
@ -53,12 +53,52 @@ public class PumpReader extends Reader {
return new InputStream(this, charset); return new InputStream(this, charset);
} }
private boolean wait(CharBuffer buffer) throws InterruptedIOException { /**
if (closed) { * Blocks until more input is available, even if {@link #readBuffer} already
return false; * 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");
} }
int oldRemaining = readBuffer.remaining();
do {
if (closed) {
return false;
}
// Wake up waiting writers
notifyAll();
try {
wait();
} 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()) { while (!buffer.hasRemaining()) {
if (closed) {
return false;
}
// Wake up waiting readers/writers // Wake up waiting readers/writers
notifyAll(); notifyAll();
@ -67,19 +107,15 @@ public class PumpReader extends Reader {
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new InterruptedIOException(); throw new InterruptedIOException();
} }
if (closed) {
return false;
}
} }
return true; 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 * @throws InterruptedIOException If {@link #wait()} is interrupted
*/ */
private boolean waitForInput() throws InterruptedIOException { private boolean waitForInput() throws InterruptedIOException {
@ -94,7 +130,8 @@ public class PumpReader extends Reader {
* @throws ClosedException If the reader was closed * @throws ClosedException If the reader was closed
*/ */
private void waitForBufferSpace() throws InterruptedIOException, ClosedException { 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(); throw new ClosedException();
} }
} }
@ -122,7 +159,9 @@ public class PumpReader extends Reader {
* @return If more input is available * @return If more input is available
*/ */
private boolean rewindReadBuffer() { private boolean rewindReadBuffer() {
return rewind(readBuffer, writeBuffer) && readBuffer.hasRemaining(); boolean rw = rewind(readBuffer, writeBuffer) && readBuffer.hasRemaining();
notifyAll();
return rw;
} }
/** /**
@ -131,6 +170,7 @@ public class PumpReader extends Reader {
*/ */
private void rewindWriteBuffer() { private void rewindWriteBuffer() {
rewind(writeBuffer, readBuffer); rewind(writeBuffer, readBuffer);
notifyAll();
} }
@Override @Override
@ -202,10 +242,33 @@ public class PumpReader extends Reader {
} }
private void encodeBytes(CharsetEncoder encoder, ByteBuffer output) throws IOException { private void encodeBytes(CharsetEncoder encoder, ByteBuffer output) throws IOException {
int oldPos = output.position();
CoderResult result = encoder.encode(readBuffer, output, false); CoderResult result = encoder.encode(readBuffer, output, false);
if (rewindReadBuffer() && result.isUnderflow()) { int encodedCount = output.position() - oldPos;
encoder.encode(readBuffer, output, false);
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(); rewindReadBuffer();
} else if (result.isOverflow()) {
if (encodedCount == 0) {
throw new AssertionError("Output buffer has not enough space");
}
} else {
result.throwException();
} }
} }
@ -334,7 +397,7 @@ public class PumpReader extends Reader {
this.encoder = charset.newEncoder() this.encoder = charset.newEncoder()
.onUnmappableCharacter(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE)
.onMalformedInput(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 // No input available after initialization
buffer.limit(0); buffer.limit(0);

View File

@ -241,7 +241,7 @@ public class StyleResolver {
if (spec.length() == 1) { if (spec.length() == 1) {
// log.warning("Invalid style-reference; missing discriminator: " + spec); // log.warning("Invalid style-reference; missing discriminator: " + spec);
} else { } else {
String name = spec.substring(1, spec.length()); String name = spec.substring(1);
String resolvedSpec = source.apply(name); String resolvedSpec = source.apply(name);
if (resolvedSpec != null) { if (resolvedSpec != null) {
return apply(style, resolvedSpec); return apply(style, resolvedSpec);

View File

@ -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;
}
}

View File

@ -70,6 +70,7 @@ public final class WCWidth {
(ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
(ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
(ucs >= 0xffe0 && ucs <= 0xffe6) || (ucs >= 0xffe0 && ucs <= 0xffe6) ||
(ucs >= 0x1f000 && ucs <= 0x1feee) ||
(ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x20000 && ucs <= 0x2fffd) ||
(ucs >= 0x30000 && ucs <= 0x3fffd))) ? 1 : 0); (ucs >= 0x30000 && ucs <= 0x3fffd))) ? 1 : 0);
} }
@ -123,8 +124,8 @@ public final class WCWidth {
new Interval( 0x10A01, 0x10A03 ), new Interval( 0x10A05, 0x10A06 ), new Interval( 0x10A0C, 0x10A0F ), 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( 0x10A38, 0x10A3A ), new Interval( 0x10A3F, 0x10A3F ), new Interval( 0x1D167, 0x1D169 ),
new Interval( 0x1D173, 0x1D182 ), new Interval( 0x1D185, 0x1D18B ), new Interval( 0x1D1AA, 0x1D1AD ), 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( 0x1D242, 0x1D244 ), new Interval( 0x1F3FB, 0x1F3FF ), new Interval( 0xE0001, 0xE0001 ),
new Interval( 0xE0100, 0xE01EF ) new Interval( 0xE0020, 0xE007F ), new Interval( 0xE0100, 0xE01EF )
}; };
private static class Interval { private static class Interval {

View File

@ -2,7 +2,7 @@ windows-vtp|windows with virtual terminal processing,
am, mc5i, mir, msgr, am, mc5i, mir, msgr,
colors#256, cols#80, it#8, lines#24, ncv#3, pairs#64, 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, 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, cuf=\E[%p1%dC, cuf1=\E[C, cup=\E[%i%p1%d;%p2%dH,
cuu=\E[%p1%dA, cuu1=\E[A, cuu=\E[%p1%dA, cuu1=\E[A,
il=\E[%p1%dL, il1=\E[L, il=\E[%p1%dL, il1=\E[L,

View File

@ -48,8 +48,6 @@ module jdk.internal.le {
exports jdk.internal.org.jline.terminal.spi to exports jdk.internal.org.jline.terminal.spi to
jdk.jshell; jdk.jshell;
uses jdk.internal.org.jline.terminal.spi.JnaSupport;
// Console // Console
provides jdk.internal.io.JdkConsoleProvider with provides jdk.internal.io.JdkConsoleProvider with
jdk.internal.org.jline.JdkConsoleProviderImpl; jdk.internal.org.jline.JdkConsoleProviderImpl;

View File

@ -1,4 +1,4 @@
## JLine v3.20.0 ## JLine v3.22.0
### JLine License ### JLine License
<pre> <pre>
@ -41,10 +41,10 @@ OF THE POSSIBILITY OF SUCH DAMAGE.
4th Party Dependency 4th Party Dependency
============= =============
org.fusesource.jansi version 1.17.1 org.fusesource.jansi version 2.4.0
org.apache.sshd 2.1 to 3 org.apache.sshd 2.9.2
org.apache.felix.gogo.runtime 1.1.2 org.apache.felix.gogo.runtime 1.1.6
org.apache.felix.gogo.jline 1.1.4 org.apache.felix.gogo.jline 1.1.8
============= =============
Apache License Apache License
Version 2.0, January 2004 Version 2.0, January 2004
@ -262,7 +262,7 @@ slf4j
SLF4J source code and binaries are distributed under the MIT license. 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. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining

View File

@ -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<InputStream, InputStream> 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();
}
}

View File

@ -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<InputStream, InputStream> 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<InputStream, InputStream> 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);
// }
}
}

View File

@ -17,17 +17,17 @@ import java.io.IOException;
class JnaWinConsoleWriter extends AbstractWindowsConsoleWriter { class JnaWinConsoleWriter extends AbstractWindowsConsoleWriter {
private final Pointer consoleHandle; private final Pointer console;
private final IntByReference writtenChars = new IntByReference(); private final IntByReference writtenChars = new IntByReference();
JnaWinConsoleWriter(Pointer consoleHandle) { JnaWinConsoleWriter(Pointer console) {
this.consoleHandle = consoleHandle; this.console = console;
} }
@Override @Override
protected void writeConsole(char[] text, int len) throws IOException { protected void writeConsole(char[] text, int len) throws IOException {
try { try {
Kernel32.INSTANCE.WriteConsoleW(this.consoleHandle, text, len, this.writtenChars, null); Kernel32.INSTANCE.WriteConsoleW(this.console, text, len, this.writtenChars, null);
} catch (LastErrorException e) { } catch (LastErrorException e) {
throw new IOException("Failed to write to console", e); throw new IOException("Failed to write to console", e);
} }

View File

@ -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 * This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software. * BSD license in the documentation provided with this software.
@ -19,11 +19,10 @@ import java.util.function.IntConsumer;
//import com.sun.jna.LastErrorException; //import com.sun.jna.LastErrorException;
//import com.sun.jna.Pointer; //import com.sun.jna.Pointer;
//import com.sun.jna.ptr.IntByReference; //import com.sun.jna.ptr.IntByReference;
import jdk.internal.org.jline.terminal.Cursor; import jdk.internal.org.jline.terminal.Cursor;
import jdk.internal.org.jline.terminal.Size; 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.impl.AbstractWindowsTerminal;
import jdk.internal.org.jline.terminal.spi.TerminalProvider;
import jdk.internal.org.jline.utils.InfoCmp; import jdk.internal.org.jline.utils.InfoCmp;
import jdk.internal.org.jline.utils.OSUtils; 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 consoleIn = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_INPUT_HANDLE);
private static final Pointer consoleOut = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_OUTPUT_HANDLE); private static final Pointer consoleOut = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_OUTPUT_HANDLE);
private static final Pointer consoleErr = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_ERROR_HANDLE);
public static JnaWinSysTerminal createTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, int codepage, boolean nativeSignals, SignalHandler signalHandler, boolean paused, Function<InputStream, InputStream> inputStreamWrapper) throws IOException { public static JnaWinSysTerminal createTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, boolean nativeSignals, SignalHandler signalHandler, boolean paused, TerminalProvider.Stream consoleStream, Function<InputStream, InputStream> 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; Writer writer;
if (ansiPassThrough) { if (ansiPassThrough) {
if (type == null) { if (type == null) {
type = OSUtils.IS_CONEMU ? TYPE_WINDOWS_CONEMU : TYPE_WINDOWS; type = OSUtils.IS_CONEMU ? TYPE_WINDOWS_CONEMU : TYPE_WINDOWS;
} }
writer = new JnaWinConsoleWriter(consoleOut); writer = new JnaWinConsoleWriter(console);
} else { } else {
IntByReference mode = new IntByReference(); IntByReference mode = new IntByReference();
Kernel32.INSTANCE.GetConsoleMode(consoleOut, mode); Kernel32.INSTANCE.GetConsoleMode(console, mode);
try { 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) { if (type == null) {
type = TYPE_WINDOWS_VTP; type = TYPE_WINDOWS_VTP;
} }
writer = new JnaWinConsoleWriter(consoleOut); writer = new JnaWinConsoleWriter(console);
} catch (LastErrorException e) { } catch (LastErrorException e) {
if (OSUtils.IS_CONEMU) { if (OSUtils.IS_CONEMU) {
if (type == null) { if (type == null) {
type = TYPE_WINDOWS_CONEMU; type = TYPE_WINDOWS_CONEMU;
} }
writer = new JnaWinConsoleWriter(consoleOut); writer = new JnaWinConsoleWriter(console);
} else { } else {
if (type == null) { if (type == null) {
type = TYPE_WINDOWS; 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 // Start input pump thread
if (!paused) { if (!paused) {
terminal.resume(); terminal.resume();
@ -70,39 +81,26 @@ public class JnaWinSysTerminal extends AbstractWindowsTerminal {
return terminal; return terminal;
} }
public static boolean isWindowsConsole() { public static boolean isWindowsSystemStream(TerminalProvider.Stream stream) {
try { try {
IntByReference mode = new IntByReference(); IntByReference mode = new IntByReference();
Kernel32.INSTANCE.GetConsoleMode(consoleOut, mode); Pointer console;
Kernel32.INSTANCE.GetConsoleMode(consoleIn, mode); 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; return true;
} catch (LastErrorException e) { } catch (LastErrorException e) {
return false; return false;
} }
} }
public static boolean isConsoleOutput() { JnaWinSysTerminal(Writer writer, String name, String type, Charset encoding, boolean nativeSignals, SignalHandler signalHandler,
try { Function<InputStream, InputStream> inputStreamWrapper) throws IOException {
IntByReference mode = new IntByReference(); super(writer, name, type, encoding, nativeSignals, signalHandler, inputStreamWrapper);
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);
return true;
} catch (LastErrorException e) {
return false;
}
}
JnaWinSysTerminal(Writer writer, String name, String type, Charset encoding, int codepage, boolean nativeSignals, SignalHandler signalHandler, Function<InputStream, InputStream> inputStreamWrapper) throws IOException {
super(writer, name, type, encoding, codepage, nativeSignals, signalHandler, inputStreamWrapper);
strings.put(InfoCmp.Capability.key_mouse, "\\E[M"); strings.put(InfoCmp.Capability.key_mouse, "\\E[M");
} }

View File

@ -70,7 +70,7 @@ public final class WindowsAnsiWriter extends AnsiWriter {
BACKGROUND_WHITE, BACKGROUND_WHITE,
}; };
private static final int MAX_ESCAPE_SEQUENCE_LENGTH = 100; private final static int MAX_ESCAPE_SEQUENCE_LENGTH = 100;
private final Pointer console; private final Pointer console;
@ -93,7 +93,7 @@ public final class WindowsAnsiWriter extends AnsiWriter {
private void getConsoleInfo() throws IOException { private void getConsoleInfo() throws IOException {
out.flush(); out.flush();
Kernel32.INSTANCE.GetConsoleScreenBufferInfo(console, info); Kernel32.INSTANCE.GetConsoleScreenBufferInfo(console, info);
if( negative ) { if (negative) {
info.wAttributes = invertAttributeColors(info.wAttributes); info.wAttributes = invertAttributeColors(info.wAttributes);
} }
} }
@ -109,14 +109,14 @@ public final class WindowsAnsiWriter extends AnsiWriter {
if (underline) { if (underline) {
attributes |= BACKGROUND_INTENSITY; attributes |= BACKGROUND_INTENSITY;
} }
if( negative ) { if (negative) {
attributes = invertAttributeColors(attributes); attributes = invertAttributeColors(attributes);
} }
Kernel32.INSTANCE.SetConsoleTextAttribute(console, attributes); Kernel32.INSTANCE.SetConsoleTextAttribute(console, attributes);
} }
private short invertAttributeColors(short attributes) { private short invertAttributeColors(short attributes) {
// Swap the Foreground and Background bits. // Swap the the Foreground and Background bits.
int fg = 0x000F & attributes; int fg = 0x000F & attributes;
fg <<= 4; fg <<= 4;
int bg = 0X00F0 & attributes; int bg = 0X00F0 & attributes;
@ -183,26 +183,26 @@ public final class WindowsAnsiWriter extends AnsiWriter {
protected void processCursorUpLine(int count) throws IOException { protected void processCursorUpLine(int count) throws IOException {
getConsoleInfo(); getConsoleInfo();
info.dwCursorPosition.X = 0; info.dwCursorPosition.X = 0;
info.dwCursorPosition.Y -= (short)count; info.dwCursorPosition.Y -= (short) count;
applyCursorPosition(); applyCursorPosition();
} }
protected void processCursorDownLine(int count) throws IOException { protected void processCursorDownLine(int count) throws IOException {
getConsoleInfo(); getConsoleInfo();
info.dwCursorPosition.X = 0; info.dwCursorPosition.X = 0;
info.dwCursorPosition.Y += (short)count; info.dwCursorPosition.Y += (short) count;
applyCursorPosition(); applyCursorPosition();
} }
protected void processCursorLeft(int count) throws IOException { protected void processCursorLeft(int count) throws IOException {
getConsoleInfo(); getConsoleInfo();
info.dwCursorPosition.X -= (short)count; info.dwCursorPosition.X -= (short) count;
applyCursorPosition(); applyCursorPosition();
} }
protected void processCursorRight(int count) throws IOException { protected void processCursorRight(int count) throws IOException {
getConsoleInfo(); getConsoleInfo();
info.dwCursorPosition.X += (short)count; info.dwCursorPosition.X += (short) count;
applyCursorPosition(); applyCursorPosition();
} }
@ -210,7 +210,7 @@ public final class WindowsAnsiWriter extends AnsiWriter {
getConsoleInfo(); getConsoleInfo();
int nb = Math.max(0, info.dwCursorPosition.Y + count - info.dwSize.Y + 1); int nb = Math.max(0, info.dwCursorPosition.Y + count - info.dwSize.Y + 1);
if (nb != count) { if (nb != count) {
info.dwCursorPosition.Y += (short)count; info.dwCursorPosition.Y += (short) count;
applyCursorPosition(); applyCursorPosition();
} }
if (nb > 0) { if (nb > 0) {
@ -226,7 +226,7 @@ public final class WindowsAnsiWriter extends AnsiWriter {
protected void processCursorUp(int count) throws IOException { protected void processCursorUp(int count) throws IOException {
getConsoleInfo(); getConsoleInfo();
info.dwCursorPosition.Y -= (short)count; info.dwCursorPosition.Y -= (short) count;
applyCursorPosition(); applyCursorPosition();
} }

View File

@ -1,27 +0,0 @@
/*
* Copyright (c) 2018, 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.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
provides jdk.internal.org.jline.terminal.spi.JnaSupport with jdk.internal.org.jline.terminal.impl.jna.JnaSupportImpl;

View File

@ -56,7 +56,7 @@ public class AbstractWindowsTerminalTest {
return is.read(); 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 @Override
protected int getConsoleMode() { protected int getConsoleMode() {
return -1; return -1;

View File

@ -59,7 +59,7 @@ public class KeyConversionTest {
void checkKeyConversion(KeyEvent event, String expected) throws IOException { void checkKeyConversion(KeyEvent event, String expected) throws IOException {
StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();
new AbstractWindowsTerminal(new StringWriter(), "", "windows", Charset.forName("UTF-8"), new AbstractWindowsTerminal(new StringWriter(), "", "windows", Charset.forName("UTF-8"),
0, true, SignalHandler.SIG_DFL, in -> in) { true, SignalHandler.SIG_DFL, in -> in) {
@Override @Override
protected int getConsoleMode() { protected int getConsoleMode() {
return 0; return 0;

View File

@ -27,6 +27,7 @@
* @summary Control Char <UNDEF> check for pty * @summary Control Char <UNDEF> check for pty
* @modules jdk.internal.le/jdk.internal.org.jline.terminal * @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.impl
* jdk.internal.le/jdk.internal.org.jline.terminal.spi
* @requires (os.family == "linux") | (os.family == "aix") * @requires (os.family == "linux") | (os.family == "aix")
*/ */
@ -35,10 +36,11 @@ import jdk.internal.org.jline.terminal.Attributes;
import jdk.internal.org.jline.terminal.Attributes.ControlChar; import jdk.internal.org.jline.terminal.Attributes.ControlChar;
import jdk.internal.org.jline.terminal.Attributes.LocalFlag; import jdk.internal.org.jline.terminal.Attributes.LocalFlag;
import jdk.internal.org.jline.terminal.impl.ExecPty; import jdk.internal.org.jline.terminal.impl.ExecPty;
import jdk.internal.org.jline.terminal.spi.TerminalProvider;
public class ExecPtyGetFlagsToSetTest extends ExecPty { public class ExecPtyGetFlagsToSetTest extends ExecPty {
public ExecPtyGetFlagsToSetTest(String name, boolean system) { public ExecPtyGetFlagsToSetTest(String name, TerminalProvider.Stream stream) {
super(name, system); super(name, stream);
} }
@Override @Override
@ -48,7 +50,7 @@ public class ExecPtyGetFlagsToSetTest extends ExecPty {
public static void main(String[] args) { public static void main(String[] args) {
ExecPtyGetFlagsToSetTest testPty = ExecPtyGetFlagsToSetTest testPty =
new ExecPtyGetFlagsToSetTest("stty", true); new ExecPtyGetFlagsToSetTest("stty", TerminalProvider.Stream.Output);
Attributes attr = new Attributes(); Attributes attr = new Attributes();
Attributes current = new Attributes(); Attributes current = new Attributes();