8241598: Upgrade JLine to 3.14.0

Upgrading to JLine 3.14.0

Reviewed-by: psandoz, rfield
This commit is contained in:
Jan Lahoda 2020-04-01 13:12:49 +02:00
parent c8b1f966cb
commit f1ef83b02e
40 changed files with 1349 additions and 243 deletions

View File

@ -117,6 +117,33 @@ public class BindingReader {
return null;
}
public String readStringUntil(String sequence) {
StringBuilder sb = new StringBuilder();
if (!pushBackChar.isEmpty()) {
pushBackChar.forEach(sb::appendCodePoint);
}
try {
char[] buf = new char[64];
while (true) {
int idx = sb.indexOf(sequence, Math.max(0, sb.length() - buf.length - sequence.length()));
if (idx >= 0) {
String rem = sb.substring(idx + sequence.length());
runMacro(rem);
return sb.substring(0, idx);
}
int l = reader.readBuffered(buf);
if (l < 0) {
throw new ClosedException();
}
sb.append(buf, 0, l);
}
} catch (ClosedException e) {
throw new EndOfFileException(e);
} catch (IOException e) {
throw new IOError(e);
}
}
/**
* Read a codepoint from the terminal.
*
@ -144,6 +171,47 @@ public class BindingReader {
}
}
public int readCharacterBuffered() {
try {
if (pushBackChar.isEmpty()) {
char[] buf = new char[32];
int l = reader.readBuffered(buf);
if (l <= 0) {
return -1;
}
int s = 0;
for (int i = 0; i < l; ) {
int c = buf[i++];
if (Character.isHighSurrogate((char) c)) {
s = c;
if (i < l) {
c = buf[i++];
pushBackChar.addLast(Character.toCodePoint((char) s, (char) c));
} else {
break;
}
} else {
s = 0;
pushBackChar.addLast(c);
}
}
if (s != 0) {
int c = reader.read();
if (c >= 0) {
pushBackChar.addLast(Character.toCodePoint((char) s, (char) c));
} else {
return -1;
}
}
}
return pushBackChar.pop();
} catch (ClosedException e) {
throw new EndOfFileException(e);
} catch (IOException e) {
throw new IOError(e);
}
}
public int peekCharacter(long timeout) {
if (!pushBackChar.isEmpty()) {
return pushBackChar.peek();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2018, the original author or authors.
* 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.
@ -46,9 +46,8 @@ public class Candidate implements Comparable<Candidate> {
* @param complete the complete flag
*/
public Candidate(String value, String displ, String group, String descr, String suffix, String key, boolean complete) {
Objects.requireNonNull(value);
this.value = value;
this.displ = displ;
this.value = Objects.requireNonNull(value);
this.displ = Objects.requireNonNull(displ);
this.group = group;
this.descr = descr;
this.suffix = suffix;

View File

@ -0,0 +1,75 @@
/*
* 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 org.jline.reader;
import java.io.IOException;
import java.nio.file.Path;
public class ConfigurationPath {
private Path appConfig;
private Path userConfig;
/**
* Configuration class constructor.
* @param appConfig Application configuration directory
* @param userConfig User private configuration directory
*/
public ConfigurationPath(Path appConfig, Path userConfig) {
this.appConfig = appConfig;
this.userConfig = userConfig;
}
/**
* Search configuration file first from userConfig and then appConfig directory. Returns null if file is not found.
* @param name Configuration file name.
* @return Configuration file.
*
*/
public Path getConfig(String name) {
Path out = null;
if (userConfig != null && userConfig.resolve(name).toFile().exists()) {
out = userConfig.resolve(name);
} else if (appConfig != null && appConfig.resolve(name).toFile().exists()) {
out = appConfig.resolve(name);
}
return out;
}
/**
* Search configuration file from userConfig directory. Returns null if file is not found.
* @param name Configuration file name.
* @return Configuration file.
* @throws IOException When we do not have read access to the file or directory.
*
*/
public Path getUserConfig(String name) throws IOException {
return getUserConfig(name, false);
}
/**
* Search configuration file from userConfig directory. Returns null if file is not found.
* @param name Configuration file name
* @param create When true configuration file is created if not found.
* @return Configuration file.
* @throws IOException When we do not have read/write access to the file or directory.
*/
public Path getUserConfig(String name, boolean create) throws IOException {
Path out = null;
if (userConfig != null) {
if (!userConfig.resolve(name).toFile().exists() && create) {
userConfig.resolve(name).toFile().createNewFile();
}
if (userConfig.resolve(name).toFile().exists()) {
out = userConfig.resolve(name);
}
}
return out;
}
}

View File

@ -23,17 +23,33 @@ public class EOFError extends SyntaxError {
private static final long serialVersionUID = 1L;
private final String missing;
private final int openBrackets;
private final String nextClosingBracket;
public EOFError(int line, int column, String message) {
this(line, column, message, null);
}
public EOFError(int line, int column, String message, String missing) {
this(line, column, message, missing, 0, null);
}
public EOFError(int line, int column, String message, String missing, int openBrackets, String nextClosingBracket) {
super(line, column, message);
this.missing = missing;
this.openBrackets = openBrackets;
this.nextClosingBracket = nextClosingBracket;
}
public String getMissing() {
return missing;
}
public int getOpenBrackets(){
return openBrackets;
}
public String getNextClosingBracket() {
return nextClosingBracket;
}
}

View File

@ -0,0 +1,18 @@
/*
* 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.reader;
import java.io.IOException;
import java.util.List;
public interface Editor {
public void open(List<String> files) throws IOException;
public void run() throws IOException;
public void setRestricted(boolean restricted);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2016, the original author or authors.
* 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.
@ -8,9 +8,13 @@
*/
package jdk.internal.org.jline.reader;
import java.util.regex.Pattern;
import jdk.internal.org.jline.utils.AttributedString;
public interface Highlighter {
AttributedString highlight(LineReader reader, String buffer);
public void setErrorPattern(Pattern errorPattern);
public void setErrorIndex(int errorIndex);
}

View File

