8241598: Upgrade JLine to 3.14.0
Upgrading to JLine 3.14.0 Reviewed-by: psandoz, rfield
This commit is contained in:
parent
c8b1f966cb
commit
f1ef83b02e
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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><tab></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();
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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;
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -1,4 +1,4 @@
|
||||
## JLine v3.12.1
|
||||
## JLine v3.14.0
|
||||
|
||||
### JLine License
|
||||
<pre>
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user