@ -45,6 +45,8 @@ public interface History extends Iterable<History.Entry>
/**
* Write history to the file. If incremental only the events that are new since the last incremental operation to
* the file are added.
* @param file History file
* @param incremental If true incremental write operation is performed.
* @throws IOException if a problem occurs
*/
void write(Path file, boolean incremental) throws IOException;
@ -52,12 +54,16 @@ public interface History extends Iterable<History.Entry>
/**
* Append history to the file. If incremental only the events that are new since the last incremental operation to
* the file are added.
* @param file History file
* @param incremental If true incremental append operation is performed.
* @throws IOException if a problem occurs
*/
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.
* @param file History file
* @param incremental If true incremental read operation is performed.
* @throws IOException if a problem occurs
*/
void read(Path file, boolean incremental) throws IOException;
@ -133,6 +139,11 @@ public interface History extends Iterable<History.Entry>
public Entry next() {
return it.previous();
}
@Override
public void remove() {
it.remove();
resetIndex();
}
};
}
@ -191,4 +202,9 @@ public interface History extends Iterable<History.Entry>
* all of the other iterator.
*/
void moveToEnd();
/**
* Reset index after remove
*/
void resetIndex();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2018, the original author or authors.
* 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.
@ -8,7 +8,9 @@
*/
package jdk.internal.org.jline.reader;
import java.io.File;
import java.io.InputStream;
import java.util.Collection;
import java.util.Map;
import java.util.function.IntConsumer;
@ -57,7 +59,7 @@ import jdk.internal.org.jline.utils.AttributedString;
* Defaults to an empty string.
* </dd>
* <dt>{@code %}<var>n</var>{@code P}<var>c</var></dt>
* <dd>Insert padding at this possion, repeating the following
* <dd>Insert padding at this position, repeating the following
* character <var>c</var> as needed to bring the total prompt
* column width as specified by the digits <var>n</var>.
* </dd>
@ -130,6 +132,7 @@ public interface LineReader {
String DOWN_LINE = "down-line";
String DOWN_LINE_OR_HISTORY = "down-line-or-history";
String DOWN_LINE_OR_SEARCH = "down-line-or-search";
String EDIT_AND_EXECUTE_COMMAND = "edit-and-execute-command";
String EMACS_BACKWARD_WORD = "emacs-backward-word";
String EMACS_EDITING_MODE = "emacs-editing-mode";
String EMACS_FORWARD_WORD = "emacs-forward-word";
@ -354,6 +357,19 @@ public interface LineReader {
*/
String HISTORY_FILE_SIZE = "history-file-size";
/**
* New line automatic indentation after opening/closing bracket.
*/
String INDENTATION = "indentation";
/**
* Max buffer size for advanced features.
* Once the length of the buffer reaches this threshold, no
* advanced features will be enabled. This includes the undo
* buffer, syntax highlighting, parsing, etc....
*/
String FEATURES_MAX_BUFFER_SIZE = "features-max-buffer-size";
Map<String, KeyMap<Binding>> defaultKeyMaps();
enum Option {
@ -398,6 +414,7 @@ public interface LineReader {
DELAY_LINE_WRAP,
AUTO_PARAM_SLASH(true),
AUTO_REMOVE_SLASH(true),
USE_FORWARD_SLASH(false),
/** When hitting the <code>&lt;tab&gt;</code> key at the beginning of the line, insert a tabulation
* instead of completing. This is mainly useful when {@link #BRACKETED_PASTE} is
* disabled, so that copy/paste of indented text does not trigger completion.
@ -415,6 +432,12 @@ public interface LineReader {
/** if history search is fully case insensitive */
CASE_INSENSITIVE_SEARCH,
/** Automatic insertion of closing bracket */
INSERT_BRACKET,
/** Show command options tab completion candidates for zero length word */
EMPTY_WORD_OPTIONS(true),
;
private final boolean def;
@ -439,6 +462,27 @@ public interface LineReader {
PASTE
}
enum SuggestionType {
/**
* As you type command line suggestions are disabled.
*/
NONE,
/**
* Prepare command line suggestions using command history.
* Requires an additional widgets implementation.
*/
HISTORY,
/**
* Prepare command line suggestions using command completer data.
*/
COMPLETER,
/**
* Prepare command line suggestions using command completer data and/or command positional argument descriptions.
* Requires an additional widgets implementation.
*/
TAIL_TIP
}
/**
* Read the next line and return the contents of the buffer.
*
@ -655,4 +699,17 @@ public interface LineReader {
int getRegionMark();
void addCommandsInBuffer(Collection<String> commands);
void editAndAddInBuffer(File file) throws Exception;
String getLastBinding();
String getTailTip();
void setTailTip(String tailTip);
void setAutosuggestion(SuggestionType type);
SuggestionType getAutosuggestion();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2016, the original author or authors.
* Copyright (c) 2002-2020, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -8,7 +8,12 @@
*/
package jdk.internal.org.jline.reader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public interface Parser {
static final String REGEX_VARIABLE = "[a-zA-Z_]{1,}[a-zA-Z0-9_-]*";
static final String REGEX_COMMAND = "[:]{0,1}[a-zA-Z]{1,}[a-zA-Z0-9_-]*";
ParsedLine parse(String line, int cursor, ParseContext context) throws SyntaxError;
@ -20,6 +25,43 @@ public interface Parser {
return ch == '\\';
}
default boolean validCommandName(String name) {
return name != null && name.matches(REGEX_COMMAND);
}
default boolean validVariableName(String name) {
return name != null && name.matches(REGEX_VARIABLE);
}
default String getCommand(final String line) {
String out = "";
Pattern patternCommand = Pattern.compile("^\\s*" + REGEX_VARIABLE + "=(" + REGEX_COMMAND + ")(\\s+.*|$)");
Matcher matcher = patternCommand.matcher(line);
if (matcher.find()) {
out = matcher.group(1);
} else {
out = line.trim().split("\\s+")[0];
int idx = out.indexOf("=");
if (idx > -1) {
out = out.substring(idx + 1);
}
if (!out.matches(REGEX_COMMAND)) {
out = "";
}
}
return out;
}
default String getVariable(final String line) {
String out = null;
Pattern patternCommand = Pattern.compile("^\\s*(" + REGEX_VARIABLE + ")\\s*=[^=~].*");
Matcher matcher = patternCommand.matcher(line);
if (matcher.find()) {
out = matcher.group(1);
}
return out;
}
enum ParseContext {
UNSPECIFIED,
@ -28,6 +70,12 @@ public interface Parser {
*/
ACCEPT_LINE,
/** Parsed words will have all characters present in input line
* including quotes and escape chars.
* May throw EOFError in which case we have incomplete input.
*/
SPLIT_LINE,
/** Parse to find completions (typically after a Tab).
* We should tolerate and ignore errors.
*/

View File

@ -0,0 +1,153 @@
/*
* 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 org.jline.reader;
import java.io.File;
import java.nio.file.Path;
import java.util.*;
/**
* Manage scriptEngine variables, statements and script execution.
*
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
*/
public interface ScriptEngine {
/**
*
* @return scriptEngine name
*/
String getEngineName();
/**
*
* @return script file name extensions
*/
Collection<String> getExtensions();
/**
* Tests if console variable exists
* @param name
* @return true if variable exists
*/
boolean hasVariable(String name);
/**
* Creates variable
* @param name of the variable
* @param value of the variable
*/
void put(String name, Object value);
/**
* Gets variable value
* @param name of the variable
* @return value of the variable
*/
Object get(String name);
/**
* Gets all variables with values
* @return map of the variables
*/
default Map<String,Object> find() {
return find(null);
}
/**
* Gets all the variables that match the name. Name can contain * wild cards.
* @param name of the variable
* @return map of the variables
*/
Map<String,Object> find(String name);
/**
* Deletes variables. Variable name cab contain * wild cards.
* @param vars
*/
void del(String... vars);
/**
* Converts object to JSON string.
* @param object object to convert to JSON
* @return formatted JSON string
*/
String toJson(Object object);
/**
* Converts object to string.
* @param object object to convert to string
* @return object string value
*/
String toString(Object object);
/**
* Substitute variable reference with its value.
* @param variable
* @return Substituted variable
* @throws Exception
*/
default Object expandParameter(String variable) {
return expandParameter(variable, "");
}
/**
* Substitute variable reference with its value.
* @param variable
* @param format serialization format
* @return Substituted variable
* @throws Exception
*/
Object expandParameter(String variable, String format);
/**
* Persists object value to file.
* @param file
* @param object
*/
default void persist(Path file, Object object) {
persist(file, object, "JSON");
}
/**
* Persists object value to file.
* @param file
* @param object
* @param format
*/
void persist(Path file, Object object, String format);
/**
* Executes scriptEngine statement
* @param statement
* @return result
* @throws Exception
*/
Object execute(String statement) throws Exception;
/**
* Executes scriptEngine script
* @param script
* @return result
* @throws Exception
*/
default Object execute(File script) throws Exception {
return execute(script, null);
}
/**
* Executes scriptEngine script
* @param script
* @param args
* @return
* @throws Exception
*/
Object execute(File script, Object[] args) throws Exception;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2016, the original author or authors.
* 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.
@ -8,6 +8,8 @@
*/
package jdk.internal.org.jline.reader.impl;
import java.util.regex.Pattern;
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.reader.LineReader.RegionType;
import jdk.internal.org.jline.reader.Highlighter;
@ -17,6 +19,18 @@ import jdk.internal.org.jline.utils.AttributedStyle;
import jdk.internal.org.jline.utils.WCWidth;
public class DefaultHighlighter implements Highlighter {
private Pattern errorPattern;
private int errorIndex = -1;
@Override
public void setErrorPattern(Pattern errorPattern) {
this.errorPattern = errorPattern;
}
@Override
public void setErrorIndex(int errorIndex) {
this.errorIndex = errorIndex;
}
@Override
public AttributedString highlight(LineReader reader, String buffer) {
@ -57,6 +71,10 @@ public class DefaultHighlighter implements Highlighter {
if (i == negativeStart) {
sb.style(AttributedStyle::inverse);
}
if (i == errorIndex) {
sb.style(AttributedStyle::inverse);
}
char c = buffer.charAt(i);
if (c == '\t' || c == '\n') {
sb.append(c);
@ -77,6 +95,12 @@ public class DefaultHighlighter implements Highlighter {
if (i == negativeEnd) {
sb.style(AttributedStyle::inverseOff);
}
if (i == errorIndex) {
sb.style(AttributedStyle::inverseOff);
}
}
if (errorPattern != null) {
sb.styleMatches(errorPattern, AttributedStyle.INVERSE);
}
return sb.toAttributedString();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2018, the original author or authors.
* Copyright (c) 2002-2020, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -10,6 +10,8 @@ package jdk.internal.org.jline.reader.impl;
import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jdk.internal.org.jline.reader.CompletingParsedLine;
import jdk.internal.org.jline.reader.EOFError;
@ -37,6 +39,10 @@ public class DefaultParser implements Parser {
private char[] closingBrackets = null;
private String regexVariable = "[a-zA-Z_]{1,}[a-zA-Z0-9_-]*((.|\\['|\\[\\\"|\\[)[a-zA-Z0-9_-]*(|'\\]|\\\"\\]|\\])){0,1}";
private String regexCommand = "[:]{0,1}[a-zA-Z]{1,}[a-zA-Z0-9_-]*";
private int commandGroup = 4;
//
// Chainable setters
//
@ -56,7 +62,7 @@ public class DefaultParser implements Parser {
return this;
}
public DefaultParser eofOnUnclosedBracket(Bracket... brackets){
public DefaultParser eofOnUnclosedBracket(Bracket... brackets) {
setEofOnUnclosedBracket(brackets);
return this;
}
@ -66,6 +72,21 @@ public class DefaultParser implements Parser {
return this;
}
public DefaultParser regexVariable(String regexVariable) {
this.regexVariable = regexVariable;
return this;
}
public DefaultParser regexCommand(String regexCommand) {
this.regexCommand = regexCommand;
return this;
}
public DefaultParser commandGroup(int commandGroup) {
this.commandGroup = commandGroup;
return this;
}
//
// Java bean getters and setters
//
@ -102,7 +123,7 @@ public class DefaultParser implements Parser {
return eofOnEscapedNewLine;
}
public void setEofOnUnclosedBracket(Bracket... brackets){
public void setEofOnUnclosedBracket(Bracket... brackets) {
if (brackets == null) {
openingBrackets = null;
closingBrackets = null;
@ -135,6 +156,60 @@ public class DefaultParser implements Parser {
}
}
public void setRegexVariable(String regexVariable) {
this.regexVariable = regexVariable;
}
public void setRegexCommand(String regexCommand) {
this.regexCommand = regexCommand;
}
public void setCommandGroup(int commandGroup) {
this.commandGroup = commandGroup;
}
@Override
public boolean validCommandName(String name) {
return name != null && name.matches(regexCommand);
}
@Override
public boolean validVariableName(String name) {
return name != null && name.matches(regexVariable);
}
@Override
public String getCommand(final String line) {
String out = "";
Pattern patternCommand = Pattern.compile("^\\s*" + regexVariable + "=(" + regexCommand + ")(\\s+.*|$)");
Matcher matcher = patternCommand.matcher(line);
if (matcher.find()) {
out = matcher.group(commandGroup);
} else {
out = line.trim().split("\\s+")[0];
int idx = out.indexOf("=");
if (idx > -1) {
out = out.substring(idx + 1);
}
if (!out.matches(regexCommand)) {
out = "";
}
}
return out;
}
@Override
public String getVariable(final String line) {
String out = null;
Pattern patternCommand = Pattern.compile("^\\s*(" + regexVariable + ")\\s*=[^=~].*");
Matcher matcher = patternCommand.matcher(line);
if (matcher.find()) {
out = matcher.group(1);
}
return out;
}
public ParsedLine parse(final String line, final int cursor, ParseContext context) {
List<String> words = new LinkedList<>();
StringBuilder current = new StringBuilder();
@ -144,7 +219,7 @@ public class DefaultParser implements Parser {
int rawWordCursor = -1;
int rawWordLength = -1;
int rawWordStart = 0;
BracketChecker bracketChecker = new BracketChecker();
BracketChecker bracketChecker = new BracketChecker(cursor);
boolean quotedWord = false;
for (int i = 0; (line != null) && (i < line.length()); i++) {
@ -163,12 +238,15 @@ public class DefaultParser implements Parser {
quoteStart = i;
if (current.length()==0) {
quotedWord = true;
if (context == ParseContext.SPLIT_LINE) {
current.append(line.charAt(i));
}
} else {
current.append(line.charAt(i));
}
} else if (quoteStart >= 0 && line.charAt(quoteStart) == line.charAt(i) && !isEscaped(line, i)) {
// End quote block
if (!quotedWord) {
if (!quotedWord || context == ParseContext.SPLIT_LINE) {
current.append(line.charAt(i));
} else if (rawWordCursor >= 0 && rawWordLength < 0) {
rawWordLength = i - rawWordStart + 1;
@ -191,6 +269,8 @@ public class DefaultParser implements Parser {
if (quoteStart < 0) {
bracketChecker.check(line, i);
}
} else if (context == ParseContext.SPLIT_LINE) {
current.append(line.charAt(i));
}
}
}
@ -217,11 +297,18 @@ public class DefaultParser implements Parser {
throw new EOFError(-1, -1, "Missing closing quote", line.charAt(quoteStart) == '\''
? "quote" : "dquote");
}
if (bracketChecker.isOpeningBracketMissing()) {
throw new EOFError(-1, -1, "Missing opening bracket", "missing: " + bracketChecker.getMissingOpeningBracket());
}
if (bracketChecker.isClosingBracketMissing()) {
throw new EOFError(-1, -1, "Missing closing brackets", "add: " + bracketChecker.getMissingClosingBrackets());
if (bracketChecker.isClosingBracketMissing() || bracketChecker.isOpeningBracketMissing()) {
String message = null;
String missing = null;
if (bracketChecker.isClosingBracketMissing()) {
message = "Missing closing brackets";
missing = "add: " + bracketChecker.getMissingClosingBrackets();
} else {
message = "Missing opening bracket";
missing = "missing: " + bracketChecker.getMissingOpeningBracket();
}
throw new EOFError(-1, -1, message, missing,
bracketChecker.getOpenBrackets(), bracketChecker.getNextClosingBracket());
}
}
@ -347,10 +434,15 @@ public class DefaultParser implements Parser {
private class BracketChecker {
private int missingOpeningBracket = -1;
private List<Integer> nested = new ArrayList<>();
private int openBrackets = 0;
private int cursor;
private String nextClosingBracket;
public BracketChecker(){}
public BracketChecker(int cursor) {
this.cursor = cursor;
}
public void check(final CharSequence buffer, final int pos){
public void check(final CharSequence buffer, final int pos) {
if (openingBrackets == null || pos < 0) {
return;
}
@ -367,24 +459,30 @@ public class DefaultParser implements Parser {
}
}
}
if (cursor > pos) {
openBrackets = nested.size();
if (nested.size() > 0) {
nextClosingBracket = String.valueOf(closingBrackets[nested.get(nested.size() - 1)]);
}
}
}
public boolean isOpeningBracketMissing(){
public boolean isOpeningBracketMissing() {
return missingOpeningBracket != -1;
}
public String getMissingOpeningBracket(){
public String getMissingOpeningBracket() {
if (!isOpeningBracketMissing()) {
return null;
}
return Character.toString(openingBrackets[missingOpeningBracket]);
}
public boolean isClosingBracketMissing(){
public boolean isClosingBracketMissing() {
return !nested.isEmpty();
}
public String getMissingClosingBrackets(){
public String getMissingClosingBrackets() {
if (!isClosingBracketMissing()) {
return null;
}
@ -395,7 +493,15 @@ public class DefaultParser implements Parser {
return out.toString();
}
private int bracketId(final char[] brackets, final CharSequence buffer, final int pos){
public int getOpenBrackets() {
return openBrackets;
}
public String getNextClosingBracket() {
return nested.size() == 2 ? nextClosingBracket : null;
}
private int bracketId(final char[] brackets, final CharSequence buffer, final int pos) {
for (int i=0; i < brackets.length; i++) {
if (buffer.charAt(pos) == brackets[i]) {
return i;

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
* BSD license in the documentation provided with this software.
@ -8,16 +8,20 @@
*/
package jdk.internal.org.jline.reader.impl;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.Flushable;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.lang.reflect.Constructor;
import java.time.Instant;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.*;
import java.util.regex.Matcher;
@ -86,6 +90,8 @@ public class LineReaderImpl implements LineReader, Flushable
public static final String DEFAULT_COMPLETION_STYLE_DESCRIPTION = "90"; // dark gray
public static final String DEFAULT_COMPLETION_STYLE_GROUP = "35;1"; // magenta
public static final String DEFAULT_COMPLETION_STYLE_SELECTION = "7"; // inverted
public static final int DEFAULT_INDENTATION = 0;
public static final int DEFAULT_FEATURES_MAX_BUFFER_SIZE = 1000;
private static final int MIN_ROWS = 3;
@ -109,6 +115,10 @@ public class LineReaderImpl implements LineReader, Flushable
* readLine should exit and return the buffer content
*/
DONE,
/**
* readLine should exit and return empty String
*/
IGNORE,
/**
* readLine should exit and throw an EOFException
*/
@ -160,6 +170,8 @@ public class LineReaderImpl implements LineReader, Flushable
protected final Map<Option, Boolean> options = new HashMap<>();
protected final Buffer buf = new BufferImpl();
protected String tailTip = "";
protected SuggestionType autosuggestion = SuggestionType.NONE;
protected final Size size = new Size();
@ -175,6 +187,7 @@ public class LineReaderImpl implements LineReader, Flushable
protected boolean searchFailing;
protected boolean searchBackward;
protected int searchIndex = -1;
protected boolean doAutosuggestion;
// Reading buffers
@ -246,13 +259,16 @@ public class LineReaderImpl implements LineReader, Flushable
protected String keyMap;
protected int smallTerminalOffset = 0;
/*
* accept-and-infer-next-history, accept-and-hold & accept-line-and-down-history
*/
protected boolean nextCommandFromHistory = false;
protected int nextHistoryId = -1;
/*
* execute commands from commandsBuffer
*/
protected List<String> commandsBuffer = new ArrayList<>();
public LineReaderImpl(Terminal terminal) throws IOException {
this(terminal, null, null);
@ -313,6 +329,26 @@ public class LineReaderImpl implements LineReader, Flushable
return buf;
}
@Override
public void setAutosuggestion(SuggestionType type) {
this.autosuggestion = type;
}
@Override
public SuggestionType getAutosuggestion() {
return autosuggestion;
}
@Override
public String getTailTip() {
return tailTip;
}
@Override
public void setTailTip(String tailTip) {
this.tailTip = tailTip;
}
@Override
public void runMacro(String macro) {
bindingReader.runMacro(macro);
@ -471,6 +507,32 @@ public class LineReaderImpl implements LineReader, Flushable
// prompt may be null
// maskingCallback may be null
// buffer may be null
if (!commandsBuffer.isEmpty()) {
String cmd = commandsBuffer.remove(0);
boolean done = false;
do {
try {
parser.parse(cmd, cmd.length() + 1, ParseContext.ACCEPT_LINE);
done = true;
} catch (EOFError e) {
if (commandsBuffer.isEmpty()) {
throw new IllegalArgumentException("Incompleted command: \n" + cmd);
}
cmd += "\n";
cmd += commandsBuffer.remove(0);
} catch (SyntaxError e) {
done = true;
} catch (Exception e) {
commandsBuffer.clear();
throw new IllegalArgumentException(e.getMessage());
}
} while (!done);
AttributedStringBuilder sb = new AttributedStringBuilder();
sb.styled(AttributedStyle::bold, cmd);
sb.toAttributedString().println(terminal);
terminal.flush();
return finish(cmd);
}
if (!startedReading.compareAndSet(false, true)) {
throw new IllegalStateException();
@ -598,18 +660,21 @@ public class LineReaderImpl implements LineReader, Flushable
try {
lock.lock();
// Get executable widget
Buffer copy = buf.copy();
Buffer copy = buf.length() <= getInt(FEATURES_MAX_BUFFER_SIZE, DEFAULT_FEATURES_MAX_BUFFER_SIZE) ? buf.copy() : null;
Widget w = getWidget(o);
if (!w.apply()) {
beep();
}
if (!isUndo && !copy.toString().equals(buf.toString())) {
if (!isUndo && copy != null && buf.length() <= getInt(FEATURES_MAX_BUFFER_SIZE, DEFAULT_FEATURES_MAX_BUFFER_SIZE)
&& !copy.toString().equals(buf.toString())) {
undo.newState(buf.copy());
}
switch (state) {
case DONE:
return finishBuffer();
case IGNORE:
return "";
case EOF:
throw new EndOfFileException();
case INTERRUPT:
@ -665,12 +730,12 @@ public class LineReaderImpl implements LineReader, Flushable
}
}
private boolean isTerminalDumb(){
private boolean isTerminalDumb() {
return Terminal.TYPE_DUMB.equals(terminal.getType())
|| Terminal.TYPE_DUMB_COLOR.equals(terminal.getType());
}
private void doDisplay(){
private void doDisplay() {
// Cache terminal size for the duration of the call to readLine()
// It will eventually be updated with WINCH signals
size.copy(terminal.getBufferSize());
@ -849,6 +914,19 @@ public class LineReaderImpl implements LineReader, Flushable
}
}
protected String doReadStringUntil(String sequence) {
if (lock.isHeldByCurrentThread()) {
try {
lock.unlock();
return bindingReader.readStringUntil(sequence);
} finally {
lock.lock();
}
} else {
return bindingReader.readStringUntil(sequence);
}
}
/**
* Read from the input stream and decode an operation from the key map.
*
@ -889,10 +967,12 @@ public class LineReaderImpl implements LineReader, Flushable
return parsedLine;
}
@Override
public String getLastBinding() {
return bindingReader.getLastBinding();
}
@Override
public String getSearchTerm() {
return searchTerm != null ? searchTerm.toString() : null;
}
@ -983,7 +1063,26 @@ public class LineReaderImpl implements LineReader, Flushable
options.put(option, Boolean.FALSE);
}
@Override
public void addCommandsInBuffer(Collection<String> commands) {
commandsBuffer.addAll(commands);
}
@Override
public void editAndAddInBuffer(File file) throws Exception {
Constructor<?> ctor = Class.forName("org.jline.builtins.Nano").getConstructor(Terminal.class, File.class);
Editor editor = (Editor) ctor.newInstance(terminal, new File(file.getParent()));
editor.setRestricted(true);
editor.open(Arrays.asList(file.getName()));
editor.run();
BufferedReader br = new BufferedReader(new FileReader(file));
String line;
commandsBuffer.clear();
while ((line = br.readLine()) != null) {
commandsBuffer.add(line);
}
br.close();
}
//
// Widget implementation
@ -995,7 +1094,10 @@ public class LineReaderImpl implements LineReader, Flushable
* @return the former contents of the buffer.
*/
protected String finishBuffer() {
String str = buf.toString();
return finish(buf.toString());
}
protected String finish(String str) {
String historyLine = str;
if (!isSet(Option.DISABLE_EVENT_EXPANSION)) {
@ -1029,6 +1131,7 @@ public class LineReaderImpl implements LineReader, Flushable
}
protected void handleSignal(Signal signal) {
doAutosuggestion = false;
if (signal == Signal.WINCH) {
Status status = Status.getStatus(terminal, false);
if (status != null) {
@ -1036,7 +1139,8 @@ public class LineReaderImpl implements LineReader, Flushable
}
size.copy(terminal.getBufferSize());
display.resize(size.getRows(), size.getColumns());
redrawLine();
// restores prompt but also prevents scrolling in consoleZ, see #492
// redrawLine();
redisplay();
}
else if (signal == Signal.CONT) {
@ -2072,6 +2176,7 @@ public class LineReaderImpl implements LineReader, Flushable
long blink = getLong(BLINK_MATCHING_PAREN, DEFAULT_BLINK_MATCHING_PAREN);
if (blink <= 0) {
removeIndentation();
return true;
}
@ -2082,11 +2187,32 @@ public class LineReaderImpl implements LineReader, Flushable
redisplay();
peekCharacter(blink);
int blinkPosition = buf.cursor();
buf.cursor(closePosition);
if (blinkPosition != closePosition - 1) {
removeIndentation();
}
return true;
}
private void removeIndentation() {
int indent = getInt(INDENTATION, DEFAULT_INDENTATION);
if (indent > 0) {
buf.move(-1);
for (int i = 0; i < indent; i++) {
buf.move(-1);
if (buf.currChar() == ' ') {
buf.delete();
} else {
buf.move(1);
break;
}
}
buf.move(1);
}
}
protected boolean viMatchBracket() {
return doViMatchBracket();
}
@ -2424,6 +2550,7 @@ public class LineReaderImpl implements LineReader, Flushable
buf.cursor(buf.length());
post = null;
if (size.getColumns() > 0 || size.getRows() > 0) {
doAutosuggestion = false;
redisplay(false);
if (nl) {
println();
@ -2835,6 +2962,7 @@ public class LineReaderImpl implements LineReader, Flushable
protected boolean acceptLine() {
parsedLine = null;
int curPos = 0;
if (!isSet(Option.DISABLE_EVENT_EXPANSION)) {
try {
String str = buf.toString();
@ -2851,9 +2979,19 @@ public class LineReaderImpl implements LineReader, Flushable
}
}
try {
curPos = buf.cursor();
parsedLine = parser.parse(buf.toString(), buf.cursor(), ParseContext.ACCEPT_LINE);
} catch (EOFError e) {
buf.write("\n");
StringBuilder sb = new StringBuilder("\n");
indention(e.getOpenBrackets(), sb);
int curMove = sb.length();
if (isSet(Option.INSERT_BRACKET) && e.getOpenBrackets() > 1 && e.getNextClosingBracket() != null) {
sb.append('\n');
indention(e.getOpenBrackets() - 1, sb);
sb.append(e.getNextClosingBracket());
}
buf.write(sb.toString());
buf.cursor(curPos + curMove);
return true;
} catch (SyntaxError e) {
// do nothing
@ -2863,6 +3001,13 @@ public class LineReaderImpl implements LineReader, Flushable
return true;
}
void indention(int nb, StringBuilder sb) {
int indent = getInt(INDENTATION, DEFAULT_INDENTATION)*nb;
for (int i = 0; i < indent; i++) {
sb.append(' ');
}
}
protected boolean selfInsert() {
for (int count = this.count; count > 0; count--) {
putString(getLastBinding());
@ -3464,6 +3609,27 @@ public class LineReaderImpl implements LineReader, Flushable
return true;
}
protected boolean editAndExecute() {
boolean out = true;
File file = null;
try {
file = File.createTempFile("jline-execute-", null);
FileWriter writer = new FileWriter(file);
writer.write(buf.toString());
writer.close();
editAndAddInBuffer(file);
} catch (Exception e) {
e.printStackTrace(terminal.writer());
out = false;
} finally {
state = State.IGNORE;
if (file != null && file.exists()) {
file.delete();
}
}
return out;
}
protected Map<String, Widget> builtinWidgets() {
Map<String, Widget> widgets = new HashMap<>();
addBuiltinWidget(widgets, ACCEPT_AND_INFER_NEXT_HISTORY, this::acceptAndInferNextHistory);
@ -3499,6 +3665,7 @@ public class LineReaderImpl implements LineReader, Flushable
addBuiltinWidget(widgets, DOWN_LINE_OR_HISTORY, this::downLineOrHistory);
addBuiltinWidget(widgets, DOWN_LINE_OR_SEARCH, this::downLineOrSearch);
addBuiltinWidget(widgets, DOWN_HISTORY, this::downHistory);
addBuiltinWidget(widgets, EDIT_AND_EXECUTE_COMMAND, this::editAndExecute);
addBuiltinWidget(widgets, EMACS_EDITING_MODE, this::emacsEditingMode);
addBuiltinWidget(widgets, EMACS_BACKWARD_WORD, this::emacsBackwardWord);
addBuiltinWidget(widgets, EMACS_FORWARD_WORD, this::emacsForwardWord);
@ -3616,7 +3783,7 @@ public class LineReaderImpl implements LineReader, Flushable
}
private void addBuiltinWidget(Map<String, Widget> widgets, String name, Widget widget) {
widgets.put(name, namedWidget(name, widget));
widgets.put(name, namedWidget("." + name, widget));
}
private Widget namedWidget(String name, Widget widget) {
@ -3791,6 +3958,38 @@ public class LineReaderImpl implements LineReader, Flushable
sb.append(lines.get(lines.size() - 1));
}
private String matchPreviousCommand(String buffer) {
if (buffer.length() == 0) {
return "";
}
History history = getHistory();
StringBuilder sb = new StringBuilder();
char prev = '0';
for (char c: buffer.toCharArray()) {
if ((c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '^') && prev != '\\' ) {
sb.append('\\');
}
sb.append(c);
prev = c;
}
Pattern pattern = Pattern.compile(sb.toString() + ".*", Pattern.DOTALL);
Iterator<History.Entry> iter = history.reverseIterator(history.last());
String suggestion = "";
int tot = 0;
while (iter.hasNext()) {
History.Entry entry = iter.next();
Matcher matcher = pattern.matcher(entry.line());
if (matcher.matches()) {
suggestion = entry.line().substring(buffer.length());
break;
} else if (tot > 200) {
break;
}
tot++;
}
return suggestion;
}
/**
* Compute the full string to be displayed with the left, right and secondary prompts
* @param secondaryPrompts a list to store the secondary prompts
@ -3803,10 +4002,51 @@ public class LineReaderImpl implements LineReader, Flushable
AttributedStringBuilder full = new AttributedStringBuilder().tabs(TAB_WIDTH);
full.append(prompt);
full.append(tNewBuf);
if (doAutosuggestion) {
String lastBinding = getLastBinding() != null ? getLastBinding() : "";
if (autosuggestion == SuggestionType.HISTORY) {
AttributedStringBuilder sb = new AttributedStringBuilder();
tailTip = matchPreviousCommand(buf.toString());
sb.styled(AttributedStyle::faint, tailTip);
full.append(sb.toAttributedString());
} else if (autosuggestion == SuggestionType.COMPLETER) {
if (buf.length() > 0 && buf.length() == buf.cursor()
&& (!lastBinding.equals("\t") || buf.prevChar() == ' ' || buf.prevChar() == '=')) {
clearChoices();
listChoices(true);
} else if (!lastBinding.equals("\t")) {
clearChoices();
}
} else if (autosuggestion == SuggestionType.TAIL_TIP) {
if (buf.length() == buf.cursor()) {
if (!lastBinding.equals("\t") || buf.prevChar() == ' ') {
clearChoices();
}
AttributedStringBuilder sb = new AttributedStringBuilder();
if (buf.prevChar() != ' ') {
if (!tailTip.startsWith("[")) {
int idx = tailTip.indexOf(' ');
int idb = buf.toString().lastIndexOf(' ');
int idd = buf.toString().lastIndexOf('-');
if (idx > 0 && ((idb == -1 && idb == idd) || (idb >= 0 && idb > idd))) {
tailTip = tailTip.substring(idx);
} else if (idb >= 0 && idb < idd) {
sb.append(" ");
}
} else {
sb.append(" ");
}
}
sb.styled(AttributedStyle::faint, tailTip);
full.append(sb.toAttributedString());
}
}
}
if (post != null) {
full.append("\n");
full.append(post.get());
}
doAutosuggestion = true;
return full.toAttributedString();
}
@ -3814,7 +4054,8 @@ public class LineReaderImpl implements LineReader, Flushable
if (maskingCallback != null) {
buffer = maskingCallback.display(buffer);
}
if (highlighter != null && !isSet(Option.DISABLE_HIGHLIGHTER)) {
if (highlighter != null && !isSet(Option.DISABLE_HIGHLIGHTER)
&& buffer.length() < getInt(FEATURES_MAX_BUFFER_SIZE, DEFAULT_FEATURES_MAX_BUFFER_SIZE)) {
return highlighter.highlight(this, buffer);
}
return new AttributedString(buffer);
@ -3936,7 +4177,8 @@ public class LineReaderImpl implements LineReader, Flushable
List<AttributedString> lines = strAtt.columnSplitLength(Integer.MAX_VALUE);
AttributedStringBuilder sb = new AttributedStringBuilder();
String secondaryPromptPattern = getString(SECONDARY_PROMPT_PATTERN, DEFAULT_SECONDARY_PROMPT_PATTERN);
boolean needsMessage = secondaryPromptPattern.contains("%M");
boolean needsMessage = secondaryPromptPattern.contains("%M")
&& strAtt.length() < getInt(FEATURES_MAX_BUFFER_SIZE, DEFAULT_FEATURES_MAX_BUFFER_SIZE);
AttributedStringBuilder buf = new AttributedStringBuilder();
int width = 0;
List<String> missings = new ArrayList<>();
@ -4102,7 +4344,11 @@ public class LineReaderImpl implements LineReader, Flushable
}
protected boolean listChoices() {
return doComplete(CompletionType.List, isSet(Option.MENU_COMPLETE), false);
return listChoices(false);
}
private boolean listChoices(boolean forSuggestion) {
return doComplete(CompletionType.List, isSet(Option.MENU_COMPLETE), false, forSuggestion);
}
protected boolean deleteCharOrList() {
@ -4114,6 +4360,10 @@ public class LineReaderImpl implements LineReader, Flushable
}
protected boolean doComplete(CompletionType lst, boolean useMenu, boolean prefix) {
return doComplete(lst, useMenu, prefix, false);
}
protected boolean doComplete(CompletionType lst, boolean useMenu, boolean prefix, boolean forSuggestion) {
// If completion is disabled, just bail out
if (getBoolean(DISABLE_COMPLETION, false)) {
return true;
@ -4212,11 +4462,17 @@ public class LineReaderImpl implements LineReader, Flushable
} else {
String wd = line.word();
String wdi = caseInsensitive ? wd.toLowerCase() : wd;
matchers = Arrays.asList(
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).startsWith(wdi)),
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).contains(wdi)),
typoMatcher(wdi, errors, caseInsensitive)
);
if (isSet(Option.EMPTY_WORD_OPTIONS) || wd.length() > 0) {
matchers = Arrays.asList(
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).startsWith(wdi)),
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).contains(wdi)),
typoMatcher(wdi, errors, caseInsensitive)
);
} else {
matchers = Arrays.asList(
simpleMatcher(s -> !s.startsWith("-"))
);
}
exact = s -> caseInsensitive ? s.equalsIgnoreCase(wd) : s.equals(wd);
}
// Find matching candidates
@ -4240,7 +4496,7 @@ public class LineReaderImpl implements LineReader, Flushable
List<Candidate> possible = matching.entrySet().stream()
.flatMap(e -> e.getValue().stream())
.collect(Collectors.toList());
doList(possible, line.word(), false, line::escape);
doList(possible, line.word(), false, line::escape, forSuggestion);
return !possible.isEmpty();
}
@ -4323,6 +4579,7 @@ public class LineReaderImpl implements LineReader, Flushable
if (hasUnambiguous) {
buf.backspace(line.rawWordLength());
buf.write(line.escape(commonPrefix, false));
callWidget(REDISPLAY);
current = commonPrefix;
if ((!isSet(Option.AUTO_LIST) && isSet(Option.AUTO_MENU))
|| (isSet(Option.AUTO_LIST) && isSet(Option.LIST_AMBIGUOUS))) {
@ -4387,7 +4644,6 @@ public class LineReaderImpl implements LineReader, Flushable
ToIntFunction<String> wordDistance = w -> distance(wdi, caseInsensitive ? w.toLowerCase() : w);
return Comparator
.comparing(Candidate::value, Comparator.comparingInt(wordDistance))
.thenComparing(Candidate::value, Comparator.comparingInt(String::length))
.thenComparing(Comparator.naturalOrder());
}
@ -4596,8 +4852,10 @@ public class LineReaderImpl implements LineReader, Flushable
PostResult pr = computePost(possible, completion(), null, completed);
AttributedString text = insertSecondaryPrompts(AttributedStringBuilder.append(prompt, buf.toString()), new ArrayList<>());
int promptLines = text.columnSplitLength(size.getColumns(), false, display.delayLineWrap()).size();
if (pr.lines > size.getRows() - promptLines) {
int displayed = size.getRows() - promptLines - 1;
Status status = Status.getStatus(terminal, false);
int displaySize = size.getRows() - (status != null ? status.size() : 0) - promptLines;
if (pr.lines > displaySize) {
int displayed = displaySize - 1;
if (pr.selectedLine >= 0) {
if (pr.selectedLine < topLine) {
topLine = pr.selectedLine;
@ -4648,7 +4906,7 @@ public class LineReaderImpl implements LineReader, Flushable
// Build menu support
MenuSupport menuSupport = new MenuSupport(original, completed, escaper);
post = menuSupport;
redisplay();
callWidget(REDISPLAY);
// Loop
KeyMap<Binding> keyMap = keyMaps.get(MENU);
@ -4704,12 +4962,24 @@ public class LineReaderImpl implements LineReader, Flushable
return true;
}
}
redisplay();
doAutosuggestion = false;
callWidget(REDISPLAY);
}
return false;
}
protected boolean doList(List<Candidate> possible, String completed, boolean runLoop, BiFunction<CharSequence, Boolean, CharSequence> escaper) {
protected boolean clearChoices() {
return doList(new ArrayList<Candidate>(), "", false, null, false);
}
protected boolean doList(List<Candidate> possible
, String completed, boolean runLoop, BiFunction<CharSequence, Boolean, CharSequence> escaper) {
return doList(possible, completed, runLoop, escaper, false);
}
protected boolean doList(List<Candidate> possible
, String completed
, boolean runLoop, BiFunction<CharSequence, Boolean, CharSequence> escaper, boolean forSuggestion) {
// If we list only and if there's a big
// number of items, we should ask the user
// for confirmation, display the list
@ -4722,13 +4992,17 @@ public class LineReaderImpl implements LineReader, Flushable
int listMax = getInt(LIST_MAX, DEFAULT_LIST_MAX);
if (listMax > 0 && possible.size() >= listMax
|| lines >= size.getRows() - promptLines) {
// prompt
post = () -> new AttributedString(getAppName() + ": do you wish to see all " + possible.size()
+ " possibilities (" + lines + " lines)?");
redisplay(true);
int c = readCharacter();
if (c != 'y' && c != 'Y' && c != '\t') {
post = null;
if (!forSuggestion) {
// prompt
post = () -> new AttributedString(getAppName() + ": do you wish to see all " + possible.size()
+ " possibilities (" + lines + " lines)?");
redisplay(true);
int c = readCharacter();
if (c != 'y' && c != 'Y' && c != '\t') {
post = null;
return false;
}
} else {
return false;
}
}
@ -4789,7 +5063,7 @@ public class LineReaderImpl implements LineReader, Flushable
}
} else if (SELF_INSERT.equals(name)) {
sb.append(getLastBinding());
buf.write(getLastBinding());
callWidget(name);
if (cands.isEmpty()) {
post = null;
return false;
@ -5370,28 +5644,10 @@ public class LineReaderImpl implements LineReader, Flushable
}
public boolean beginPaste() {
final Object SELF_INSERT = new Object();
final Object END_PASTE = new Object();
KeyMap<Object> keyMap = new KeyMap<>();
keyMap.setUnicode(SELF_INSERT);
keyMap.setNomatch(SELF_INSERT);
keyMap.setAmbiguousTimeout(0);
keyMap.bind(END_PASTE, BRACKETED_PASTE_END);
StringBuilder sb = new StringBuilder();
while (true) {
Object b = doReadBinding(keyMap, null);
if (b == END_PASTE) {
break;
}
String s = getLastBinding();
if ("\r".equals(s)) {
s = "\n";
}
sb.append(s);
}
String str = doReadStringUntil(BRACKETED_PASTE_END);
regionActive = RegionType.PASTE;
regionMark = getBuffer().cursor();
getBuffer().write(sb);
getBuffer().write(str.replace('\r', '\n'));
return true;
}
@ -5587,6 +5843,7 @@ public class LineReaderImpl implements LineReader, Flushable
bind(emacs, BACKWARD_DELETE_CHAR, del());
bind(emacs, VI_MATCH_BRACKET, translate("^X^B"));
bind(emacs, SEND_BREAK, translate("^X^G"));
bind(emacs, EDIT_AND_EXECUTE_COMMAND, translate("^X^E"));
bind(emacs, VI_FIND_NEXT_CHAR, translate("^X^F"));
bind(emacs, VI_JOIN, translate("^X^J"));
bind(emacs, KILL_BUFFER, translate("^X^K"));
@ -5887,5 +6144,4 @@ public class LineReaderImpl implements LineReader, Flushable
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2018, the original author or authors.
* 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.
@ -34,6 +34,7 @@ public class ArgumentCompleter implements Completer
private final List<Completer> completers = new ArrayList<>();
private boolean strict = true;
private boolean strictCommand = true;
/**
* Create a new completer.
@ -64,6 +65,15 @@ public class ArgumentCompleter implements Completer
this.strict = strict;
}
/**
* If true, a completion at argument index N will only succeed
* if all the completions from 1-(N-1) also succeed.
*
* @param strictCommand the strictCommand flag
*/
public void setStrictCommand(final boolean strictCommand) {
this.strictCommand = strictCommand;
}
/**
* Returns whether a completion at argument index N will success
* if all the completions from arguments 0-(N-1) also succeed.
@ -104,8 +114,12 @@ public class ArgumentCompleter implements Completer
}
// ensure that all the previous completers are successful before allowing this completer to pass (only if strict).
for (int i = 0; isStrict() && (i < line.wordIndex()); i++) {
Completer sub = completers.get(i >= completers.size() ? (completers.size() - 1) : i);
for (int i = strictCommand ? 0 : 1; isStrict() && (i < line.wordIndex()); i++) {
int idx = i >= completers.size() ? (completers.size() - 1) : i;
if (idx == 0 && !strictCommand) {
continue;
}
Completer sub = completers.get(idx);
List<? extends CharSequence> args = line.words();
String arg = (args == null || i >= args.size()) ? "" : args.get(i).toString();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2016, the original author or authors.
* 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.
@ -12,6 +12,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;
import jdk.internal.org.jline.reader.Candidate;
import jdk.internal.org.jline.reader.Completer;
@ -27,11 +28,18 @@ import jdk.internal.org.jline.utils.AttributedString;
*/
public class StringsCompleter implements Completer
{
protected final Collection<Candidate> candidates = new ArrayList<>();
protected Collection<Candidate> candidates = new ArrayList<>();
protected Supplier<Collection<String>> stringsSupplier;
public StringsCompleter() {
}
public StringsCompleter(Supplier<Collection<String>> stringsSupplier) {
assert stringsSupplier != null;
candidates = null;
this.stringsSupplier = stringsSupplier;
}
public StringsCompleter(String... strings) {
this(Arrays.asList(strings));
}
@ -44,14 +52,24 @@ public class StringsCompleter implements Completer
}
public StringsCompleter(Candidate ... candidates) {
this(Arrays.asList(candidates));
}
public StringsCompleter(Collection<Candidate> candidates) {
assert candidates != null;
this.candidates.addAll(Arrays.asList(candidates));
this.candidates.addAll(candidates);
}
public void complete(LineReader reader, final ParsedLine commandLine, final List<Candidate> candidates) {
assert commandLine != null;
assert candidates != null;
candidates.addAll(this.candidates);
if (this.candidates != null) {
candidates.addAll(this.candidates);
} else {
for (String string : stringsSupplier.get()) {
candidates.add(new Candidate(AttributedString.stripAnsi(string), string, null, null, null, null, true));
}
}
}
}

View File

@ -344,7 +344,11 @@ public class DefaultHistory implements History {
}
public String get(final int index) {
return items.get(index - offset).line();
int idx = index - offset;
if (idx >= items.size() || idx < 0) {
throw new IllegalArgumentException("IndexOutOfBounds: Index:" + idx +", Size:" + items.size());
}
return items.get(idx).line();
}
@Override
@ -436,6 +440,10 @@ public class DefaultHistory implements History {
return items.spliterator();
}
public void resetIndex() {
index = index > items.size() ? items.size() : index;
}
protected static class EntryImpl implements Entry {
private final int index;

View File

@ -19,9 +19,11 @@ import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import jdk.internal.org.jline.terminal.impl.AbstractPosixTerminal;
import jdk.internal.org.jline.terminal.impl.AbstractTerminal;
import jdk.internal.org.jline.terminal.impl.DumbTerminal;
import jdk.internal.org.jline.terminal.impl.ExecPty;
import jdk.internal.org.jline.terminal.impl.ExternalTerminal;
@ -85,6 +87,8 @@ public final class TerminalBuilder {
return new TerminalBuilder();
}
private static final AtomicReference<Terminal> SYSTEM_TERMINAL = new AtomicReference<>();
private String name;
private InputStream in;
private OutputStream out;
@ -318,6 +322,7 @@ public final class TerminalBuilder {
Log.warn("Attributes and size fields are ignored when creating a system terminal");
}
IllegalStateException exception = new IllegalStateException("Unable to create a system terminal");
Terminal terminal = null;
if (OSUtils.IS_WINDOWS) {
boolean cygwinTerm = "cygwin".equals(System.getenv("TERM"));
boolean ansiPassThrough = OSUtils.IS_CONEMU;
@ -332,7 +337,7 @@ public final class TerminalBuilder {
if ("xterm".equals(type) && this.type == null && System.getProperty(PROP_TYPE) == null) {
type = "xterm-256color";
}
return new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
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);
@ -341,7 +346,7 @@ public final class TerminalBuilder {
}
if (jna) {
try {
return load(JnaSupport.class).winSysTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, paused, inputStreamWrapper);
terminal = load(JnaSupport.class).winSysTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, paused, inputStreamWrapper);
} catch (Throwable t) {
Log.debug("Error creating JNA based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
@ -349,7 +354,7 @@ public final class TerminalBuilder {
}
if (jansi) {
try {
return load(JansiSupport.class).winSysTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, paused);
terminal = load(JansiSupport.class).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);
@ -359,7 +364,7 @@ public final class TerminalBuilder {
if (jna) {
try {
Pty pty = load(JnaSupport.class).current();
return new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
terminal = new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (Throwable t) {
// ignore
Log.debug("Error creating JNA based terminal: ", t.getMessage(), t);
@ -369,7 +374,7 @@ public final class TerminalBuilder {
if (jansi) {
try {
Pty pty = load(JansiSupport.class).current();
return new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
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);
@ -378,7 +383,7 @@ public final class TerminalBuilder {
if (exec) {
try {
Pty pty = ExecPty.current();
return new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
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);
@ -386,7 +391,24 @@ public final class TerminalBuilder {
}
}
}
if (dumb == null || dumb) {
if (terminal instanceof AbstractTerminal) {
AbstractTerminal t = (AbstractTerminal) terminal;
if (SYSTEM_TERMINAL.compareAndSet(null, t)) {
t.setOnClose(new Runnable() {
@Override
public void run() {
SYSTEM_TERMINAL.compareAndSet(t, null);
}
});
} else {
exception.addSuppressed(new IllegalStateException("A system terminal is already running. " +
"Make sure to use the created system Terminal on the LineReaderBuilder if you're using one " +
"or that previously created system Terminals have been correctly closed."));
terminal.close();
terminal = null;
}
}
if (terminal == null && (dumb == null || dumb)) {
// forced colored dumb terminal
boolean color = getBoolean(PROP_DUMB_COLOR, false);
// detect emacs using the env variable
@ -405,13 +427,15 @@ public final class TerminalBuilder {
Log.warn("Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)");
}
}
return new DumbTerminal(name, color ? Terminal.TYPE_DUMB_COLOR : Terminal.TYPE_DUMB,
terminal = new DumbTerminal(name, color ? Terminal.TYPE_DUMB_COLOR : Terminal.TYPE_DUMB,
new FileInputStream(FileDescriptor.in),
new FileOutputStream(FileDescriptor.out),
encoding, signalHandler);
} else {
}
if (terminal == null) {
throw exception;
}
return terminal;
} else {
if (jna) {
try {
@ -429,14 +453,7 @@ public final class TerminalBuilder {
Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t);
}
}
Terminal terminal = new ExternalTerminal(name, type, in, out, encoding, signalHandler, paused);
if (attributes != null) {
terminal.setAttributes(attributes);
}
if (size != null) {
terminal.setSize(size);
}
return terminal;
return new ExternalTerminal(name, type, in, out, encoding, signalHandler, paused, attributes, size);
}
}

View File

@ -71,8 +71,8 @@ public abstract class AbstractPosixTerminal extends AbstractTerminal {
}
}
public void close() throws IOException {
super.close();
protected void doClose() throws IOException {
super.doClose();
pty.setAttr(originalAttributes);
pty.close();
}

View File

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

View File

@ -43,6 +43,7 @@ public abstract class AbstractTerminal implements Terminal {
protected final Map<Capability, Integer> ints = new HashMap<>();
protected final Map<Capability, String> strings = new HashMap<>();
protected Status status;
protected Runnable onClose;
public AbstractTerminal(String name, String type) throws IOException {
this(name, type, null, SignalHandler.SIG_DFL);
@ -57,6 +58,10 @@ public abstract class AbstractTerminal implements Terminal {
}
}
public void setOnClose(Runnable onClose) {
this.onClose = onClose;
}
public Status getStatus() {
return getStatus(true);
}
@ -85,7 +90,17 @@ public abstract class AbstractTerminal implements Terminal {
}
}
public void close() throws IOException {
public final void close() throws IOException {
try {
doClose();
} finally {
if (onClose != null) {
onClose.run();
}
}
}
protected void doClose() throws IOException {
if (status != null) {
status.update(null);
flush();

View File

@ -214,10 +214,12 @@ public abstract class AbstractWindowsTerminal extends AbstractTerminal {
throw new UnsupportedOperationException("Can not resize windows terminal");
}
public void close() throws IOException {
super.close();
protected void doClose() throws IOException {
super.doClose();
closing = true;
pump.interrupt();
if (pump != null) {
pump.interrupt();
}
ShutdownHooks.remove(closer);
for (Map.Entry<Signal, Object> entry : nativeHandlers.entrySet()) {
Signals.unregister(entry.getKey().name(), entry.getValue());

View File

@ -8,7 +8,9 @@
*/
package jdk.internal.org.jline.terminal.impl;
import jdk.internal.org.jline.terminal.Attributes;
import jdk.internal.org.jline.terminal.Cursor;
import jdk.internal.org.jline.terminal.Size;
import java.io.IOException;
import java.io.InputStream;
@ -57,17 +59,34 @@ public class ExternalTerminal extends LineDisciplineTerminal {
Charset encoding,
SignalHandler signalHandler,
boolean paused) throws IOException {
this(name, type, masterInput, masterOutput, encoding, signalHandler, paused, null, null);
}
public ExternalTerminal(String name, String type,
InputStream masterInput,
OutputStream masterOutput,
Charset encoding,
SignalHandler signalHandler,
boolean paused,
Attributes attributes,
Size size) throws IOException {
super(name, type, masterOutput, encoding, signalHandler);
this.masterInput = masterInput;
if (attributes != null) {
setAttributes(attributes);
}
if (size != null) {
setSize(size);
}
if (!paused) {
resume();
}
}
public void close() throws IOException {
protected void doClose() throws IOException {
if (closed.compareAndSet(false, true)) {
pause();
super.close();
super.doClose();
}
}

View File

@ -257,8 +257,8 @@ public class LineDisciplineTerminal extends AbstractTerminal {
this.slaveInput.setIoException(ioException);
}
public void close() throws IOException {
super.close();
protected void doClose() throws IOException {
super.doClose();
try {
slaveReader.close();
} finally {

View File

@ -80,8 +80,8 @@ public class PosixPtyTerminal extends AbstractPosixTerminal {
}
@Override
public void close() throws IOException {
super.close();
protected void doClose() throws IOException {
super.doClose();
reader.close();
}

View File

@ -87,12 +87,12 @@ public class PosixSysTerminal extends AbstractPosixTerminal {
}
@Override
public void close() throws IOException {
protected void doClose() throws IOException {
ShutdownHooks.remove(closer);
for (Map.Entry<Signal, Object> entry : nativeHandlers.entrySet()) {
Signals.unregister(entry.getKey().name(), entry.getValue());
}
super.close();
super.doClose();
// Do not call reader.close()
reader.shutdown();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2018, the original author or authors.
* Copyright (c) 2002-2020, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -215,17 +215,15 @@ public class Display {
// go to next line column zero
rawPrint(new AttributedString(" \b"));
} else {
AttributedString firstChar =
newLine.columnSubSequence(0, 1);
AttributedString firstChar = newLine.substring(0, 1);
// go to next line column one
rawPrint(firstChar);
cursorPos++;
int firstLength = firstChar.length(); // normally 1
newLine = newLine.substring(firstLength, newLength);
newLength -= firstLength;
if (oldLength >= firstLength) {
oldLine = oldLine.substring(firstLength, oldLength);
oldLength -= firstLength;
cursorPos += firstChar.columnLength(); // normally 1
newLine = newLine.substring(1, newLength);
newLength--;
if (oldLength > 0) {
oldLine = oldLine.substring(1, oldLength);
oldLength--;
}
currentPos = cursorPos;
}
@ -329,7 +327,6 @@ public class Display {
currentPos = cursorPos;
}
}
int was = cursorPos;
if (cursorPos != targetCursorPos) {
moveVisualCursorTo(targetCursorPos < 0 ? currentPos : targetCursorPos, newLines);
}
@ -496,7 +493,7 @@ public class Display {
}
public int wcwidth(String str) {
return AttributedString.fromAnsi(str).columnLength();
return str != null ? AttributedString.fromAnsi(str).columnLength() : 0;
}
}

View File

@ -576,7 +576,9 @@ public final class InfoCmp {
String key = cap.substring(0, index);
String val = cap.substring(index + 1);
int iVal;
if (val.startsWith("0x")) {
if ("0".equals(val)) {
iVal = 0;
} else if (val.startsWith("0x")) {
iVal = Integer.parseInt(val.substring(2), 16);
} else if (val.startsWith("0")) {
iVal = Integer.parseInt(val.substring(1), 8);
@ -615,9 +617,9 @@ public final class InfoCmp {
}
static {
for (String s : Arrays.asList("dumb", "ansi", "xterm", "xterm-256color",
"windows", "windows-256color", "windows-conemu", "windows-vtp",
"screen", "screen-256color")) {
for (String s : Arrays.asList("dumb", "dumb-color", "ansi", "xterm", "xterm-256color",
"windows", "windows-256color", "windows-conemu", "windows-vtp",
"screen", "screen-256color")) {
setDefaultInfoCmp(s, () -> loadDefaultInfoCmp(s));
}
}

View File

@ -197,6 +197,33 @@ public class NonBlocking {
}
}
@Override
public int readBuffered(char[] b) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (b.length == 0) {
return 0;
} else {
if (chars.hasRemaining()) {
int r = Math.min(b.length, chars.remaining());
chars.get(b);
return r;
} else {
byte[] buf = new byte[b.length];
int l = input.readBuffered(buf);
if (l < 0) {
return l;
} else {
ByteBuffer bytes = ByteBuffer.wrap(buf, 0, l);
CharBuffer chars = CharBuffer.wrap(b);
decoder.decode(bytes, chars, false);
chars.flip();
return chars.remaining();
}
}
}
}
@Override
public void shutdown() {
input.shutdown();

View File

@ -78,6 +78,16 @@ public abstract class NonBlockingInputStream extends InputStream {
return 1;
}
public int readBuffered(byte[] b) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (b.length == 0) {
return 0;
} else {
return super.read(b, 0, b.length);
}
}
/**
* Shuts down the thread that is handling blocking I/O if any. Note that if the
* thread is currently blocked waiting for I/O it may not actually

View File

@ -11,15 +11,25 @@ package jdk.internal.org.jline.utils;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.Writer;
import java.nio.CharBuffer;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class NonBlockingPumpReader extends NonBlockingReader {
private static final int DEFAULT_BUFFER_SIZE = 4096;
// Read and write buffer are backed by the same array
private final CharBuffer readBuffer;
private final CharBuffer writeBuffer;
private final char[] buffer;
private int read;
private int write;
private int count;
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
private final Writer writer;
@ -30,111 +40,151 @@ public class NonBlockingPumpReader extends NonBlockingReader {
}
public NonBlockingPumpReader(int bufferSize) {
char[] buf = new char[bufferSize];
this.readBuffer = CharBuffer.wrap(buf);
this.writeBuffer = CharBuffer.wrap(buf);
this.buffer = new char[bufferSize];
this.writer = new NbpWriter();
// There are no bytes available to read after initialization
readBuffer.limit(0);
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
this.notFull = lock.newCondition();
}
public Writer getWriter() {
return this.writer;
}
private int wait(CharBuffer buffer, long timeout) throws InterruptedIOException {
boolean isInfinite = (timeout <= 0L);
long end = 0;
if (!isInfinite) {
end = System.currentTimeMillis() + timeout;
}
while (!closed && !buffer.hasRemaining() && (isInfinite || timeout > 0L)) {
// Wake up waiting readers/writers
notifyAll();
try {
wait(timeout);
} catch (InterruptedException e) {
throw new InterruptedIOException();
}
if (!isInfinite) {
timeout = end - System.currentTimeMillis();
}
}
return closed
? EOF
: buffer.hasRemaining()
? 0
: READ_EXPIRED;
@Override
public boolean ready() {
return available() > 0;
}
private static boolean rewind(CharBuffer buffer, CharBuffer other) {
// Extend limit of other buffer if there is additional input/output available
if (buffer.position() > other.position()) {
other.limit(buffer.position());
public int available() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
// If we have reached the end of the buffer, rewind and set the new limit
if (buffer.position() == buffer.capacity()) {
buffer.rewind();
buffer.limit(other.position());
return true;
}
@Override
protected int read(long timeout, boolean isPeek) throws IOException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// Blocks until more input is available or the reader is closed.
if (!closed && count == 0) {
try {
notEmpty.await(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw (IOException) new InterruptedIOException().initCause(e);
}
}
if (closed) {
return EOF;
} else if (count == 0) {
return READ_EXPIRED;
} else {
if (isPeek) {
return buffer[read];
} else {
int res = buffer[read];
if (++read == buffer.length) {
read = 0;
}
--count;
notFull.signal();
return res;
}
}
} finally {
lock.unlock();
}
}
@Override
public int readBuffered(char[] b) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (b.length == 0) {
return 0;
} else {
return false;
}
}
@Override
public synchronized boolean ready() {
return readBuffer.hasRemaining();
}
public synchronized int available() {
int count = readBuffer.remaining();
if (writeBuffer.position() < readBuffer.position()) {
count += writeBuffer.position();
}
return count;
}
@Override
protected synchronized int read(long timeout, boolean isPeek) throws IOException {
// Blocks until more input is available or the reader is closed.
int res = wait(readBuffer, timeout);
if (res >= 0) {
res = isPeek ? readBuffer.get(readBuffer.position()) : readBuffer.get();
}
rewind(readBuffer, writeBuffer);
return res;
}
synchronized void write(char[] cbuf, int off, int len) throws IOException {
while (len > 0) {
// Blocks until there is new space available for buffering or the
// reader is closed.
if (wait(writeBuffer, 0L) == EOF) {
throw new ClosedException();
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (!closed && count == 0) {
try {
notEmpty.await();
} catch (InterruptedException e) {
throw (IOException) new InterruptedIOException().initCause(e);
}
}
if (closed) {
return EOF;
} else if (count == 0) {
return READ_EXPIRED;
} else {
int r = Math.min(b.length, count);
for (int i = 0; i < r; i++) {
b[i] = buffer[read++];
if (read == buffer.length) {
read = 0;
}
}
count -= r;
notFull.signal();
return r;
}
} finally {
lock.unlock();
}
// Copy as much characters as we can
int count = Math.min(len, writeBuffer.remaining());
writeBuffer.put(cbuf, off, count);
off += count;
len -= count;
// Update buffer states and rewind if necessary
rewind(writeBuffer, readBuffer);
}
}
synchronized void flush() {
// Avoid waking up readers when there is nothing to read
if (readBuffer.hasRemaining()) {
// Notify readers
notifyAll();
void write(char[] cbuf, int off, int len) throws IOException {
if (len > 0) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
while (len > 0) {
// Blocks until there is new space available for buffering or the
// reader is closed.
if (!closed && count == buffer.length) {
try {
notFull.await();
} catch (InterruptedException e) {
throw (IOException) new InterruptedIOException().initCause(e);
}
}
if (closed) {
throw new IOException("Closed");
}
while (len > 0 && count < buffer.length) {
buffer[write++] = cbuf[off++];
count++;
len--;
if (write == buffer.length) {
write = 0;
}
}
notEmpty.signal();
}
} finally {
lock.unlock();
}
}
}
@Override
public synchronized void close() throws IOException {
this.closed = true;
notifyAll();
public void close() throws IOException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
this.closed = true;
this.notEmpty.signalAll();
this.notFull.signalAll();
} finally {
lock.unlock();
}
}
private class NbpWriter extends Writer {
@ -146,7 +196,6 @@ public class NonBlockingPumpReader extends NonBlockingReader {
@Override
public void flush() throws IOException {
NonBlockingPumpReader.this.flush();
}
@Override

View File

@ -85,6 +85,8 @@ public abstract class NonBlockingReader extends Reader {
return 1;
}
public abstract int readBuffered(char[] b) throws IOException;
public int available() {
return 0;
}

View File

@ -90,6 +90,34 @@ public class NonBlockingReaderImpl
return ch >= 0 || in.ready();
}
@Override
public int readBuffered(char[] b) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (b.length == 0) {
return 0;
} else if (exception != null) {
assert ch == READ_EXPIRED;
IOException toBeThrown = exception;
exception = null;
throw toBeThrown;
} else if (ch >= -1) {
b[0] = (char) ch;
ch = READ_EXPIRED;
return 1;
} else if (!threadIsReading) {
return in.read(b);
} else {
int c = read(-1, false);
if (c >= 0) {
b[0] = (char) c;
return 1;
} else {
return -1;
}
}
}
/**
* Attempts to read a character from the input stream for a specific
* period of time.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2018, the original author or authors.
* 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.
@ -13,8 +13,6 @@ import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.terminal.Terminal.Signal;
import jdk.internal.org.jline.terminal.Terminal.SignalHandler;
import jdk.internal.org.jline.terminal.impl.AbstractTerminal;
import jdk.internal.org.jline.utils.InfoCmp.Capability;
import jdk.internal.org.jline.terminal.Size;
@ -29,6 +27,8 @@ public class Status {
protected int columns;
protected boolean force;
protected boolean suspended = false;
protected AttributedString borderString;
protected int border = 0;
public static Status getStatus(Terminal terminal) {
return getStatus(terminal, true);
@ -48,10 +48,20 @@ public class Status {
&& terminal.getStringCapability(Capability.restore_cursor) != null
&& terminal.getStringCapability(Capability.cursor_address) != null;
if (supported) {
char borderChar = '\u2700';
AttributedStringBuilder bb = new AttributedStringBuilder();
for (int i = 0; i < 200; i++) {
bb.append(borderChar);
}
borderString = bb.toAttributedString();
resize();
}
}
public void setBorder(boolean border) {
this.border = border ? 1 : 0;
}
public void resize() {
Size size = terminal.getSize();
this.rows = size.getRows();
@ -68,7 +78,9 @@ public class Status {
return;
}
List<AttributedString> lines = new ArrayList<>(oldLines);
int b = border;
update(null);
border = b;
update(lines);
}
@ -79,6 +91,26 @@ public class Status {
update(oldLines);
}
public void clear() {
privateClear(oldLines.size());
}
private void clearAll() {
int b = border;
border = 0;
privateClear(oldLines.size() + b);
}
private void privateClear(int statusSize) {
List<AttributedString> as = new ArrayList<>();
for (int i = 0; i < statusSize; i++) {
as.add(new AttributedString(""));
}
if (!as.isEmpty()) {
update(as);
}
}
public void update(List<AttributedString> lines) {
if (!supported) {
return;
@ -90,10 +122,14 @@ public class Status {
linesToRestore = new ArrayList<>(lines);
return;
}
if (lines.isEmpty()) {
clearAll();
}
if (oldLines.equals(lines) && !force) {
return;
}
int nb = lines.size() - oldLines.size();
int statusSize = lines.size() + (lines.size() == 0 ? 0 : border);
int nb = statusSize - oldLines.size() - (oldLines.size() == 0 ? 0 : border);
if (nb > 0) {
for (int i = 0; i < nb; i++) {
terminal.puts(Capability.cursor_down);
@ -103,13 +139,28 @@ public class Status {
}
}
terminal.puts(Capability.save_cursor);
terminal.puts(Capability.cursor_address, rows - lines.size(), 0);
terminal.puts(Capability.clr_eos);
terminal.puts(Capability.cursor_address, rows - statusSize, 0);
if (!terminal.puts(Capability.clr_eos)) {
for (int i = rows - statusSize; i < rows; i++) {
terminal.puts(Capability.cursor_address, i, 0);
terminal.puts(Capability.clr_eol);
}
}
if (border == 1 && lines.size() > 0) {
terminal.puts(Capability.cursor_address, rows - statusSize, 0);
borderString.columnSubSequence(0, columns).print(terminal);
}
for (int i = 0; i < lines.size(); i++) {
terminal.puts(Capability.cursor_address, rows - lines.size() + i, 0);
lines.get(i).columnSubSequence(0, columns).print(terminal);
if (lines.get(i).length() > columns) {
AttributedStringBuilder asb = new AttributedStringBuilder();
asb.append(lines.get(i).substring(0, columns - 3)).append("...", new AttributedStyle(AttributedStyle.INVERSE));
asb.toAttributedString().columnSubSequence(0, columns).print(terminal);
} else {
lines.get(i).columnSubSequence(0, columns).print(terminal);
}
}
terminal.puts(Capability.change_scroll_region, 0, rows - 1 - lines.size());
terminal.puts(Capability.change_scroll_region, 0, rows - 1 - statusSize);
terminal.puts(Capability.restore_cursor);
terminal.flush();
oldLines = new ArrayList<>(lines);
@ -121,7 +172,9 @@ public class Status {
return;
}
linesToRestore = new ArrayList<>(oldLines);
int b = border;
update(null);
border = b;
suspended = true;
}
@ -135,7 +188,7 @@ public class Status {
}
public int size() {
return oldLines.size();
return oldLines.size() + border;
}
}

View File

@ -8,7 +8,7 @@ windows-256color|windows with 256 colors terminal compatibility,
il=\E[%p1%dL, il1=\E[L,
dl=\E[%p1%dM, dl1=\E[M,
ech=\E[%p1%dX,
el=\E[K, ed=\E[2K,
el=\E[K, ed=\E[J,
el1=\E[1K, home=\E[H, hpa=\E[%i%p1%dG,
ind=^J,
invis=\E[8m, kbs=^H, kcbt=\E[Z,

View File

@ -8,7 +8,7 @@ windows-conemu|conemu windows terminal,
il=\E[%p1%dL, il1=\E[L,
dl=\E[%p1%dM, dl1=\E[M,
ech=\E[%p1%dX,
el=\E[K, ed=\E[2K,
el=\E[K, ed=\E[J,
el1=\E[1K, home=\E[H, hpa=\E[%i%p1%dG,
ind=^J,
invis=\E[8m, kbs=^H, kcbt=\E[Z,

View File

@ -8,7 +8,7 @@ windows-vtp|windows with virtual terminal processing,
il=\E[%p1%dL, il1=\E[L,
dl=\E[%p1%dM, dl1=\E[M,
ech=\E[%p1%dX,
el=\E[K, ed=\E[2K,
el=\E[K, ed=\E[J,
el1=\E[1K, home=\E[H, hpa=\E[%i%p1%dG,
ind=^J,
invis=\E[8m, kbs=^H, kcbt=\E[Z,

View File

@ -8,7 +8,7 @@ windows|windows terminal compatibility,
il=\E[%p1%dL, il1=\E[L,
dl=\E[%p1%dM, dl1=\E[M,
ech=\E[%p1%dX,
el=\E[K, ed=\E[2K,
el=\E[K, ed=\E[J,
el1=\E[1K, home=\E[H, hpa=\E[%i%p1%dG,
ind=^J,
invis=\E[8m, kbs=^H, kcbt=\E[Z,

View File

@ -1,4 +1,4 @@
## JLine v3.12.1
## JLine v3.14.0
### JLine License
<pre>

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2018, the original author or authors.
* 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.