8273682: Upgrade Jline to 3.20.0

Reviewed-by: sundar
This commit is contained in:
Jan Lahoda 2021-10-13 10:15:54 +00:00
parent dcf428c7a7
commit b8cb76ad21
47 changed files with 2194 additions and 837 deletions

View File

@ -137,4 +137,9 @@ public class Candidate implements Comparable<Candidate> {
public int compareTo(Candidate o) {
return value.compareTo(o.value);
}
@Override
public String toString() {
return "Candidate{" + value + "}";
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2002-2020, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package jdk.internal.org.jline.reader;
import java.util.List;
import java.util.Map;
public interface CompletionMatcher {
/**
* Compiles completion matcher functions
*
* @param options LineReader options
* @param prefix invoked by complete-prefix or expand-or-complete-prefix widget
* @param line The parsed line within which completion has been requested
* @param caseInsensitive if completion is case insensitive or not
* @param errors number of errors accepted in matching
* @param originalGroupName value of JLineReader variable original-group-name
*/
void compile(Map<LineReader.Option, Boolean> options, boolean prefix, CompletingParsedLine line
, boolean caseInsensitive, int errors, String originalGroupName);
/**
*
* @param candidates list of candidates
* @return a map of candidates that completion matcher matches
*/
List<Candidate> matches(List<Candidate> candidates);
/**
*
* @return a candidate that have exact match, null if no exact match found
*/
Candidate exactMatch();
/**
*
* @return a common prefix of matched candidates
*/
String getCommonPrefix();
}

View File

@ -1,75 +0,0 @@
/*
* Copyright (c) 2002-2019, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package jdk.internal.org.jline.reader;
import java.io.IOException;
import java.nio.file.Path;
public class ConfigurationPath {
private Path appConfig;
private Path userConfig;
/**
* Configuration class constructor.
* @param appConfig Application configuration directory
* @param userConfig User private configuration directory
*/
public ConfigurationPath(Path appConfig, Path userConfig) {
this.appConfig = appConfig;
this.userConfig = userConfig;
}
/**
* Search configuration file first from userConfig and then appConfig directory. Returns null if file is not found.
* @param name Configuration file name.
* @return Configuration file.
*
*/
public Path getConfig(String name) {
Path out = null;
if (userConfig != null && userConfig.resolve(name).toFile().exists()) {
out = userConfig.resolve(name);
} else if (appConfig != null && appConfig.resolve(name).toFile().exists()) {
out = appConfig.resolve(name);
}
return out;
}
/**
* Search configuration file from userConfig directory. Returns null if file is not found.
* @param name Configuration file name.
* @return Configuration file.
* @throws IOException When we do not have read access to the file or directory.
*
*/
public Path getUserConfig(String name) throws IOException {
return getUserConfig(name, false);
}
/**
* Search configuration file from userConfig directory. Returns null if file is not found.
* @param name Configuration file name
* @param create When true configuration file is created if not found.
* @return Configuration file.
* @throws IOException When we do not have read/write access to the file or directory.
*/
public Path getUserConfig(String name, boolean create) throws IOException {
Path out = null;
if (userConfig != null) {
if (!userConfig.resolve(name).toFile().exists() && create) {
userConfig.resolve(name).toFile().createNewFile();
}
if (userConfig.resolve(name).toFile().exists()) {
out = userConfig.resolve(name);
}
}
return out;
}
}

View File

@ -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.
@ -15,6 +15,7 @@ package jdk.internal.org.jline.reader;
public class EndOfFileException extends RuntimeException {
private static final long serialVersionUID = 528485360925144689L;
private String partialLine;
public EndOfFileException() {
}
@ -34,4 +35,13 @@ public class EndOfFileException extends RuntimeException {
public EndOfFileException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public EndOfFileException partialLine(String partialLine) {
this.partialLine = partialLine;
return this;
}
public String getPartialLine() {
return partialLine;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2019, the original author or authors.
* Copyright (c) 2002-2021, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -294,7 +294,13 @@ public interface LineReader {
String COMMENT_BEGIN = "comment-begin";
String BELL_STYLE = "bell-style";
String PREFER_VISIBLE_BELL = "prefer-visible-bell";
/** tab completion: if candidates are more than list-max a question will be asked before displaying them */
String LIST_MAX = "list-max";
/**
* tab completion: if candidates are less than menu-list-max
* they are displayed in a list below the field to be completed
*/
String MENU_LIST_MAX = "menu-list-max";
String DISABLE_HISTORY = "disable-history";
String DISABLE_COMPLETION = "disable-completion";
String EDITING_MODE = "editing-mode";
@ -303,6 +309,7 @@ public interface LineReader {
String WORDCHARS = "WORDCHARS";
String REMOVE_SUFFIX_CHARS = "REMOVE_SUFFIX_CHARS";
String SEARCH_TERMINATORS = "search-terminators";
/** Number of matching errors that are accepted by the completion matcher */
String ERRORS = "errors";
/** Property for the "others" group name */
String OTHERS_GROUP_NAME = "OTHERS_GROUP_NAME";
@ -310,12 +317,19 @@ public interface LineReader {
String ORIGINAL_GROUP_NAME = "ORIGINAL_GROUP_NAME";
/** Completion style for displaying groups name */
String COMPLETION_STYLE_GROUP = "COMPLETION_STYLE_GROUP";
String COMPLETION_STYLE_LIST_GROUP = "COMPLETION_STYLE_LIST_GROUP";
/** Completion style for displaying the current selected item */
String COMPLETION_STYLE_SELECTION = "COMPLETION_STYLE_SELECTION";
String COMPLETION_STYLE_LIST_SELECTION = "COMPLETION_STYLE_LIST_SELECTION";
/** Completion style for displaying the candidate description */
String COMPLETION_STYLE_DESCRIPTION = "COMPLETION_STYLE_DESCRIPTION";
String COMPLETION_STYLE_LIST_DESCRIPTION = "COMPLETION_STYLE_LIST_DESCRIPTION";
/** Completion style for displaying the matching part of candidates */
String COMPLETION_STYLE_STARTING = "COMPLETION_STYLE_STARTING";
String COMPLETION_STYLE_LIST_STARTING = "COMPLETION_STYLE_LIST_STARTING";
/** Completion style for displaying the list */
String COMPLETION_STYLE_BACKGROUND = "COMPLETION_STYLE_BACKGROUND";
String COMPLETION_STYLE_LIST_BACKGROUND = "COMPLETION_STYLE_LIST_BACKGROUND";
/**
* Set the template for prompts for secondary (continuation) lines.
* This is a prompt template as described in the class header.
@ -370,10 +384,20 @@ public interface LineReader {
*/
String FEATURES_MAX_BUFFER_SIZE = "features-max-buffer-size";
/**
* Min buffer size for tab auto-suggestions.
* For shorter buffer sizes auto-suggestions are not resolved.
*/
String SUGGESTIONS_MIN_BUFFER_SIZE = "suggestions-min-buffer-size";
Map<String, KeyMap<Binding>> defaultKeyMaps();
enum Option {
COMPLETE_IN_WORD,
/** use camel case completion matcher */
COMPLETE_MATCHER_CAMELCASE,
/** use type completion matcher */
COMPLETE_MATCHER_TYPO(true),
DISABLE_EVENT_EXPANSION,
HISTORY_VERIFY,
HISTORY_IGNORE_SPACE(true),
@ -386,9 +410,13 @@ public interface LineReader {
AUTO_GROUP(true),
AUTO_MENU(true),
AUTO_LIST(true),
/** list candidates below the field to be completed */
AUTO_MENU_LIST,
RECOGNIZE_EXACT,
/** display group name before each group (else display all group names first) */
GROUP(true),
/** when double tab to select candidate keep candidates grouped (else loose grouping) */
GROUP_PERSIST,
/** if completion is case insensitive or not */
CASE_INSENSITIVE,
LIST_AMBIGUOUS,
@ -414,7 +442,8 @@ public interface LineReader {
DELAY_LINE_WRAP,
AUTO_PARAM_SLASH(true),
AUTO_REMOVE_SLASH(true),
USE_FORWARD_SLASH(false),
/** FileNameCompleter: Use '/' character as a file directory separator */
USE_FORWARD_SLASH,
/** When hitting the <code>&lt;tab&gt;</code> key at the beginning of the line, insert a tabulation
* instead of completing. This is mainly useful when {@link #BRACKETED_PASTE} is
* disabled, so that copy/paste of indented text does not trigger completion.
@ -450,6 +479,11 @@ public interface LineReader {
this.def = def;
}
public final boolean isSet(Map<Option, Boolean> options) {
Boolean b = options.get(this);
return b != null ? b : this.isDef();
}
public boolean isDef() {
return def;
}
@ -489,8 +523,9 @@ public interface LineReader {
* Equivalent to <code>readLine(null, null, null)</code>.
*
* @return the line read
* @throws UserInterruptException If the call was interrupted by the user.
* @throws EndOfFileException If the end of the input stream was reached.
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
* @throws java.io.IOError in case of other i/o errors
*/
String readLine() throws UserInterruptException, EndOfFileException;
@ -502,8 +537,9 @@ public interface LineReader {
*
* @param mask The mask character, <code>null</code> or <code>0</code>.
* @return A line that is read from the terminal, can never be null.
* @throws UserInterruptException If the call was interrupted by the user.
* @throws EndOfFileException If the end of the input stream was reached.
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
* @throws java.io.IOError in case of other i/o errors
*/
String readLine(Character mask) throws UserInterruptException, EndOfFileException;
@ -515,8 +551,9 @@ public interface LineReader {
*
* @param prompt The prompt to issue to the terminal, may be null.
* @return A line that is read from the terminal, can never be null.
* @throws UserInterruptException If the call was interrupted by the user.
* @throws EndOfFileException If the end of the input stream was reached.
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
* @throws java.io.IOError in case of other i/o errors
*/
String readLine(String prompt) throws UserInterruptException, EndOfFileException;
@ -529,8 +566,9 @@ public interface LineReader {
* @param prompt The prompt to issue to the terminal, may be null.
* @param mask The mask character, <code>null</code> or <code>0</code>.
* @return A line that is read from the terminal, can never be null.
* @throws UserInterruptException If the call was interrupted by the user.
* @throws EndOfFileException If the end of the input stream was reached.
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
* @throws java.io.IOError in case of other i/o errors
*/
String readLine(String prompt, Character mask) throws UserInterruptException, EndOfFileException;
@ -546,8 +584,9 @@ public interface LineReader {
* @param mask The character mask, may be null.
* @param buffer The default value presented to the user to edit, may be null.
* @return A line that is read from the terminal, can never be null.
* @throws UserInterruptException If the call was interrupted by the user.
* @throws EndOfFileException If the end of the input stream was reached.
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
* @throws java.io.IOError in case of other i/o errors
*/
String readLine(String prompt, Character mask, String buffer) throws UserInterruptException, EndOfFileException;
@ -568,8 +607,6 @@ public interface LineReader {
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
* @throws java.io.IOError in case of other i/o errors
* @throws UserInterruptException If the call was interrupted by the user.
* @throws EndOfFileException If the end of the input stream was reached.
*/
String readLine(String prompt, String rightPrompt, Character mask, String buffer) throws UserInterruptException, EndOfFileException;
@ -590,8 +627,6 @@ public interface LineReader {
* @throws UserInterruptException if readLine was interrupted (using Ctrl-C for example)
* @throws EndOfFileException if an EOF has been found (using Ctrl-D for example)
* @throws java.io.IOError in case of other i/o errors
* @throws UserInterruptException If the call was interrupted by the user.
* @throws EndOfFileException If the end of the input stream was reached.
*/
String readLine(String prompt, String rightPrompt, MaskingCallback maskingCallback, String buffer) throws UserInterruptException, EndOfFileException;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2018, the original author or authors.
* Copyright (c) 2002-2020, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -36,6 +36,7 @@ public final class LineReaderBuilder {
Highlighter highlighter;
Parser parser;
Expander expander;
CompletionMatcher completionMatcher;
private LineReaderBuilder() {
}
@ -103,6 +104,11 @@ public final class LineReaderBuilder {
return this;
}
public LineReaderBuilder completionMatcher(CompletionMatcher completionMatcher) {
this.completionMatcher = completionMatcher;
return this;
}
public LineReader build() {
Terminal terminal = this.terminal;
if (terminal == null) {
@ -133,6 +139,9 @@ public final class LineReaderBuilder {
if (expander != null) {
reader.setExpander(expander);
}
if (completionMatcher != null) {
reader.setCompletionMatcher(completionMatcher);
}
for (Map.Entry<LineReader.Option, Boolean> e : options.entrySet()) {
reader.option(e.getKey(), e.getValue());
}

View File

@ -35,16 +35,12 @@ public interface Parser {
default String getCommand(final String line) {
String out = "";
Pattern patternCommand = Pattern.compile("^\\s*" + REGEX_VARIABLE + "=(" + REGEX_COMMAND + ")(\\s+.*|$)");
Pattern patternCommand = Pattern.compile("^\\s*" + REGEX_VARIABLE + "=(" + REGEX_COMMAND + ")(\\s+|$)");
Matcher matcher = patternCommand.matcher(line);
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 = "";
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2002-2021, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package jdk.internal.org.jline.reader;
import java.io.StringWriter;
import java.io.Writer;
/**
* Redirects a {@link Writer} to a {@link LineReader}'s {@link LineReader#printAbove(String)} method,
* which draws output above the current prompt / input line.
*
* <p>Example:</p>
* <pre>
* LineReader reader = LineReaderBuilder.builder().terminal(terminal).parser(parser).build();
* PrintAboveWriter printAbove = new PrintAboveWriter(reader);
* printAbove.write(new char[] { 'h', 'i', '!', '\n'});
* </pre>
*
*/
public class PrintAboveWriter extends StringWriter {
private final LineReader reader;
public PrintAboveWriter(LineReader reader) {
this.reader = reader;
}
@Override
public void flush() {
StringBuffer buffer = getBuffer();
int lastNewline = buffer.lastIndexOf("\n");
if (lastNewline >= 0) {
reader.printAbove(buffer.substring(0, lastNewline + 1));
buffer.delete(0, lastNewline + 1);
}
}
}

View File

@ -1,153 +0,0 @@
/*
* Copyright (c) 2002-2020, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package jdk.internal.org.jline.reader;
import java.io.File;
import java.nio.file.Path;
import java.util.*;
/**
* Manage scriptEngine variables, statements and script execution.
*
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
*/
public interface ScriptEngine {
/**
*
* @return scriptEngine name
*/
String getEngineName();
/**
*
* @return script file name extensions
*/
Collection<String> getExtensions();
/**
* Tests if console variable exists
* @param name
* @return true if variable exists
*/
boolean hasVariable(String name);
/**
* Creates variable
* @param name of the variable
* @param value of the variable
*/
void put(String name, Object value);
/**
* Gets variable value
* @param name of the variable
* @return value of the variable
*/
Object get(String name);
/**
* Gets all variables with values
* @return map of the variables
*/
default Map<String,Object> find() {
return find(null);
}
/**
* Gets all the variables that match the name. Name can contain * wild cards.
* @param name of the variable
* @return map of the variables
*/
Map<String,Object> find(String name);
/**
* Deletes variables. Variable name cab contain * wild cards.
* @param vars
*/
void del(String... vars);
/**
* Converts object to JSON string.
* @param object object to convert to JSON
* @return formatted JSON string
*/
String toJson(Object object);
/**
* Converts object to string.
* @param object object to convert to string
* @return object string value
*/
String toString(Object object);
/**
* Substitute variable reference with its value.
* @param variable
* @return Substituted variable
* @throws Exception
*/
default Object expandParameter(String variable) {
return expandParameter(variable, "");
}
/**
* Substitute variable reference with its value.
* @param variable
* @param format serialization format
* @return Substituted variable
* @throws Exception
*/
Object expandParameter(String variable, String format);
/**
* Persists object value to file.
* @param file
* @param object
*/
default void persist(Path file, Object object) {
persist(file, object, "JSON");
}
/**
* Persists object value to file.
* @param file
* @param object
* @param format
*/
void persist(Path file, Object object, String format);
/**
* Executes scriptEngine statement
* @param statement
* @return result
* @throws Exception
*/
Object execute(String statement) throws Exception;
/**
* Executes scriptEngine script
* @param script
* @return result
* @throws Exception
*/
default Object execute(File script) throws Exception {
return execute(script, null);
}
/**
* Executes scriptEngine script
* @param script
* @param args
* @return
* @throws Exception
*/
Object execute(File script, Object[] args) throws Exception;
}

View File

@ -0,0 +1,210 @@
/*
* Copyright (c) 2002-2021, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package jdk.internal.org.jline.reader.impl;
import jdk.internal.org.jline.reader.Candidate;
import jdk.internal.org.jline.reader.CompletingParsedLine;
import jdk.internal.org.jline.reader.CompletionMatcher;
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.utils.AttributedString;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class CompletionMatcherImpl implements CompletionMatcher {
protected Predicate<String> exact;
protected List<Function<Map<String, List<Candidate>>, Map<String, List<Candidate>>>> matchers;
private Map<String, List<Candidate>> matching;
private boolean caseInsensitive;
public CompletionMatcherImpl() {
}
protected void reset(boolean caseInsensitive) {
this.caseInsensitive = caseInsensitive;
exact = s -> false;
matchers = new ArrayList<>();
matching = null;
}
@Override
public void compile(Map<LineReader.Option, Boolean> options, boolean prefix, CompletingParsedLine line
, boolean caseInsensitive, int errors, String originalGroupName) {
reset(caseInsensitive);
defaultMatchers(options, prefix, line, caseInsensitive, errors, originalGroupName);
}
@Override
public List<Candidate> matches(List<Candidate> candidates) {
matching = Collections.emptyMap();
Map<String, List<Candidate>> sortedCandidates = sort(candidates);
for (Function<Map<String, List<Candidate>>,
Map<String, List<Candidate>>> matcher : matchers) {
matching = matcher.apply(sortedCandidates);
if (!matching.isEmpty()) {
break;
}
}
return !matching.isEmpty() ? matching.entrySet().stream().flatMap(e -> e.getValue().stream()).collect(Collectors.toList())
: new ArrayList<>();
}
@Override
public Candidate exactMatch() {
if (matching == null) {
throw new IllegalStateException();
}
return matching.values().stream().flatMap(Collection::stream)
.filter(Candidate::complete)
.filter(c -> exact.test(c.value()))
.findFirst().orElse(null);
}
@Override
public String getCommonPrefix() {
if (matching == null) {
throw new IllegalStateException();
}
String commonPrefix = null;
for (String key : matching.keySet()) {
commonPrefix = commonPrefix == null ? key : getCommonStart(commonPrefix, key, caseInsensitive);
}
return commonPrefix;
}
/**
* Default JLine matchers
*/
protected void defaultMatchers(Map<LineReader.Option, Boolean> options, boolean prefix, CompletingParsedLine line
, boolean caseInsensitive, int errors, String originalGroupName) {
// Find matchers
// TODO: glob completion
String wd = line.word();
String wdi = caseInsensitive ? wd.toLowerCase() : wd;
String wp = wdi.substring(0, line.wordCursor());
if (prefix) {
matchers = new ArrayList<>(Arrays.asList(
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).startsWith(wp)),
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).contains(wp))
));
if (LineReader.Option.COMPLETE_MATCHER_TYPO.isSet(options)) {
matchers.add(typoMatcher(wp, errors, caseInsensitive, originalGroupName));
}
exact = s -> caseInsensitive ? s.equalsIgnoreCase(wp) : s.equals(wp);
} else if (!LineReader.Option.EMPTY_WORD_OPTIONS.isSet(options) && wd.length() == 0) {
matchers = new ArrayList<>(Collections.singletonList(simpleMatcher(s -> !s.startsWith("-"))));
exact = s -> caseInsensitive ? s.equalsIgnoreCase(wd) : s.equals(wd);
} else {
if (LineReader.Option.COMPLETE_IN_WORD.isSet(options)) {
String ws = wdi.substring(line.wordCursor());
Pattern p1 = Pattern.compile(Pattern.quote(wp) + ".*" + Pattern.quote(ws) + ".*");
Pattern p2 = Pattern.compile(".*" + Pattern.quote(wp) + ".*" + Pattern.quote(ws) + ".*");
matchers = new ArrayList<>(Arrays.asList(
simpleMatcher(s -> p1.matcher(caseInsensitive ? s.toLowerCase() : s).matches()),
simpleMatcher(s -> p2.matcher(caseInsensitive ? s.toLowerCase() : s).matches())
));
} else {
matchers = new ArrayList<>(Arrays.asList(
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).startsWith(wdi)),
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).contains(wdi))
));
}
if (LineReader.Option.COMPLETE_MATCHER_CAMELCASE.isSet(options)) {
matchers.add(simpleMatcher(s -> camelMatch(wd, 0, s, 0)));
}
if (LineReader.Option.COMPLETE_MATCHER_TYPO.isSet(options)) {
matchers.add(typoMatcher(wdi, errors, caseInsensitive, originalGroupName));
}
exact = s -> caseInsensitive ? s.equalsIgnoreCase(wd) : s.equals(wd);
}
}
protected Function<Map<String, List<Candidate>>,
Map<String, List<Candidate>>> simpleMatcher(Predicate<String> predicate) {
return m -> m.entrySet().stream()
.filter(e -> predicate.test(e.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
protected Function<Map<String, List<Candidate>>,
Map<String, List<Candidate>>> typoMatcher(String word, int errors, boolean caseInsensitive, String originalGroupName) {
return m -> {
Map<String, List<Candidate>> map = m.entrySet().stream()
.filter(e -> ReaderUtils.distance(word, caseInsensitive ? e.getKey().toLowerCase() : e.getKey()) < errors)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (map.size() > 1) {
map.computeIfAbsent(word, w -> new ArrayList<>())
.add(new Candidate(word, word, originalGroupName, null, null, null, false));
}
return map;
};
}
protected boolean camelMatch(String word, int i, String candidate, int j) {
if (word.length() <= i) {
return true;
} else if (candidate.length() <= j) {
return false;
} else {
char c = word.charAt(i);
if (c == candidate.charAt(j)) {
return camelMatch(word, i + 1, candidate, j + 1);
} else {
for (int j1 = j; j1 < candidate.length(); j1++) {
if (Character.isUpperCase(candidate.charAt(j1))) {
if (Character.toUpperCase(c) == candidate.charAt(j1)) {
if (camelMatch(word, i + 1, candidate, j1 + 1)) {
return true;
}
}
}
}
return false;
}
}
}
private Map<String, List<Candidate>> sort(List<Candidate> candidates) {
// Build a list of sorted candidates
Map<String, List<Candidate>> sortedCandidates = new HashMap<>();
for (Candidate candidate : candidates) {
sortedCandidates
.computeIfAbsent(AttributedString.fromAnsi(candidate.value()).toString(), s -> new ArrayList<>())
.add(candidate);
}
return sortedCandidates;
}
private String getCommonStart(String str1, String str2, boolean caseInsensitive) {
int[] s1 = str1.codePoints().toArray();
int[] s2 = str2.codePoints().toArray();
int len = 0;
while (len < Math.min(s1.length, s2.length)) {
int ch1 = s1[len];
int ch2 = s2[len];
if (ch1 != ch2 && caseInsensitive) {
ch1 = Character.toUpperCase(ch1);
ch2 = Character.toUpperCase(ch2);
if (ch1 != ch2) {
ch1 = Character.toLowerCase(ch1);
ch2 = Character.toLowerCase(ch2);
}
}
if (ch1 != ch2) {
break;
}
len++;
}
return new String(s1, 0, len);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2019, the original author or authors.
* Copyright (c) 2002-2021, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -19,8 +19,8 @@ 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;
protected Pattern errorPattern;
protected int errorIndex = -1;
@Override
public void setErrorPattern(Pattern errorPattern) {

View File

@ -24,7 +24,7 @@ public class DefaultParser implements Parser {
ROUND, // ()
CURLY, // {}
SQUARE, // []
ANGLE; // <>
ANGLE // <>
}
private char[] quoteChars = {'\'', '"'};
@ -39,8 +39,8 @@ 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 String regexVariable = "[a-zA-Z_]+[a-zA-Z0-9_-]*((\\.|\\['|\\[\"|\\[)[a-zA-Z0-9_-]*(|']|\"]|]))?";
private String regexCommand = "[:]?[a-zA-Z]+[a-zA-Z0-9_-]*";
private int commandGroup = 4;
//
@ -175,23 +175,25 @@ public class DefaultParser implements Parser {
@Override
public boolean validVariableName(String name) {
return name != null && name.matches(regexVariable);
return name != null && regexVariable != 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);
boolean checkCommandOnly = regexVariable == null;
if (!checkCommandOnly) {
Pattern patternCommand = Pattern.compile("^\\s*" + regexVariable + "=(" + regexCommand + ")(\\s+|$)");
Matcher matcher = patternCommand.matcher(line);
if (matcher.find()) {
out = matcher.group(commandGroup);
} else {
checkCommandOnly = true;
}
}
if (checkCommandOnly) {
out = line.trim().split("\\s+")[0];
if (!out.matches(regexCommand)) {
out = "";
}
@ -202,10 +204,12 @@ public class DefaultParser implements Parser {
@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);
if (regexVariable != null) {
Pattern patternCommand = Pattern.compile("^\\s*(" + regexVariable + ")\\s*=[^=~].*");
Matcher matcher = patternCommand.matcher(line);
if (matcher.find()) {
out = matcher.group(1);
}
}
return out;
}
@ -289,7 +293,7 @@ public class DefaultParser implements Parser {
rawWordLength = rawWordCursor;
}
if (context != ParseContext.COMPLETE) {
if (context != ParseContext.COMPLETE && context != ParseContext.SPLIT_LINE) {
if (eofOnEscapedNewLine && isEscapeChar(line, line.length() - 1)) {
throw new EOFError(-1, -1, "Escaped new line", "newline");
}
@ -609,25 +613,28 @@ public class DefaultParser implements Parser {
}
}
if (escapeChars != null) {
// Completion is protected by an opening quote:
// Delimiters (spaces) don't need to be escaped, nor do other quotes, but everything else does.
// Also, close the quote at the end
if (openingQuote != null) {
needToBeEscaped = i -> isRawEscapeChar(sb.charAt(i)) || String.valueOf(sb.charAt(i)).equals(openingQuote);
}
// Completion is protected by middle quotes:
// Delimiters (spaces) don't need to be escaped, nor do quotes, but everything else does.
else if (middleQuotes) {
needToBeEscaped = i -> isRawEscapeChar(sb.charAt(i));
}
// No quote protection, need to escape everything: delimiter chars (spaces), quote chars
// and escapes themselves
else {
needToBeEscaped = i -> isDelimiterChar(sb, i) || isRawEscapeChar(sb.charAt(i)) || isRawQuoteChar(sb.charAt(i));
}
for (int i = 0; i < sb.length(); i++) {
if (needToBeEscaped.test(i)) {
sb.insert(i++, escapeChars[0]);
if (escapeChars.length > 0) {
// Completion is protected by an opening quote:
// Delimiters (spaces) don't need to be escaped, nor do other quotes, but everything else does.
// Also, close the quote at the end
if (openingQuote != null) {
needToBeEscaped = i -> isRawEscapeChar(sb.charAt(i)) || String.valueOf(sb.charAt(i)).equals(openingQuote);
}
// Completion is protected by middle quotes:
// Delimiters (spaces) don't need to be escaped, nor do quotes, but everything else does.
else if (middleQuotes) {
needToBeEscaped = i -> isRawEscapeChar(sb.charAt(i));
}
// No quote protection, need to escape everything: delimiter chars (spaces), quote chars
// and escapes themselves
else {
needToBeEscaped = i -> isDelimiterChar(sb, i) || isRawEscapeChar(sb.charAt(i))
|| isRawQuoteChar(sb.charAt(i));
}
for (int i = 0; i < sb.length(); i++) {
if (needToBeEscaped.test(i)) {
sb.insert(i++, escapeChars[0]);
}
}
}
} else if (openingQuote == null && !middleQuotes) {

View File

@ -20,7 +20,6 @@ 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.ReentrantLock;
import java.util.function.*;
@ -46,9 +45,9 @@ import jdk.internal.org.jline.utils.AttributedStyle;
import jdk.internal.org.jline.utils.Curses;
import jdk.internal.org.jline.utils.Display;
import jdk.internal.org.jline.utils.InfoCmp.Capability;
import jdk.internal.org.jline.utils.Levenshtein;
import jdk.internal.org.jline.utils.Log;
import jdk.internal.org.jline.utils.Status;
import jdk.internal.org.jline.utils.StyleResolver;
import jdk.internal.org.jline.utils.WCWidth;
import static jdk.internal.org.jline.keymap.KeyMap.alt;
@ -80,18 +79,26 @@ public class LineReaderImpl implements LineReader, Flushable
public static final String DEFAULT_SEARCH_TERMINATORS = "\033\012";
public static final String DEFAULT_BELL_STYLE = "";
public static final int DEFAULT_LIST_MAX = 100;
public static final int DEFAULT_MENU_LIST_MAX = Integer.MAX_VALUE;
public static final int DEFAULT_ERRORS = 2;
public static final long DEFAULT_BLINK_MATCHING_PAREN = 500L;
public static final long DEFAULT_AMBIGUOUS_BINDING = 1000L;
public static final String DEFAULT_SECONDARY_PROMPT_PATTERN = "%M> ";
public static final String DEFAULT_OTHERS_GROUP_NAME = "others";
public static final String DEFAULT_ORIGINAL_GROUP_NAME = "original";
public static final String DEFAULT_COMPLETION_STYLE_STARTING = "36"; // cyan
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 String DEFAULT_COMPLETION_STYLE_STARTING = "fg:cyan";
public static final String DEFAULT_COMPLETION_STYLE_DESCRIPTION = "fg:bright-black";
public static final String DEFAULT_COMPLETION_STYLE_GROUP = "fg:bright-magenta,bold";
public static final String DEFAULT_COMPLETION_STYLE_SELECTION = "inverse";
public static final String DEFAULT_COMPLETION_STYLE_BACKGROUND = "bg:default";
public static final String DEFAULT_COMPLETION_STYLE_LIST_STARTING = DEFAULT_COMPLETION_STYLE_STARTING;
public static final String DEFAULT_COMPLETION_STYLE_LIST_DESCRIPTION = DEFAULT_COMPLETION_STYLE_DESCRIPTION;
public static final String DEFAULT_COMPLETION_STYLE_LIST_GROUP = "fg:black,bold";
public static final String DEFAULT_COMPLETION_STYLE_LIST_SELECTION = DEFAULT_COMPLETION_STYLE_SELECTION;
public static final String DEFAULT_COMPLETION_STYLE_LIST_BACKGROUND = "bg:bright-magenta";
public static final int DEFAULT_INDENTATION = 0;
public static final int DEFAULT_FEATURES_MAX_BUFFER_SIZE = 1000;
public static final int DEFAULT_SUGGESTIONS_MIN_BUFFER_SIZE = 1;
private static final int MIN_ROWS = 3;
@ -162,6 +169,7 @@ public class LineReaderImpl implements LineReader, Flushable
protected Highlighter highlighter = new DefaultHighlighter();
protected Parser parser = new DefaultParser();
protected Expander expander = new DefaultExpander();
protected CompletionMatcher completionMatcher = new CompletionMatcherImpl();
//
// State variables
@ -270,6 +278,8 @@ public class LineReaderImpl implements LineReader, Flushable
*/
protected List<String> commandsBuffer = new ArrayList<>();
int candidateStartPosition = 0;
public LineReaderImpl(Terminal terminal) throws IOException {
this(terminal, null, null);
}
@ -419,6 +429,10 @@ public class LineReaderImpl implements LineReader, Flushable
this.expander = expander;
}
public void setCompletionMatcher(CompletionMatcher completionMatcher) {
this.completionMatcher = completionMatcher;
}
//
// Line Reading
//
@ -636,7 +650,7 @@ public class LineReaderImpl implements LineReader, Flushable
}
Binding o = readBinding(getKeys(), local);
if (o == null) {
throw new EndOfFileException();
throw new EndOfFileException().partialLine(buf.length() > 0 ? buf.toString() : null);
}
Log.trace("Binding: ", o);
if (buf.length() == 0 && getLastBinding().charAt(0) == originalAttributes.getControlChar(ControlChar.VEOF)) {
@ -741,11 +755,7 @@ public class LineReaderImpl implements LineReader, Flushable
size.copy(terminal.getBufferSize());
display = new Display(terminal, false);
if (size.getRows() == 0 || size.getColumns() == 0) {
display.resize(1, Integer.MAX_VALUE);
} else {
display.resize(size.getRows(), size.getColumns());
}
display.resize(size.getRows(), size.getColumns());
if (isSet(Option.DELAY_LINE_WRAP))
display.setDelayLineWrap(true);
}
@ -1049,8 +1059,7 @@ public class LineReaderImpl implements LineReader, Flushable
@Override
public boolean isSet(Option option) {
Boolean b = options.get(option);
return b != null ? b : option.isDef();
return option.isSet(options);
}
@Override
@ -1070,10 +1079,13 @@ public class LineReaderImpl implements LineReader, Flushable
@Override
public void editAndAddInBuffer(File file) throws Exception {
if (isSet(Option.BRACKETED_PASTE)) {
terminal.writer().write(BRACKETED_PASTE_OFF);
}
Constructor<?> ctor = Class.forName("org.jline.builtins.Nano").getConstructor(Terminal.class, File.class);
Editor editor = (Editor) ctor.newInstance(terminal, new File(file.getParent()));
editor.setRestricted(true);
editor.open(Arrays.asList(file.getName()));
editor.open(Collections.singletonList(file.getName()));
editor.run();
BufferedReader br = new BufferedReader(new FileReader(file));
String line;
@ -1344,7 +1356,7 @@ public class LineReaderImpl implements LineReader, Flushable
protected boolean viForwardWord() {
if (count < 0) {
return callNeg(this::backwardWord);
return callNeg(this::viBackwardWord);
}
while (count-- > 0) {
if (isViAlphaNum(buf.currChar())) {
@ -1395,21 +1407,7 @@ public class LineReaderImpl implements LineReader, Flushable
}
protected boolean emacsForwardWord() {
if (count < 0) {
return callNeg(this::emacsBackwardWord);
}
while (count-- > 0) {
while (buf.cursor() < buf.length() && !isWord(buf.currChar())) {
buf.move(1);
}
if (isInViChangeOperation() && count == 0) {
return true;
}
while (buf.cursor() < buf.length() && isWord(buf.currChar())) {
buf.move(1);
}
}
return true;
return forwardWord();
}
protected boolean viForwardBlankWordEnd() {
@ -1481,7 +1479,7 @@ public class LineReaderImpl implements LineReader, Flushable
protected boolean viBackwardWord() {
if (count < 0) {
return callNeg(this::backwardWord);
return callNeg(this::viForwardWord);
}
while (count-- > 0) {
int nl = 0;
@ -1584,24 +1582,7 @@ public class LineReaderImpl implements LineReader, Flushable
}
protected boolean emacsBackwardWord() {
if (count < 0) {
return callNeg(this::emacsForwardWord);
}
while (count-- > 0) {
while (buf.cursor() > 0) {
buf.move(-1);
if (isWord(buf.currChar())) {
break;
}
}
while (buf.cursor() > 0) {
buf.move(-1);
if (!isWord(buf.currChar())) {
break;
}
}
}
return true;
return backwardWord();
}
protected boolean backwardDeleteWord() {
@ -2557,7 +2538,7 @@ public class LineReaderImpl implements LineReader, Flushable
}
terminal.puts(Capability.keypad_local);
terminal.trackMouse(Terminal.MouseTracking.Off);
if (isSet(Option.BRACKETED_PASTE))
if (isSet(Option.BRACKETED_PASTE) && !isTerminalDumb())
terminal.writer().write(BRACKETED_PASTE_OFF);
flush();
}
@ -2720,7 +2701,7 @@ public class LineReaderImpl implements LineReader, Flushable
starts.add(new Pair<>(index, m.start()));
}
return starts;
}
}
private String doGetSearchPattern() {
StringBuilder sb = new StringBuilder();
@ -3908,7 +3889,7 @@ public class LineReaderImpl implements LineReader, Flushable
}
List<AttributedString> newLinesToDisplay = new ArrayList<>();
int displaySize = size.getRows() - (status != null ? status.size() : 0);
int displaySize = displayRows(status);
if (newLines.size() > displaySize && !isTerminalDumb()) {
StringBuilder sb = new StringBuilder(">....");
// blanks are needed when displaying command completion candidate list
@ -3964,13 +3945,12 @@ public class LineReaderImpl implements LineReader, Flushable
}
History history = getHistory();
StringBuilder sb = new StringBuilder();
char prev = '0';
for (char c: buffer.toCharArray()) {
if ((c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '^') && prev != '\\' ) {
for (char c: buffer.replace("\\", "\\\\").toCharArray()) {
if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '^' || c == '*'
|| c == '$' || c == '.' || c == '?' || c == '+') {
sb.append('\\');
}
sb.append(c);
prev = c;
}
Pattern pattern = Pattern.compile(sb.toString() + ".*", Pattern.DOTALL);
Iterator<History.Entry> iter = history.reverseIterator(history.last());
@ -4002,7 +3982,7 @@ public class LineReaderImpl implements LineReader, Flushable
AttributedStringBuilder full = new AttributedStringBuilder().tabs(TAB_WIDTH);
full.append(prompt);
full.append(tNewBuf);
if (doAutosuggestion) {
if (doAutosuggestion && !isTerminalDumb()) {
String lastBinding = getLastBinding() != null ? getLastBinding() : "";
if (autosuggestion == SuggestionType.HISTORY) {
AttributedStringBuilder sb = new AttributedStringBuilder();
@ -4010,8 +3990,9 @@ public class LineReaderImpl implements LineReader, Flushable
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() == '=')) {
if (buf.length() >= getInt(SUGGESTIONS_MIN_BUFFER_SIZE, DEFAULT_SUGGESTIONS_MIN_BUFFER_SIZE)
&& buf.length() == buf.cursor()
&& (!lastBinding.equals("\t") || buf.prevChar() == ' ' || buf.prevChar() == '=')) {
clearChoices();
listChoices(true);
} else if (!lastBinding.equals("\t")) {
@ -4184,6 +4165,9 @@ public class LineReaderImpl implements LineReader, Flushable
List<String> missings = new ArrayList<>();
if (computePrompts && secondaryPromptPattern.contains("%P")) {
width = prompt.columnLength();
if (width > size.getColumns() || prompt.contains('\n')) {
width = new TerminalLine(prompt.toString(), 0, size.getColumns()).getEndLine().length();
}
for (int line = 0; line < lines.size() - 1; line++) {
AttributedString prompt;
buf.append(lines.get(line)).append("\n");
@ -4398,6 +4382,9 @@ public class LineReaderImpl implements LineReader, Flushable
}
} catch (Exception e) {
Log.info("Error while finding completion candidates", e);
if (Log.isDebugEnabled()) {
e.printStackTrace();
}
return false;
}
@ -4423,79 +4410,17 @@ public class LineReaderImpl implements LineReader, Flushable
boolean caseInsensitive = isSet(Option.CASE_INSENSITIVE);
int errors = getInt(ERRORS, DEFAULT_ERRORS);
// Build a list of sorted candidates
Map<String, List<Candidate>> sortedCandidates = new HashMap<>();
for (Candidate cand : candidates) {
sortedCandidates
.computeIfAbsent(AttributedString.fromAnsi(cand.value()).toString(), s -> new ArrayList<>())
.add(cand);
}
// Find matchers
// TODO: glob completion
List<Function<Map<String, List<Candidate>>,
Map<String, List<Candidate>>>> matchers;
Predicate<String> exact;
if (prefix) {
String wd = line.word();
String wdi = caseInsensitive ? wd.toLowerCase() : wd;
String wp = wdi.substring(0, line.wordCursor());
matchers = Arrays.asList(
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).startsWith(wp)),
simpleMatcher(s -> (caseInsensitive ? s.toLowerCase() : s).contains(wp)),
typoMatcher(wp, errors, caseInsensitive)
);
exact = s -> caseInsensitive ? s.equalsIgnoreCase(wp) : s.equals(wp);
} else if (isSet(Option.COMPLETE_IN_WORD)) {
String wd = line.word();
String wdi = caseInsensitive ? wd.toLowerCase() : wd;
String wp = wdi.substring(0, line.wordCursor());
String ws = wdi.substring(line.wordCursor());
Pattern p1 = Pattern.compile(Pattern.quote(wp) + ".*" + Pattern.quote(ws) + ".*");
Pattern p2 = Pattern.compile(".*" + Pattern.quote(wp) + ".*" + Pattern.quote(ws) + ".*");
matchers = Arrays.asList(
simpleMatcher(s -> p1.matcher(caseInsensitive ? s.toLowerCase() : s).matches()),
simpleMatcher(s -> p2.matcher(caseInsensitive ? s.toLowerCase() : s).matches()),
typoMatcher(wdi, errors, caseInsensitive)
);
exact = s -> caseInsensitive ? s.equalsIgnoreCase(wd) : s.equals(wd);
} else {
String wd = line.word();
String wdi = caseInsensitive ? wd.toLowerCase() : wd;
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);
}
completionMatcher.compile(options, prefix, line, caseInsensitive, errors, getOriginalGroupName());
// Find matching candidates
Map<String, List<Candidate>> matching = Collections.emptyMap();
for (Function<Map<String, List<Candidate>>,
Map<String, List<Candidate>>> matcher : matchers) {
matching = matcher.apply(sortedCandidates);
if (!matching.isEmpty()) {
break;
}
}
List<Candidate> possible = completionMatcher.matches(candidates);
// If we have no matches, bail out
if (matching.isEmpty()) {
if (possible.isEmpty()) {
return false;
}
size.copy(terminal.getSize());
try {
// If we only need to display the list, do it now
if (lst == CompletionType.List) {
List<Candidate> possible = matching.entrySet().stream()
.flatMap(e -> e.getValue().stream())
.collect(Collectors.toList());
doList(possible, line.word(), false, line::escape, forSuggestion);
return !possible.isEmpty();
}
@ -4503,16 +4428,12 @@ public class LineReaderImpl implements LineReader, Flushable
// Check if there's a single possible match
Candidate completion = null;
// If there's a single possible completion
if (matching.size() == 1) {
completion = matching.values().stream().flatMap(Collection::stream)
.findFirst().orElse(null);
if (possible.size() == 1) {
completion = possible.get(0);
}
// Or if RECOGNIZE_EXACT is set, try to find an exact match
else if (isSet(Option.RECOGNIZE_EXACT)) {
completion = matching.values().stream().flatMap(Collection::stream)
.filter(Candidate::complete)
.filter(c -> exact.test(c.value()))
.findFirst().orElse(null);
completion = completionMatcher.exactMatch();
}
// Complete and exit
if (completion != null && !completion.value().isEmpty()) {
@ -4531,6 +4452,9 @@ public class LineReaderImpl implements LineReader, Flushable
}
}
if (completion.suffix() != null) {
if (autosuggestion == SuggestionType.COMPLETER) {
listChoices(true);
}
redisplay();
Binding op = readBinding(getKeys());
if (op != null) {
@ -4549,10 +4473,6 @@ public class LineReaderImpl implements LineReader, Flushable
return true;
}
List<Candidate> possible = matching.entrySet().stream()
.flatMap(e -> e.getValue().stream())
.collect(Collectors.toList());
if (useMenu) {
buf.move(line.word().length() - line.wordCursor());
buf.backspace(line.word().length());
@ -4570,10 +4490,7 @@ public class LineReaderImpl implements LineReader, Flushable
}
// Now, we need to find the unambiguous completion
// TODO: need to find common suffix
String commonPrefix = null;
for (String key : matching.keySet()) {
commonPrefix = commonPrefix == null ? key : getCommonStart(commonPrefix, key, caseInsensitive);
}
String commonPrefix = completionMatcher.getCommonPrefix();
boolean hasUnambiguous = commonPrefix.startsWith(current) && !commonPrefix.equals(current);
if (hasUnambiguous) {
@ -4641,7 +4558,7 @@ public class LineReaderImpl implements LineReader, Flushable
protected Comparator<Candidate> getCandidateComparator(boolean caseInsensitive, String word) {
String wdi = caseInsensitive ? word.toLowerCase() : word;
ToIntFunction<String> wordDistance = w -> distance(wdi, caseInsensitive ? w.toLowerCase() : w);
ToIntFunction<String> wordDistance = w -> ReaderUtils.distance(wdi, caseInsensitive ? w.toLowerCase() : w);
return Comparator
.comparing(Candidate::value, Comparator.comparingInt(wordDistance))
.thenComparing(Comparator.naturalOrder());
@ -4688,37 +4605,6 @@ public class LineReaderImpl implements LineReader, Flushable
}
}
private Function<Map<String, List<Candidate>>,
Map<String, List<Candidate>>> simpleMatcher(Predicate<String> pred) {
return m -> m.entrySet().stream()
.filter(e -> pred.test(e.getKey()))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
private Function<Map<String, List<Candidate>>,
Map<String, List<Candidate>>> typoMatcher(String word, int errors, boolean caseInsensitive) {
return m -> {
Map<String, List<Candidate>> map = m.entrySet().stream()
.filter(e -> distance(word, caseInsensitive ? e.getKey() : e.getKey().toLowerCase()) < errors)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
if (map.size() > 1) {
map.computeIfAbsent(word, w -> new ArrayList<>())
.add(new Candidate(word, word, getOriginalGroupName(), null, null, null, false));
}
return map;
};
}
private int distance(String word, String cand) {
if (word.length() < cand.length()) {
int d1 = Levenshtein.distance(word, cand.substring(0, Math.min(cand.length(), word.length())));
int d2 = Levenshtein.distance(word, cand);
return Math.min(d1, d2);
} else {
return Levenshtein.distance(word, cand);
}
}
protected boolean nextBindingIsComplete() {
redisplay();
KeyMap<Binding> keyMap = keyMaps.get(MENU);
@ -4731,6 +4617,19 @@ public class LineReaderImpl implements LineReader, Flushable
}
}
private int displayRows() {
return displayRows(Status.getStatus(terminal, false));
}
private int displayRows(Status status) {
return size.getRows() - (status != null ? status.size() : 0);
}
private int promptLines() {
AttributedString text = insertSecondaryPrompts(AttributedStringBuilder.append(prompt, buf.toString()), new ArrayList<>());
return text.columnSplitLength(size.getColumns(), false, display.delayLineWrap()).size();
}
private class MenuSupport implements Supplier<AttributedString> {
final List<Candidate> possible;
final BiFunction<CharSequence, Boolean, CharSequence> escaper;
@ -4850,10 +4749,7 @@ public class LineReaderImpl implements LineReader, Flushable
// Compute displayed prompt
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();
Status status = Status.getStatus(terminal, false);
int displaySize = size.getRows() - (status != null ? status.size() : 0) - promptLines;
int displaySize = displayRows() - promptLines();
if (pr.lines > displaySize) {
int displayed = displaySize - 1;
if (pr.selectedLine >= 0) {
@ -4902,7 +4798,13 @@ public class LineReaderImpl implements LineReader, Flushable
original.sort(getCandidateComparator(caseInsensitive, completed));
mergeCandidates(original);
computePost(original, null, possible, completed);
// candidate grouping is not supported by MenuSupport
boolean defaultAutoGroup = isSet(Option.AUTO_GROUP);
boolean defaultGroup = isSet(Option.GROUP);
if (!isSet(Option.GROUP_PERSIST)) {
option(Option.AUTO_GROUP, false);
option(Option.GROUP, false);
}
// Build menu support
MenuSupport menuSupport = new MenuSupport(original, completed, escaper);
post = menuSupport;
@ -4959,17 +4861,21 @@ public class LineReaderImpl implements LineReader, Flushable
pushBackBinding(true);
}
post = null;
option(Option.AUTO_GROUP, defaultAutoGroup);
option(Option.GROUP, defaultGroup);
return true;
}
}
doAutosuggestion = false;
callWidget(REDISPLAY);
}
option(Option.AUTO_GROUP, defaultAutoGroup);
option(Option.GROUP, defaultGroup);
return false;
}
protected boolean clearChoices() {
return doList(new ArrayList<Candidate>(), "", false, null, false);
return doList(new ArrayList<>(), "", false, null, false);
}
protected boolean doList(List<Candidate> possible
@ -5009,14 +4915,14 @@ public class LineReaderImpl implements LineReader, Flushable
boolean caseInsensitive = isSet(Option.CASE_INSENSITIVE);
StringBuilder sb = new StringBuilder();
candidateStartPosition = 0;
while (true) {
String current = completed + sb.toString();
List<Candidate> cands;
if (sb.length() > 0) {
cands = possible.stream()
.filter(c -> caseInsensitive
? c.value().toLowerCase().startsWith(current.toLowerCase())
: c.value().startsWith(current))
completionMatcher.compile(options, false, new CompletingWord(current), caseInsensitive, 0
, null);
cands = completionMatcher.matches(possible).stream()
.sorted(getCandidateComparator(caseInsensitive, current))
.collect(Collectors.toList());
} else {
@ -5024,6 +4930,9 @@ public class LineReaderImpl implements LineReader, Flushable
.sorted(getCandidateComparator(caseInsensitive, current))
.collect(Collectors.toList());
}
if (isSet(Option.AUTO_MENU_LIST) && candidateStartPosition == 0) {
candidateStartPosition = candidateStartPosition(cands);
}
post = () -> {
AttributedString t = insertSecondaryPrompts(AttributedStringBuilder.append(prompt, buf.toString()), new ArrayList<>());
int pl = t.columnSplitLength(size.getColumns(), false, display.delayLineWrap()).size();
@ -5035,10 +4944,11 @@ public class LineReaderImpl implements LineReader, Flushable
redisplay(false);
buf.cursor(oldCursor);
println();
List<AttributedString> ls = postResult.post.columnSplitLength(size.getColumns(), false, display.delayLineWrap());
List<AttributedString> ls = pr.post.columnSplitLength(size.getColumns(), false, display.delayLineWrap());
Display d = new Display(terminal, false);
d.resize(size.getRows(), size.getColumns());
d.update(ls, -1);
println();
redrawLine();
return new AttributedString("");
}
@ -5089,6 +4999,59 @@ public class LineReaderImpl implements LineReader, Flushable
}
}
private static class CompletingWord implements CompletingParsedLine {
private final String word;
public CompletingWord(String word) {
this.word = word;
}
@Override
public CharSequence escape(CharSequence candidate, boolean complete) {
return null;
}
@Override
public int rawWordCursor() {
return word.length();
}
@Override
public int rawWordLength() {
return word.length();
}
@Override
public String word() {
return word;
}
@Override
public int wordCursor() {
return word.length();
}
@Override
public int wordIndex() {
return 0;
}
@Override
public List<String> words() {
return null;
}
@Override
public String line() {
return word;
}
@Override
public int cursor() {
return word.length();
}
}
protected static class PostResult {
final AttributedString post;
final int lines;
@ -5156,6 +5119,63 @@ public class LineReaderImpl implements LineReader, Flushable
private static final String DESC_SUFFIX = ")";
private static final int MARGIN_BETWEEN_DISPLAY_AND_DESC = 1;
private static final int MARGIN_BETWEEN_COLUMNS = 3;
private static final int MENU_LIST_WIDTH = 25;
private static class TerminalLine {
private String endLine;
private int startPos;
public TerminalLine(String line, int startPos, int width) {
this.startPos = startPos;
endLine = line.substring(line.lastIndexOf('\n') + 1);
boolean first = true;
while (endLine.length() + (first ? startPos : 0) > width) {
if (first) {
endLine = endLine.substring(width - startPos);
} else {
endLine = endLine.substring(width);
}
first = false;
}
if (!first) {
this.startPos = 0;
}
}
public int getStartPos() {
return startPos;
}
public String getEndLine() {
return endLine;
}
}
private int candidateStartPosition(List<Candidate> cands) {
List<String> values = cands.stream().map(c -> AttributedString.stripAnsi(c.displ()))
.filter(c -> !c.matches("\\w+") && c.length() > 1).collect(Collectors.toList());
Set<String> notDelimiters = new HashSet<>();
values.forEach(v -> v.substring(0, v.length() - 1).chars()
.filter(c -> !Character.isDigit(c) && !Character.isAlphabetic(c))
.forEach(c -> notDelimiters.add(Character.toString((char)c))));
int width = size.getColumns();
int promptLength = prompt != null ? prompt.length() : 0;
if (promptLength > 0) {
TerminalLine tp = new TerminalLine(prompt.toString(), 0, width);
promptLength = tp.getEndLine().length();
}
TerminalLine tl = new TerminalLine(buf.substring(0, buf.cursor()), promptLength, width);
int out = tl.getStartPos();
String buffer = tl.getEndLine();
for (int i = buffer.length(); i > 0; i--) {
if (buffer.substring(0, i).matches(".*\\W")
&& !notDelimiters.contains(buffer.substring(i - 1, i))) {
out += i;
break;
}
}
return out;
}
@SuppressWarnings("unchecked")
protected PostResult toColumns(List<Object> items, Candidate selection, String completed, Function<String, Integer> wcwidth, int width, boolean rowsFirst) {
@ -5163,6 +5183,7 @@ public class LineReaderImpl implements LineReader, Flushable
// TODO: support Option.LIST_PACKED
// Compute column width
int maxWidth = 0;
int listSize = 0;
for (Object item : items) {
if (item instanceof String) {
int len = wcwidth.apply((String) item);
@ -5170,6 +5191,7 @@ public class LineReaderImpl implements LineReader, Flushable
}
else if (item instanceof List) {
for (Candidate cand : (List<Candidate>) item) {
listSize++;
int len = wcwidth.apply(cand.displ());
if (cand.descr() != null) {
len += MARGIN_BETWEEN_DISPLAY_AND_DESC;
@ -5183,8 +5205,33 @@ public class LineReaderImpl implements LineReader, Flushable
}
// Build columns
AttributedStringBuilder sb = new AttributedStringBuilder();
for (Object list : items) {
toColumns(list, width, maxWidth, sb, selection, completed, rowsFirst, out);
if (listSize > 0) {
if (isSet(Option.AUTO_MENU_LIST)
&& listSize < Math.min(getInt(MENU_LIST_MAX, DEFAULT_MENU_LIST_MAX), displayRows() - promptLines())) {
maxWidth = Math.max(maxWidth, MENU_LIST_WIDTH);
sb.tabs(Math.max(Math.min(candidateStartPosition, width - maxWidth - 1), 1));
width = maxWidth + 2;
if (!isSet(Option.GROUP_PERSIST)) {
List<Candidate> list = new ArrayList<>();
for (Object o : items) {
if (o instanceof Collection) {
list.addAll((Collection<Candidate>) o);
}
}
list = list.stream()
.sorted(getCandidateComparator(isSet(Option.CASE_INSENSITIVE), ""))
.collect(Collectors.toList());
toColumns(list, width, maxWidth, sb, selection, completed, rowsFirst, true, out);
} else {
for (Object list : items) {
toColumns(list, width, maxWidth, sb, selection, completed, rowsFirst, true, out);
}
}
} else {
for (Object list : items) {
toColumns(list, width, maxWidth, sb, selection, completed, rowsFirst, false, out);
}
}
}
if (sb.length() > 0 && sb.charAt(sb.length() - 1) == '\n') {
sb.setLength(sb.length() - 1);
@ -5193,16 +5240,29 @@ public class LineReaderImpl implements LineReader, Flushable
}
@SuppressWarnings("unchecked")
protected void toColumns(Object items, int width, int maxWidth, AttributedStringBuilder sb, Candidate selection, String completed, boolean rowsFirst, int[] out) {
protected void toColumns(Object items, int width, int maxWidth, AttributedStringBuilder sb, Candidate selection, String completed
, boolean rowsFirst, boolean doMenuList, int[] out) {
if (maxWidth <= 0 || width <= 0) {
return;
}
// This is a group
if (items instanceof String) {
sb.style(getCompletionStyleGroup())
if (doMenuList) {
sb.style(AttributedStyle.DEFAULT);
sb.append('\t');
}
AttributedStringBuilder asb = new AttributedStringBuilder();
asb.style(getCompletionStyleGroup(doMenuList))
.append((String) items)
.style(AttributedStyle.DEFAULT)
.append("\n");
.style(AttributedStyle.DEFAULT);
if (doMenuList) {
for (int k = ((String) items).length(); k < maxWidth + 1; k++) {
asb.append(' ');
}
}
sb.style(getCompletionStyleBackground(doMenuList));
sb.append(asb);
sb.append("\n");
out[0]++;
}
// This is a Candidate list
@ -5224,6 +5284,11 @@ public class LineReaderImpl implements LineReader, Flushable
index = (i, j) -> j * lines + i;
}
for (int i = 0; i < lines; i++) {
if (doMenuList) {
sb.style(AttributedStyle.DEFAULT);
sb.append('\t');
}
AttributedStringBuilder asb = new AttributedStringBuilder();
for (int j = 0; j < columns; j++) {
int idx = index.applyAsInt(i, j);
if (idx < candidates.size()) {
@ -5248,56 +5313,85 @@ public class LineReaderImpl implements LineReader, Flushable
}
if (cand == selection) {
out[1] = i;
sb.style(getCompletionStyleSelection());
asb.style(getCompletionStyleSelection(doMenuList));
if (left.toString().regionMatches(
isSet(Option.CASE_INSENSITIVE), 0, completed, 0, completed.length())) {
sb.append(left.toString(), 0, completed.length());
sb.append(left.toString(), completed.length(), left.length());
asb.append(left.toString(), 0, completed.length());
asb.append(left.toString(), completed.length(), left.length());
} else {
sb.append(left.toString());
asb.append(left.toString());
}
for (int k = 0; k < maxWidth - lw - rw; k++) {
sb.append(' ');
asb.append(' ');
}
if (right != null) {
sb.append(right);
asb.append(right);
}
sb.style(AttributedStyle.DEFAULT);
asb.style(AttributedStyle.DEFAULT);
} else {
if (left.toString().regionMatches(
isSet(Option.CASE_INSENSITIVE), 0, completed, 0, completed.length())) {
sb.style(getCompletionStyleStarting());
sb.append(left, 0, completed.length());
sb.style(AttributedStyle.DEFAULT);
sb.append(left, completed.length(), left.length());
asb.style(getCompletionStyleStarting(doMenuList));
asb.append(left, 0, completed.length());
asb.style(AttributedStyle.DEFAULT);
asb.append(left, completed.length(), left.length());
} else {
sb.append(left);
asb.append(left);
}
if (right != null || hasRightItem) {
for (int k = 0; k < maxWidth - lw - rw; k++) {
sb.append(' ');
asb.append(' ');
}
}
if (right != null) {
sb.style(getCompletionStyleDescription());
sb.append(right);
sb.style(AttributedStyle.DEFAULT);
asb.style(getCompletionStyleDescription(doMenuList));
asb.append(right);
asb.style(AttributedStyle.DEFAULT);
} else if (doMenuList) {
for (int k = lw; k < maxWidth; k++) {
asb.append(' ');
}
}
}
if (hasRightItem) {
for (int k = 0; k < MARGIN_BETWEEN_COLUMNS; k++) {
sb.append(' ');
asb.append(' ');
}
}
if (doMenuList) {
asb.append(' ');
}
}
}
sb.style(getCompletionStyleBackground(doMenuList));
sb.append(asb);
sb.append('\n');
}
out[0] += lines;
}
}
private AttributedStyle getCompletionStyleStarting() {
protected AttributedStyle getCompletionStyleStarting(boolean menuList) {
return menuList ? getCompletionStyleListStarting() : getCompletionStyleStarting();
}
protected AttributedStyle getCompletionStyleDescription(boolean menuList) {
return menuList ? getCompletionStyleListDescription() : getCompletionStyleDescription();
}
protected AttributedStyle getCompletionStyleGroup(boolean menuList) {
return menuList ? getCompletionStyleListGroup() : getCompletionStyleGroup();
}
protected AttributedStyle getCompletionStyleSelection(boolean menuList) {
return menuList ? getCompletionStyleListSelection() : getCompletionStyleSelection();
}
protected AttributedStyle getCompletionStyleBackground(boolean menuList) {
return menuList ? getCompletionStyleListBackground() : getCompletionStyleBackground();
}
protected AttributedStyle getCompletionStyleStarting() {
return getCompletionStyle(COMPLETION_STYLE_STARTING, DEFAULT_COMPLETION_STYLE_STARTING);
}
@ -5313,37 +5407,38 @@ public class LineReaderImpl implements LineReader, Flushable
return getCompletionStyle(COMPLETION_STYLE_SELECTION, DEFAULT_COMPLETION_STYLE_SELECTION);
}
protected AttributedStyle getCompletionStyleBackground() {
return getCompletionStyle(COMPLETION_STYLE_BACKGROUND, DEFAULT_COMPLETION_STYLE_BACKGROUND);
}
protected AttributedStyle getCompletionStyleListStarting() {
return getCompletionStyle(COMPLETION_STYLE_LIST_STARTING, DEFAULT_COMPLETION_STYLE_LIST_STARTING);
}
protected AttributedStyle getCompletionStyleListDescription() {
return getCompletionStyle(COMPLETION_STYLE_LIST_DESCRIPTION, DEFAULT_COMPLETION_STYLE_LIST_DESCRIPTION);
}
protected AttributedStyle getCompletionStyleListGroup() {
return getCompletionStyle(COMPLETION_STYLE_LIST_GROUP, DEFAULT_COMPLETION_STYLE_LIST_GROUP);
}
protected AttributedStyle getCompletionStyleListSelection() {
return getCompletionStyle(COMPLETION_STYLE_LIST_SELECTION, DEFAULT_COMPLETION_STYLE_LIST_SELECTION);
}
protected AttributedStyle getCompletionStyleListBackground() {
return getCompletionStyle(COMPLETION_STYLE_LIST_BACKGROUND, DEFAULT_COMPLETION_STYLE_LIST_BACKGROUND);
}
protected AttributedStyle getCompletionStyle(String name, String value) {
return buildStyle(getString(name, value));
return new StyleResolver(s -> getString(s, null)).resolve("." + name, value);
}
protected AttributedStyle buildStyle(String str) {
return AttributedString.fromAnsi("\u001b[" + str + "m ").styleAt(0);
}
private String getCommonStart(String str1, String str2, boolean caseInsensitive) {
int[] s1 = str1.codePoints().toArray();
int[] s2 = str2.codePoints().toArray();
int len = 0;
while (len < Math.min(s1.length, s2.length)) {
int ch1 = s1[len];
int ch2 = s2[len];
if (ch1 != ch2 && caseInsensitive) {
ch1 = Character.toUpperCase(ch1);
ch2 = Character.toUpperCase(ch2);
if (ch1 != ch2) {
ch1 = Character.toLowerCase(ch1);
ch2 = Character.toLowerCase(ch2);
}
}
if (ch1 != ch2) {
break;
}
len++;
}
return new String(s1, 0, len);
}
/**
* Used in "vi" mode for argumented history move, to move a specific
* number of history entries forward or back.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2016, the original author or authors.
* Copyright (c) 2002-2020, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -9,6 +9,7 @@
package jdk.internal.org.jline.reader.impl;
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.utils.Levenshtein;
public class ReaderUtils {
@ -67,4 +68,14 @@ public class ReaderUtils {
return nb;
}
public static int distance(String word, String cand) {
if (word.length() < cand.length()) {
int d1 = Levenshtein.distance(word, cand.substring(0, Math.min(cand.length(), word.length())));
int d2 = Levenshtein.distance(word, cand);
return Math.min(d1, d2);
} else {
return Levenshtein.distance(word, cand);
}
}
}

View File

@ -11,6 +11,7 @@ package jdk.internal.org.jline.reader.impl.completer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
@ -28,10 +29,11 @@ import jdk.internal.org.jline.utils.AttributedString;
*/
public class StringsCompleter implements Completer
{
protected Collection<Candidate> candidates = new ArrayList<>();
protected Collection<Candidate> candidates;
protected Supplier<Collection<String>> stringsSupplier;
public StringsCompleter() {
this(Collections.<Candidate>emptyList());
}
public StringsCompleter(Supplier<Collection<String>> stringsSupplier) {
@ -46,6 +48,7 @@ public class StringsCompleter implements Completer
public StringsCompleter(Iterable<String> strings) {
assert strings != null;
this.candidates = new ArrayList<>();
for (String string : strings) {
candidates.add(new Candidate(AttributedString.stripAnsi(string), string, null, null, null, null, true));
}
@ -57,9 +60,10 @@ public class StringsCompleter implements Completer
public StringsCompleter(Collection<Candidate> candidates) {
assert candidates != null;
this.candidates.addAll(candidates);
this.candidates = new ArrayList<>(candidates);
}
@Override
public void complete(LineReader reader, final ParsedLine commandLine, final List<Candidate> candidates) {
assert commandLine != null;
assert candidates != null;
@ -72,4 +76,9 @@ public class StringsCompleter implements Completer
}
}
@Override
public String toString() {
String value = candidates != null ? candidates.toString() : "{" + stringsSupplier.toString() + "}";
return "StringsCompleter" + value;
}
}

View File

@ -0,0 +1,150 @@
/*
* Copyright (c) 2002-2020, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package jdk.internal.org.jline.reader.impl.completer;
import java.util.*;
import jdk.internal.org.jline.reader.Candidate;
import jdk.internal.org.jline.reader.Completer;
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.reader.ParsedLine;
import jdk.internal.org.jline.utils.AttributedString;
/**
* Completer which contains multiple completers and aggregates them together.
*
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
*/
public class SystemCompleter implements Completer {
private Map<String,List<Completer>> completers = new HashMap<>();
private Map<String,String> aliasCommand = new HashMap<>();
private StringsCompleter commands;
private boolean compiled = false;
public SystemCompleter() {}
@Override
public void complete(LineReader reader, ParsedLine commandLine, List<Candidate> candidates) {
if (!compiled) {
throw new IllegalStateException();
}
assert commandLine != null;
assert candidates != null;
if (commandLine.words().size() > 0) {
if (commandLine.words().size() == 1) {
String buffer = commandLine.words().get(0);
int eq = buffer.indexOf('=');
if (eq < 0) {
commands.complete(reader, commandLine, candidates);
} else if (reader.getParser().validVariableName(buffer.substring(0, eq))) {
String curBuf = buffer.substring(0, eq + 1);
for (String c: completers.keySet()) {
candidates.add(new Candidate(AttributedString.stripAnsi(curBuf+c)
, c, null, null, null, null, true));
}
}
} else {
String cmd = reader.getParser().getCommand(commandLine.words().get(0));
if (command(cmd) != null) {
completers.get(command(cmd)).get(0).complete(reader, commandLine, candidates);
}
}
}
}
public boolean isCompiled() {
return compiled;
}
private String command(String cmd) {
String out = null;
if (cmd != null) {
if (completers.containsKey(cmd)) {
out = cmd;
} else if (aliasCommand.containsKey(cmd)) {
out = aliasCommand.get(cmd);
}
}
return out;
}
public void add(String command, List<Completer> completers) {
for (Completer c : completers) {
add(command, c);
}
}
public void add(List<String> commands, Completer completer) {
for (String c: commands) {
add(c, completer);
}
}
public void add(String command, Completer completer) {
Objects.requireNonNull(command);
if (compiled) {
throw new IllegalStateException();
}
if (!completers.containsKey(command)) {
completers.put(command, new ArrayList<Completer>());
}
if (completer instanceof ArgumentCompleter) {
((ArgumentCompleter) completer).setStrictCommand(false);
}
completers.get(command).add(completer);
}
public void add(SystemCompleter other) {
if (other.isCompiled()) {
throw new IllegalStateException();
}
for (Map.Entry<String, List<Completer>> entry: other.getCompleters().entrySet()) {
for (Completer c: entry.getValue()) {
add(entry.getKey(), c);
}
}
addAliases(other.getAliases());
}
public void addAliases(Map<String,String> aliasCommand) {
if (compiled) {
throw new IllegalStateException();
}
this.aliasCommand.putAll(aliasCommand);
}
private Map<String,String> getAliases() {
return aliasCommand;
}
public void compile() {
if (compiled) {
return;
}
Map<String, List<Completer>> compiledCompleters = new HashMap<>();
for (Map.Entry<String, List<Completer>> entry: completers.entrySet()) {
if (entry.getValue().size() == 1) {
compiledCompleters.put(entry.getKey(), entry.getValue());
} else {
compiledCompleters.put(entry.getKey(), new ArrayList<Completer>());
compiledCompleters.get(entry.getKey()).add(new AggregateCompleter(entry.getValue()));
}
}
completers = compiledCompleters;
Set<String> cmds = new HashSet<>(completers.keySet());
cmds.addAll(aliasCommand.keySet());
commands = new StringsCompleter(cmds);
compiled = true;
}
public Map<String,List<Completer>> getCompleters() {
return completers;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2018, the original author or authors.
* Copyright (c) 2002-2021, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -84,7 +84,7 @@ public class DefaultHistory implements History {
try (BufferedReader reader = Files.newBufferedReader(path)) {
internalClear();
reader.lines().forEach(line -> addHistoryLine(path, line));
setHistoryFileData(path, new HistoryFileData(items.size(), items.size()));
setHistoryFileData(path, new HistoryFileData(items.size(), offset + items.size()));
maybeResize();
}
}
@ -105,7 +105,7 @@ public class DefaultHistory implements History {
Log.trace("Reading history from: ", path);
try (BufferedReader reader = Files.newBufferedReader(path)) {
reader.lines().forEach(line -> addHistoryLine(path, line, incremental));
setHistoryFileData(path, new HistoryFileData(items.size(), items.size()));
setHistoryFileData(path, new HistoryFileData(items.size(), offset + items.size()));
maybeResize();
}
}
@ -136,11 +136,7 @@ public class DefaultHistory implements History {
private boolean isLineReaderHistory (Path path) throws IOException {
Path lrp = getPath();
if (lrp == null) {
if (path != null) {
return false;
} else {
return true;
}
return path == null;
}
return Files.isSameFile(lrp, path);
}
@ -226,7 +222,10 @@ public class DefaultHistory implements History {
private void internalWrite(Path path, int from) throws IOException {
if (path != null) {
Log.trace("Saving history to: ", path);
Files.createDirectories(path.toAbsolutePath().getParent());
Path parent = path.toAbsolutePath().getParent();
if (!Files.exists(parent)) {
Files.createDirectories(parent);
}
// Append new items to the history file
try (BufferedWriter writer = Files.newBufferedWriter(path.toAbsolutePath(),
StandardOpenOption.WRITE, StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
@ -258,11 +257,11 @@ public class DefaultHistory implements History {
});
}
// Remove duplicates
doTrimHistory(allItems, max);
List<Entry> trimmedItems = doTrimHistory(allItems, max);
// Write history
Path temp = Files.createTempFile(path.toAbsolutePath().getParent(), path.getFileName().toString(), ".tmp");
try (BufferedWriter writer = Files.newBufferedWriter(temp, StandardOpenOption.WRITE)) {
for (Entry entry : allItems) {
for (Entry entry : trimmedItems) {
writer.append(format(entry));
}
}
@ -270,8 +269,8 @@ public class DefaultHistory implements History {
// Keep items in memory
if (isLineReaderHistory(path)) {
internalClear();
offset = allItems.get(0).index();
items.addAll(allItems);
offset = trimmedItems.get(0).index();
items.addAll(trimmedItems);
setHistoryFileData(path, new HistoryFileData(items.size(), items.size()));
} else {
setEntriesInFile(path, allItems.size());
@ -297,7 +296,7 @@ public class DefaultHistory implements History {
items.clear();
}
static void doTrimHistory(List<Entry> allItems, int max) {
static List<Entry> doTrimHistory(List<Entry> allItems, int max) {
int idx = 0;
while (idx < allItems.size()) {
int ridx = allItems.size() - idx - 1;
@ -314,6 +313,12 @@ public class DefaultHistory implements History {
while (allItems.size() > max) {
allItems.remove(0);
}
int index = allItems.get(allItems.size() - 1).index() - allItems.size() + 1;
List<Entry> out = new ArrayList<>();
for (Entry e : allItems) {
out.add(new EntryImpl(index++, e.time(), e.line()));
}
return out;
}
public int size() {
@ -338,7 +343,7 @@ public class DefaultHistory implements History {
private String format(Entry entry) {
if (reader.isSet(LineReader.Option.HISTORY_TIMESTAMPED)) {
return Long.toString(entry.time().toEpochMilli()) + ":" + escape(entry.line()) + "\n";
return entry.time().toEpochMilli() + ":" + escape(entry.line()) + "\n";
}
return escape(entry.line()) + "\n";
}
@ -398,6 +403,8 @@ public class DefaultHistory implements History {
sb.append('|');
} else if (ch == '*') {
sb.append('.').append('*');
} else {
sb.append(ch);
}
}
return line.matches(sb.toString());
@ -441,7 +448,7 @@ public class DefaultHistory implements History {
}
public void resetIndex() {
index = index > items.size() ? items.size() : index;
index = Math.min(index, items.size());
}
protected static class EntryImpl implements Entry {
@ -622,7 +629,7 @@ public class DefaultHistory implements History {
return sb.toString();
}
private class HistoryFileData {
private static class HistoryFileData {
private int lastLoaded = 0;
private int entriesInFile = 0;

View File

@ -18,6 +18,7 @@ import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import jdk.internal.org.jline.terminal.impl.NativeSignalHandler;
import jdk.internal.org.jline.utils.ColorPalette;
import jdk.internal.org.jline.utils.InfoCmp.Capability;
import jdk.internal.org.jline.utils.NonBlockingReader;
@ -328,4 +329,10 @@ public interface Terminal extends Closeable, Flushable {
* @return <code>true</code> if focus tracking is supported
*/
boolean trackFocus(boolean tracking);
/**
* Color support
*/
ColorPalette getPalette();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2019, the original author or authors.
* Copyright (c) 2002-2020, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -88,6 +88,7 @@ public final class TerminalBuilder {
}
private static final AtomicReference<Terminal> SYSTEM_TERMINAL = new AtomicReference<>();
private static final AtomicReference<Terminal> TERMINAL_OVERRIDE = new AtomicReference<>();
private String name;
private InputStream in;
@ -100,12 +101,13 @@ public final class TerminalBuilder {
private Boolean jansi;
private Boolean exec;
private Boolean dumb;
private Boolean color;
private Attributes attributes;
private Size size;
private boolean nativeSignals = false;
private Function<InputStream, InputStream> inputStreamWrapper = in -> in;
private Terminal.SignalHandler signalHandler = Terminal.SignalHandler.SIG_DFL;
private boolean paused = false;
private Function<InputStream, InputStream> inputStreamWrapper = in -> in;
private TerminalBuilder() {
}
@ -151,6 +153,11 @@ public final class TerminalBuilder {
return this;
}
public TerminalBuilder color(boolean color) {
this.color = color;
return this;
}
/**
* Set the encoding to use for reading/writing from the console.
* If {@code null} (the default value), JLine will automatically select
@ -205,8 +212,8 @@ public final class TerminalBuilder {
/**
* Attributes to use when creating a non system terminal,
* i.e. when the builder has been given the input and
* outut streams using the {@link #streams(InputStream, OutputStream)} method
* or when {@link #system(boolean)} has been explicitely called with
* output streams using the {@link #streams(InputStream, OutputStream)} method
* or when {@link #system(boolean)} has been explicitly called with
* <code>false</code>.
*
* @param attributes the attributes to use
@ -222,8 +229,8 @@ public final class TerminalBuilder {
/**
* Initial size to use when creating a non system terminal,
* i.e. when the builder has been given the input and
* outut streams using the {@link #streams(InputStream, OutputStream)} method
* or when {@link #system(boolean)} has been explicitely called with
* output streams using the {@link #streams(InputStream, OutputStream)} method
* or when {@link #system(boolean)} has been explicitly called with
* <code>false</code>.
*
* @param size the initial size
@ -246,6 +253,11 @@ public final class TerminalBuilder {
return this;
}
public TerminalBuilder inputStreamWrapper(Function<InputStream, InputStream> wrapper) {
this.inputStreamWrapper = wrapper;
return this;
}
/**
* Initial paused state of the terminal (defaults to false).
* By default, the terminal is started, but in some cases,
@ -261,13 +273,12 @@ public final class TerminalBuilder {
return this;
}
public TerminalBuilder inputStreamWrapper(Function<InputStream, InputStream> wrapper) {
this.inputStreamWrapper = wrapper;
return this;
}
public Terminal build() throws IOException {
Terminal terminal = doBuild();
Terminal override = TERMINAL_OVERRIDE.get();
Terminal terminal = override != null ? override : doBuild();
if (override != null) {
Log.debug(() -> "Overriding terminal with global value set by TerminalBuilder.setTerminalOverride");
}
Log.debug(() -> "Using terminal " + terminal.getClass().getSimpleName());
if (terminal instanceof AbstractPosixTerminal) {
Log.debug(() -> "Using pty " + ((AbstractPosixTerminal) terminal).getPty().getClass().getSimpleName());
@ -318,117 +329,132 @@ public final class TerminalBuilder {
dumb = getBoolean(PROP_DUMB, null);
}
if ((system != null && system) || (system == null && in == null && out == null)) {
if (attributes != null || size != null) {
Log.warn("Attributes and size fields are ignored when creating a system terminal");
if (system != null && ((in != null && !in.equals(System.in)) || (out != null && !out.equals(System.out)))) {
throw new IllegalArgumentException("Cannot create a system terminal using non System streams");
}
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;
//
// Cygwin support
//
if ((OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) && exec && !cygwinTerm) {
try {
Pty pty = ExecPty.current();
// Cygwin defaults to XTERM, but actually supports 256 colors,
// so if the value comes from the environment, change it to xterm-256color
if ("xterm".equals(type) && this.type == null && System.getProperty(PROP_TYPE) == null) {
type = "xterm-256color";
IllegalStateException exception = new IllegalStateException("Unable to create a system terminal");
TerminalBuilderSupport tbs = new TerminalBuilderSupport(jna, jansi);
if (tbs.isConsoleInput() && tbs.isConsoleOutput()) {
if (attributes != null || size != null) {
Log.warn("Attributes and size fields are ignored when creating a system terminal");
}
if (OSUtils.IS_WINDOWS) {
if (!OSUtils.IS_CYGWIN && !OSUtils.IS_MSYSTEM) {
boolean ansiPassThrough = OSUtils.IS_CONEMU;
if (tbs.hasJnaSupport()) {
try {
terminal = tbs.getJnaSupport().winSysTerminal(name, type, ansiPassThrough, encoding, codepage
, nativeSignals, signalHandler, paused, inputStreamWrapper);
} catch (Throwable t) {
Log.debug("Error creating JNA based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
terminal = new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (IOException e) {
// Ignore if not a tty
Log.debug("Error creating EXEC based terminal: ", e.getMessage(), e);
exception.addSuppressed(e);
}
}
if (jna) {
try {
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);
}
}
if (jansi) {
try {
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);
}
}
} else {
if (jna) {
try {
Pty pty = load(JnaSupport.class).current();
terminal = new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (Throwable t) {
// ignore
Log.debug("Error creating JNA based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
if (jansi) {
try {
Pty pty = load(JansiSupport.class).current();
terminal = new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (Throwable t) {
Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
if (exec) {
try {
Pty pty = ExecPty.current();
terminal = new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (Throwable t) {
// Ignore if not a tty
Log.debug("Error creating EXEC based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
}
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);
if (terminal == null && tbs.hasJansiSupport()) {
try {
terminal = tbs.getJansiSupport().winSysTerminal(name, type, ansiPassThrough, encoding, codepage
, nativeSignals, signalHandler, paused);
} catch (Throwable t) {
Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
});
} else if (exec) {
//
// Cygwin support
//
try {
// Cygwin defaults to XTERM, but actually supports 256 colors,
// so if the value comes from the environment, change it to xterm-256color
if ("xterm".equals(type) && this.type == null && System.getProperty(PROP_TYPE) == null) {
type = "xterm-256color";
}
Pty pty = tbs.getExecPty();
terminal = new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (IOException e) {
// Ignore if not a tty
Log.debug("Error creating EXEC based terminal: ", e.getMessage(), e);
exception.addSuppressed(e);
}
}
if (terminal == null && !jna && !jansi && (dumb == null || !dumb)) {
throw new IllegalStateException("Unable to create a system terminal. On windows, either "
+ "JNA or JANSI library is required. Make sure to add one of those in the classpath.");
}
} else {
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 (tbs.hasJnaSupport()) {
try {
Pty pty = tbs.getJnaSupport().current();
terminal = new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (Throwable t) {
// ignore
Log.debug("Error creating JNA based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
if (terminal == null && tbs.hasJansiSupport()) {
try {
Pty pty = tbs.getJansiSupport().current();
terminal = new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (Throwable t) {
Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
if (terminal == null && exec) {
try {
Pty pty = tbs.getExecPty();
terminal = new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (Throwable t) {
// Ignore if not a tty
Log.debug("Error creating EXEC based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
}
if (terminal instanceof AbstractTerminal) {
AbstractTerminal t = (AbstractTerminal) terminal;
if (SYSTEM_TERMINAL.compareAndSet(null, t)) {
t.setOnClose(() -> SYSTEM_TERMINAL.compareAndSet(t, null));
} else {
exception.addSuppressed(new IllegalStateException("A system terminal is already running. " +
"Make sure to use the created system Terminal on the LineReaderBuilder if you're using one " +
"or that previously created system Terminals have been correctly closed."));
terminal.close();
terminal = null;
}
}
}
if (terminal == null && (dumb == null || dumb)) {
// forced colored dumb terminal
boolean color = getBoolean(PROP_DUMB_COLOR, false);
// detect emacs using the env variable
if (!color) {
color = System.getenv("INSIDE_EMACS") != null;
}
// detect Intellij Idea
if (!color) {
String command = getParentProcessCommand();
color = command != null && command.contains("idea");
}
if (!color && dumb == null) {
if (Log.isDebugEnabled()) {
Log.warn("Creating a dumb terminal", exception);
} else {
Log.warn("Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)");
Boolean color = this.color;
if (color == null) {
color = getBoolean(PROP_DUMB_COLOR, false);
// detect emacs using the env variable
if (!color) {
color = System.getenv("INSIDE_EMACS") != null;
}
// detect Intellij Idea
if (!color) {
String command = getParentProcessCommand();
color = command != null && command.contains("idea");
}
if (!color) {
color = tbs.isConsoleOutput() && System.getenv("TERM") != null;
}
if (!color && dumb == null) {
if (Log.isDebugEnabled()) {
Log.warn("input is tty: {}", tbs.isConsoleInput());
Log.warn("output is tty: {}", tbs.isConsoleOutput());
Log.warn("Creating a dumb terminal", exception);
} else {
Log.warn("Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)");
}
}
}
terminal = new DumbTerminal(name, color ? Terminal.TYPE_DUMB_COLOR : Terminal.TYPE_DUMB,
new FileInputStream(FileDescriptor.in),
inputStreamWrapper.apply(new FileInputStream(FileDescriptor.in)),
new FileOutputStream(FileDescriptor.out),
encoding, signalHandler);
}
@ -440,7 +466,7 @@ public final class TerminalBuilder {
if (jna) {
try {
Pty pty = load(JnaSupport.class).open(attributes, size);
return new PosixPtyTerminal(name, type, pty, in, out, encoding, signalHandler, paused);
return new PosixPtyTerminal(name, type, pty, inputStreamWrapper.apply(in), out, encoding, signalHandler, paused);
} catch (Throwable t) {
Log.debug("Error creating JNA based terminal: ", t.getMessage(), t);
}
@ -448,12 +474,12 @@ public final class TerminalBuilder {
if (jansi) {
try {
Pty pty = load(JansiSupport.class).open(attributes, size);
return new PosixPtyTerminal(name, type, pty, in, out, encoding, signalHandler, paused);
return new PosixPtyTerminal(name, type, pty, inputStreamWrapper.apply(in), out, encoding, signalHandler, paused);
} catch (Throwable t) {
Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t);
}
}
return new ExternalTerminal(name, type, in, out, encoding, signalHandler, paused, attributes, size);
return new ExternalTerminal(name, type, inputStreamWrapper.apply(in), out, encoding, signalHandler, paused, attributes, size);
}
}
@ -482,7 +508,116 @@ public final class TerminalBuilder {
return def;
}
private <S> S load(Class<S> clazz) {
private static <S> S load(Class<S> clazz) {
return ServiceLoader.load(clazz, clazz.getClassLoader()).iterator().next();
}
/**
* Allows an application to override the result of {@link #build()}. The
* intended use case is to allow a container or server application to control
* an embedded application that uses a LineReader that uses Terminal
* constructed with TerminalBuilder.build but provides no public api for setting
* the <code>LineReader</code> of the {@link Terminal}. For example, the sbt
* build tool uses a <code>LineReader</code> to implement an interactive shell.
* One of its supported commands is <code>console</code> which invokes
* the scala REPL. The scala REPL also uses a <code>LineReader</code> and it
* is necessary to override the {@link Terminal} used by the the REPL to
* share the same {@link Terminal} instance used by sbt.
*
* <p>
* When this method is called with a non-null {@link Terminal}, all subsequent
* calls to {@link #build()} will return the provided {@link Terminal} regardless
* of how the {@link TerminalBuilder} was constructed. The default behavior
* of {@link TerminalBuilder} can be restored by calling setTerminalOverride
* with a null {@link Terminal}
* </p>
*
* <p>
* Usage of setTerminalOverride should be restricted to cases where it
* isn't possible to update the api of the nested application to accept
* a {@link Terminal instance}.
* </p>
*
* @param terminal the {@link Terminal} to globally override
*/
@Deprecated
public static void setTerminalOverride(final Terminal terminal) {
TERMINAL_OVERRIDE.set(terminal);
}
private static class TerminalBuilderSupport {
private JansiSupport jansiSupport = null;
private JnaSupport jnaSupport = null;
private Pty pty = null;
private boolean consoleOutput;
TerminalBuilderSupport(boolean jna, boolean jansi) {
if (jna) {
try {
jnaSupport = load(JnaSupport.class);
consoleOutput = jnaSupport.isConsoleOutput();
} catch (Throwable e) {
jnaSupport = null;
Log.debug("jnaSupport.isConsoleOutput(): ", e);
}
}
if (jansi) {
try {
jansiSupport = load(JansiSupport.class);
consoleOutput = jansiSupport.isConsoleOutput();
} catch (Throwable e) {
jansiSupport = null;
Log.debug("jansiSupport.isConsoleOutput(): ", e);
}
}
if (jnaSupport == null && jansiSupport == null) {
try {
pty = ExecPty.current();
consoleOutput = true;
} catch (Exception e) {
Log.debug("ExecPty.current(): ", e);
}
}
}
public boolean isConsoleOutput() {
return consoleOutput;
}
public boolean isConsoleInput() {
if (pty != null) {
return true;
} else if (hasJnaSupport()) {
return jnaSupport.isConsoleInput();
} else if (hasJansiSupport()) {
return jansiSupport.isConsoleInput();
} else {
return false;
}
}
public boolean hasJnaSupport() {
return jnaSupport != null;
}
public boolean hasJansiSupport() {
return jansiSupport != null;
}
public JnaSupport getJnaSupport() {
return jnaSupport;
}
public JansiSupport getJansiSupport() {
return jansiSupport;
}
public Pty getExecPty() throws IOException {
if (pty == null) {
pty = ExecPty.current();
}
return pty;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2018, the original author or authors.
* Copyright (c) 2002-2021, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -17,6 +17,7 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
@ -27,6 +28,7 @@ import jdk.internal.org.jline.terminal.Attributes.LocalFlag;
import jdk.internal.org.jline.terminal.Cursor;
import jdk.internal.org.jline.terminal.MouseEvent;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.utils.ColorPalette;
import jdk.internal.org.jline.utils.Curses;
import jdk.internal.org.jline.utils.InfoCmp;
import jdk.internal.org.jline.utils.InfoCmp.Capability;
@ -38,10 +40,11 @@ public abstract class AbstractTerminal implements Terminal {
protected final String name;
protected final String type;
protected final Charset encoding;
protected final Map<Signal, SignalHandler> handlers = new HashMap<>();
protected final Map<Signal, SignalHandler> handlers = new ConcurrentHashMap<>();
protected final Set<Capability> bools = new HashSet<>();
protected final Map<Capability, Integer> ints = new HashMap<>();
protected final Map<Capability, String> strings = new HashMap<>();
protected final ColorPalette palette = new ColorPalette(this);
protected Status status;
protected Runnable onClose;
@ -51,7 +54,7 @@ public abstract class AbstractTerminal implements Terminal {
public AbstractTerminal(String name, String type, Charset encoding, SignalHandler signalHandler) throws IOException {
this.name = name;
this.type = type;
this.type = type != null ? type : "ansi";
this.encoding = encoding != null ? encoding : Charset.defaultCharset();
for (Signal signal : Signal.values()) {
handlers.put(signal, signalHandler);
@ -197,12 +200,10 @@ public abstract class AbstractTerminal implements Terminal {
protected void parseInfoCmp() {
String capabilities = null;
if (type != null) {
try {
capabilities = InfoCmp.getInfoCmp(type);
} catch (Exception e) {
Log.warn("Unable to retrieve infocmp for type " + type, e);
}
try {
capabilities = InfoCmp.getInfoCmp(type);
} catch (Exception e) {
Log.warn("Unable to retrieve infocmp for type " + type, e);
}
if (capabilities == null) {
capabilities = InfoCmp.getLoadedInfoCmp("ansi");
@ -241,7 +242,7 @@ public abstract class AbstractTerminal implements Terminal {
@Override
public boolean hasFocusSupport() {
return type != null && type.startsWith("xterm");
return type.startsWith("xterm");
}
@Override
@ -283,4 +284,8 @@ public abstract class AbstractTerminal implements Terminal {
return false;
}
@Override
public ColorPalette getPalette() {
return palette;
}
}

View File

@ -521,8 +521,6 @@ public abstract class AbstractWindowsTerminal extends AbstractTerminal {
return true;
}
protected abstract int getConsoleOutputCP();
protected abstract int getConsoleMode();
protected abstract void setConsoleMode(int mode);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2019, the original author or authors.
* Copyright (c) 2002-2020, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -25,4 +25,9 @@ public interface JansiSupport {
Terminal winSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, int codepage, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused) throws IOException;
boolean isWindowsConsole();
boolean isConsoleOutput();
boolean isConsoleInput();
}

View File

@ -1,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.
@ -29,4 +29,9 @@ public interface JnaSupport {
Terminal winSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, int codepage, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, Function<InputStream, InputStream> inputStreamWrapper) throws IOException;
boolean isWindowsConsole();
boolean isConsoleOutput();
boolean isConsoleInput();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2019, the original author or authors.
* Copyright (c) 2002-2021, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -20,12 +20,16 @@ import static jdk.internal.org.jline.utils.AttributedStyle.BG_COLOR_EXP;
import static jdk.internal.org.jline.utils.AttributedStyle.FG_COLOR;
import static jdk.internal.org.jline.utils.AttributedStyle.FG_COLOR_EXP;
import static jdk.internal.org.jline.utils.AttributedStyle.F_BACKGROUND;
import static jdk.internal.org.jline.utils.AttributedStyle.F_BACKGROUND_IND;
import static jdk.internal.org.jline.utils.AttributedStyle.F_BACKGROUND_RGB;
import static jdk.internal.org.jline.utils.AttributedStyle.F_BLINK;
import static jdk.internal.org.jline.utils.AttributedStyle.F_BOLD;
import static jdk.internal.org.jline.utils.AttributedStyle.F_CONCEAL;
import static jdk.internal.org.jline.utils.AttributedStyle.F_CROSSED_OUT;
import static jdk.internal.org.jline.utils.AttributedStyle.F_FAINT;
import static jdk.internal.org.jline.utils.AttributedStyle.F_FOREGROUND;
import static jdk.internal.org.jline.utils.AttributedStyle.F_FOREGROUND_IND;
import static jdk.internal.org.jline.utils.AttributedStyle.F_FOREGROUND_RGB;
import static jdk.internal.org.jline.utils.AttributedStyle.F_INVERSE;
import static jdk.internal.org.jline.utils.AttributedStyle.F_ITALIC;
import static jdk.internal.org.jline.utils.AttributedStyle.F_UNDERLINE;
@ -35,6 +39,15 @@ import static jdk.internal.org.jline.terminal.TerminalBuilder.PROP_DISABLE_ALTER
public abstract class AttributedCharSequence implements CharSequence {
public static final int TRUE_COLORS = 0x1000000;
private static final int HIGH_COLORS = 0x7FFF;
public enum ForceMode {
None,
Force256Colors,
ForceTrueColors
}
// cache the value here as we can't afford to get it each time
static final boolean DISABLE_ALTERNATE_CHARSET = Boolean.getBoolean(PROP_DISABLE_ALTERNATE_CHARSET);
@ -55,33 +68,54 @@ public abstract class AttributedCharSequence implements CharSequence {
return toString();
}
int colors = 256;
boolean force256colors = false;
ForceMode forceMode = ForceMode.None;
ColorPalette palette = null;
String alternateIn = null, alternateOut = null;
if (terminal != null) {
Integer max_colors = terminal.getNumericCapability(Capability.max_colors);
if (max_colors != null) {
colors = max_colors;
}
force256colors = AbstractWindowsTerminal.TYPE_WINDOWS_256_COLOR.equals(terminal.getType())
|| AbstractWindowsTerminal.TYPE_WINDOWS_CONEMU.equals(terminal.getType());
if (AbstractWindowsTerminal.TYPE_WINDOWS_256_COLOR.equals(terminal.getType())
|| AbstractWindowsTerminal.TYPE_WINDOWS_CONEMU.equals(terminal.getType())) {
forceMode = ForceMode.Force256Colors;
}
palette = terminal.getPalette();
if (!DISABLE_ALTERNATE_CHARSET) {
alternateIn = Curses.tputs(terminal.getStringCapability(Capability.enter_alt_charset_mode));
alternateOut = Curses.tputs(terminal.getStringCapability(Capability.exit_alt_charset_mode));
}
}
return toAnsi(colors, force256colors, alternateIn, alternateOut);
return toAnsi(colors, forceMode, palette, alternateIn, alternateOut);
}
@Deprecated
public String toAnsi(int colors, boolean force256colors) {
return toAnsi(colors, force256colors, null, null);
}
@Deprecated
public String toAnsi(int colors, boolean force256colors, String altIn, String altOut) {
return toAnsi(colors, force256colors ? ForceMode.Force256Colors : ForceMode.None, null, altIn, altOut);
}
public String toAnsi(int colors, ForceMode force) {
return toAnsi(colors, force, null, null, null);
}
public String toAnsi(int colors, ForceMode force, ColorPalette palette) {
return toAnsi(colors, force, palette, null, null);
}
public String toAnsi(int colors, ForceMode force, ColorPalette palette, String altIn, String altOut) {
StringBuilder sb = new StringBuilder();
int style = 0;
int foreground = -1;
int background = -1;
long style = 0;
long foreground = 0;
long background = 0;
boolean alt = false;
if (palette == null) {
palette = ColorPalette.DEFAULT;
}
for (int i = 0; i < length(); i++) {
char c = charAt(i);
if (altIn != null && altOut != null) {
@ -105,14 +139,14 @@ public abstract class AttributedCharSequence implements CharSequence {
sb.append(alt ? altIn : altOut);
}
}
int s = styleCodeAt(i) & ~F_HIDDEN; // The hidden flag does not change the ansi styles
long s = styleCodeAt(i) & ~F_HIDDEN; // The hidden flag does not change the ansi styles
if (style != s) {
int d = (style ^ s) & MASK;
int fg = (s & F_FOREGROUND) != 0 ? (s & FG_COLOR) >>> FG_COLOR_EXP : -1;
int bg = (s & F_BACKGROUND) != 0 ? (s & BG_COLOR) >>> BG_COLOR_EXP : -1;
long d = (style ^ s) & MASK;
long fg = (s & F_FOREGROUND) != 0 ? s & (FG_COLOR | F_FOREGROUND) : 0;
long bg = (s & F_BACKGROUND) != 0 ? s & (BG_COLOR | F_BACKGROUND) : 0;
if (s == 0) {
sb.append("\033[0m");
foreground = background = -1;
foreground = background = 0;
} else {
sb.append("\033[");
boolean first = true;
@ -135,18 +169,38 @@ public abstract class AttributedCharSequence implements CharSequence {
first = attr(sb, (s & F_CROSSED_OUT) != 0 ? "9" : "29", first);
}
if (foreground != fg) {
if (fg >= 0) {
int rounded = Colors.roundColor(fg, colors);
if (rounded < 8 && !force256colors) {
first = attr(sb, "3" + Integer.toString(rounded), first);
// small hack to force setting bold again after a foreground color change
d |= (s & F_BOLD);
} else if (rounded < 16 && !force256colors) {
first = attr(sb, "9" + Integer.toString(rounded - 8), first);
// small hack to force setting bold again after a foreground color change
d |= (s & F_BOLD);
} else {
first = attr(sb, "38;5;" + Integer.toString(rounded), first);
if (fg > 0) {
int rounded = -1;
if ((fg & F_FOREGROUND_RGB) != 0) {
int r = (int)(fg >> (FG_COLOR_EXP + 16)) & 0xFF;
int g = (int)(fg >> (FG_COLOR_EXP + 8)) & 0xFF;
int b = (int)(fg >> FG_COLOR_EXP) & 0xFF;
if (colors >= HIGH_COLORS) {
first = attr(sb, "38;2;" + r + ";" + g + ";" + b, first);
} else {
rounded = palette.round(r, g, b);
}
} else if ((fg & F_FOREGROUND_IND) != 0) {
rounded = palette.round((int)(fg >> FG_COLOR_EXP) & 0xFF);
}
if (rounded >= 0) {
if (colors >= HIGH_COLORS && force == ForceMode.ForceTrueColors) {
int col = palette.getColor(rounded);
int r = (col >> 16) & 0xFF;
int g = (col >> 8) & 0xFF;
int b = col & 0xFF;
first = attr(sb, "38;2;" + r + ";" + g + ";" + b, first);
} else if (force == ForceMode.Force256Colors || rounded >= 16) {
first = attr(sb, "38;5;" + rounded, first);
} else if (rounded >= 8) {
first = attr(sb, "9" + (rounded - 8), first);
// small hack to force setting bold again after a foreground color change
d |= (s & F_BOLD);
} else {
first = attr(sb, "3" + rounded, first);
// small hack to force setting bold again after a foreground color change
d |= (s & F_BOLD);
}
}
} else {
first = attr(sb, "39", first);
@ -154,14 +208,34 @@ public abstract class AttributedCharSequence implements CharSequence {
foreground = fg;
}
if (background != bg) {
if (bg >= 0) {
int rounded = Colors.roundColor(bg, colors);
if (rounded < 8 && !force256colors) {
first = attr(sb, "4" + Integer.toString(rounded), first);
} else if (rounded < 16 && !force256colors) {
first = attr(sb, "10" + Integer.toString(rounded - 8), first);
} else {
first = attr(sb, "48;5;" + Integer.toString(rounded), first);
if (bg > 0) {
int rounded = -1;
if ((bg & F_BACKGROUND_RGB) != 0) {
int r = (int)(bg >> (BG_COLOR_EXP + 16)) & 0xFF;
int g = (int)(bg >> (BG_COLOR_EXP + 8)) & 0xFF;
int b = (int)(bg >> BG_COLOR_EXP) & 0xFF;
if (colors >= HIGH_COLORS) {
first = attr(sb, "48;2;" + r + ";" + g + ";" + b, first);
} else {
rounded = palette.round(r, g, b);
}
} else if ((bg & F_BACKGROUND_IND) != 0) {
rounded = palette.round((int)(bg >> BG_COLOR_EXP) & 0xFF);
}
if (rounded >= 0) {
if (colors >= HIGH_COLORS && force == ForceMode.ForceTrueColors) {
int col = palette.getColor(rounded);
int r = (col >> 16) & 0xFF;
int g = (col >> 8) & 0xFF;
int b = col & 0xFF;
first = attr(sb, "48;2;" + r + ";" + g + ";" + b, first);
} else if (force == ForceMode.Force256Colors || rounded >= 16) {
first = attr(sb, "48;5;" + rounded, first);
} else if (rounded >= 8) {
first = attr(sb, "10" + (rounded - 8), first);
} else {
first = attr(sb, "4" + rounded, first);
}
}
} else {
first = attr(sb, "49", first);
@ -220,7 +294,7 @@ public abstract class AttributedCharSequence implements CharSequence {
public abstract AttributedStyle styleAt(int index);
int styleCodeAt(int index) {
long styleCodeAt(int index) {
return styleAt(index).getStyle();
}

View File

@ -25,7 +25,7 @@ import java.util.regex.Pattern;
public class AttributedString extends AttributedCharSequence {
final char[] buffer;
final int[] style;
final long[] style;
final int start;
final int end;
public static final AttributedString EMPTY = new AttributedString("");
@ -78,7 +78,7 @@ public class AttributedString extends AttributedCharSequence {
for (int i = 0; i < l; i++) {
buffer[i] = str.charAt(start + i);
}
style = new int[l];
style = new long[l];
if (s != null) {
Arrays.fill(style, s.getStyle());
}
@ -87,7 +87,7 @@ public class AttributedString extends AttributedCharSequence {
}
}
AttributedString(char[] buffer, int[] style, int start, int end) {
AttributedString(char[] buffer, long[] style, int start, int end) {
this.buffer = buffer;
this.style = style;
this.start = start;
@ -142,7 +142,7 @@ public class AttributedString extends AttributedCharSequence {
}
@Override
int styleCodeAt(int index) {
long styleCodeAt(int index) {
return style[start + index];
}
@ -155,7 +155,7 @@ public class AttributedString extends AttributedCharSequence {
Matcher matcher = pattern.matcher(this);
boolean result = matcher.find();
if (result) {
int[] newstyle = this.style.clone();
long[] newstyle = this.style.clone();
do {
for (int i = matcher.start(); i < matcher.end(); i++) {
newstyle[this.start + i] = (newstyle[this.start + i] & ~style.getMask()) | style.getStyle();
@ -185,7 +185,7 @@ public class AttributedString extends AttributedCharSequence {
}
return true;
}
private boolean arrEq(int[] a1, int[] a2, int s1, int s2, int l) {
private boolean arrEq(long[] a1, long[] a2, int s1, int s2, int l) {
for (int i = 0; i < l; i++) {
if (a1[s1+i] != a2[s2+i]) {
return false;

View File

@ -24,7 +24,7 @@ import java.util.regex.Pattern;
public class AttributedStringBuilder extends AttributedCharSequence implements Appendable {
private char[] buffer;
private int[] style;
private long[] style;
private int length;
private TabStops tabs = new TabStops(0);
private int lastLineLength = 0;
@ -44,7 +44,7 @@ public class AttributedStringBuilder extends AttributedCharSequence implements A
public AttributedStringBuilder(int capacity) {
buffer = new char[capacity];
style = new int[capacity];
style = new long[capacity];
length = 0;
}
@ -64,7 +64,7 @@ public class AttributedStringBuilder extends AttributedCharSequence implements A
}
@Override
int styleCodeAt(int index) {
long styleCodeAt(int index) {
return style[index];
}
@ -89,11 +89,17 @@ public class AttributedStringBuilder extends AttributedCharSequence implements A
@Override
public AttributedStringBuilder append(CharSequence csq) {
if (csq == null) {
csq = "null"; // Required by Appendable.append
}
return append(new AttributedString(csq, current));
}
@Override
public AttributedStringBuilder append(CharSequence csq, int start, int end) {
if (csq == null) {
csq = "null"; // Required by Appendable.append
}
return append(csq.subSequence(start, end));
}
@ -152,7 +158,7 @@ public class AttributedStringBuilder extends AttributedCharSequence implements A
ensureCapacity(length + end - start);
for (int i = start; i < end; i++) {
char c = str.charAt(i);
int s = str.styleCodeAt(i) & ~current.getMask() | current.getStyle();
long s = str.styleCodeAt(i) & ~current.getMask() | current.getStyle();
if (tabs.defined() && c == '\t') {
insertTab(new AttributedStyle(s, 0));
} else {
@ -286,12 +292,10 @@ public class AttributedStringBuilder extends AttributedCharSequence implements A
int r = Integer.parseInt(params[++j]);
int g = Integer.parseInt(params[++j]);
int b = Integer.parseInt(params[++j]);
// convert to 256 colors
int col = 16 + (r >> 3) * 36 + (g >> 3) * 6 + (b >> 3);
if (ansiParam == 38) {
current = current.foreground(col);
current = current.foreground(r, g, b);
} else {
current = current.background(col);
current = current.background(r, g, b);
}
}
} else if (ansiParam2 == 5) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2018, the original author or authors.
* Copyright (c) 2002-2021, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -26,24 +26,28 @@ public class AttributedStyle {
public static final int BRIGHT = 8;
static final int F_BOLD = 0x00000001;
static final int F_FAINT = 0x00000002;
static final int F_ITALIC = 0x00000004;
static final int F_UNDERLINE = 0x00000008;
static final int F_BLINK = 0x00000010;
static final int F_INVERSE = 0x00000020;
static final int F_CONCEAL = 0x00000040;
static final int F_CROSSED_OUT = 0x00000080;
static final int F_FOREGROUND = 0x00000100;
static final int F_BACKGROUND = 0x00000200;
static final int F_HIDDEN = 0x00000400;
static final long F_BOLD = 0x00000001;
static final long F_FAINT = 0x00000002;
static final long F_ITALIC = 0x00000004;
static final long F_UNDERLINE = 0x00000008;
static final long F_BLINK = 0x00000010;
static final long F_INVERSE = 0x00000020;
static final long F_CONCEAL = 0x00000040;
static final long F_CROSSED_OUT = 0x00000080;
static final long F_FOREGROUND_IND = 0x00000100;
static final long F_FOREGROUND_RGB = 0x00000200;
static final long F_FOREGROUND = F_FOREGROUND_IND | F_FOREGROUND_RGB;
static final long F_BACKGROUND_IND = 0x00000400;
static final long F_BACKGROUND_RGB = 0x00000800;
static final long F_BACKGROUND = F_BACKGROUND_IND | F_BACKGROUND_RGB;
static final long F_HIDDEN = 0x00001000;
static final int MASK = 0x000007FF;
static final long MASK = 0x00001FFF;
static final int FG_COLOR_EXP = 16;
static final int BG_COLOR_EXP = 24;
static final int FG_COLOR = 0xFF << FG_COLOR_EXP;
static final int BG_COLOR = 0xFF << BG_COLOR_EXP;
static final int FG_COLOR_EXP = 15;
static final int BG_COLOR_EXP = 39;
static final long FG_COLOR = 0xFFFFFFL << FG_COLOR_EXP;
static final long BG_COLOR = 0xFFFFFFL << BG_COLOR_EXP;
public static final AttributedStyle DEFAULT = new AttributedStyle();
public static final AttributedStyle BOLD = DEFAULT.bold();
@ -53,8 +57,8 @@ public class AttributedStyle {
public static final AttributedStyle HIDDEN = DEFAULT.hidden();
public static final AttributedStyle HIDDEN_OFF = DEFAULT.hiddenOff();
final int style;
final int mask;
final long style;
final long mask;
public AttributedStyle() {
this(0, 0);
@ -64,7 +68,7 @@ public class AttributedStyle {
this(s.style, s.mask);
}
public AttributedStyle(int style, int mask) {
public AttributedStyle(long style, long mask) {
this.style = style;
this.mask = mask & MASK | ((style & F_FOREGROUND) != 0 ? FG_COLOR : 0)
| ((style & F_BACKGROUND) != 0 ? BG_COLOR : 0);
@ -135,7 +139,7 @@ public class AttributedStyle {
}
public AttributedStyle inverseNeg() {
int s = (style & F_INVERSE) != 0 ? style & ~F_INVERSE : style | F_INVERSE;
long s = (style & F_INVERSE) != 0 ? style & ~F_INVERSE : style | F_INVERSE;
return new AttributedStyle(s, mask | F_INVERSE);
}
@ -172,7 +176,15 @@ public class AttributedStyle {
}
public AttributedStyle foreground(int color) {
return new AttributedStyle(style & ~FG_COLOR | F_FOREGROUND | ((color << FG_COLOR_EXP) & FG_COLOR), mask | F_FOREGROUND);
return new AttributedStyle(style & ~FG_COLOR | F_FOREGROUND_IND | (((long) color << FG_COLOR_EXP) & FG_COLOR), mask | F_FOREGROUND_IND);
}
public AttributedStyle foreground(int r, int g, int b) {
return foregroundRgb(r << 16 | g << 8 | b);
}
public AttributedStyle foregroundRgb(int color) {
return new AttributedStyle(style & ~FG_COLOR | F_FOREGROUND_RGB | ((((long) color & 0xFFFFFF) << FG_COLOR_EXP) & FG_COLOR), mask | F_FOREGROUND_RGB);
}
public AttributedStyle foregroundOff() {
@ -184,7 +196,15 @@ public class AttributedStyle {
}
public AttributedStyle background(int color) {
return new AttributedStyle(style & ~BG_COLOR | F_BACKGROUND | ((color << BG_COLOR_EXP) & BG_COLOR), mask | F_BACKGROUND);
return new AttributedStyle(style & ~BG_COLOR | F_BACKGROUND_IND | (((long) color << BG_COLOR_EXP) & BG_COLOR), mask | F_BACKGROUND_IND);
}
public AttributedStyle background(int r, int g, int b) {
return backgroundRgb(r << 16 | g << 8 | b);
}
public AttributedStyle backgroundRgb(int color) {
return new AttributedStyle(style & ~BG_COLOR | F_BACKGROUND_RGB | ((((long) color & 0xFFFFFF) << BG_COLOR_EXP) & BG_COLOR), mask | F_BACKGROUND_RGB);
}
public AttributedStyle backgroundOff() {
@ -214,11 +234,11 @@ public class AttributedStyle {
return new AttributedStyle(style & ~F_HIDDEN, mask & ~F_HIDDEN);
}
public int getStyle() {
public long getStyle() {
return style;
}
public int getMask() {
public long getMask() {
return mask;
}
@ -234,8 +254,22 @@ public class AttributedStyle {
@Override
public int hashCode() {
int result = style;
result = 31 * result + mask;
return result;
return 31 * Long.hashCode(style) + Long.hashCode(mask);
}
public String toAnsi() {
AttributedStringBuilder sb = new AttributedStringBuilder();
sb.styled(this, " ");
String s = sb.toAnsi(AttributedCharSequence.TRUE_COLORS, AttributedCharSequence.ForceMode.None);
return s.length() > 1 ? s.substring(2, s.indexOf('m')) : s;
}
@Override
public String toString() {
return "AttributedStyle{" +
"style=" + style +
", mask=" + mask +
", ansi=" + toAnsi() +
'}';
}
}

View File

@ -0,0 +1,262 @@
/*
* Copyright (c) 2002-2020, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package jdk.internal.org.jline.utils;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import jdk.internal.org.jline.terminal.Terminal;
/**
* Color palette
*/
public class ColorPalette {
public static final String XTERM_INITC = "\\E]4;%p1%d;rgb\\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\\E\\\\";
public static final ColorPalette DEFAULT = new ColorPalette();
private final Terminal terminal;
private String distanceName;
private Colors.Distance distance;
private boolean osc4;
private int[] palette;
public ColorPalette() {
this.terminal = null;
this.distanceName = null;
this.palette = Colors.DEFAULT_COLORS_256;
}
public ColorPalette(Terminal terminal) throws IOException {
this(terminal, null);
}
public ColorPalette(Terminal terminal, String distance) throws IOException {
this.terminal = terminal;
this.distanceName = distance;
loadPalette(false);
}
/**
* Get the name of the distance to use for rounding colors.
* @return the name of the color distance
*/
public String getDistanceName() {
return distanceName;
}
/**
* Set the name of the color distance to use when rounding RGB colors to the palette.
* @param name the name of the color distance
*/
public void setDistance(String name) {
this.distanceName = name;
}
/**
* Check if the terminal has the capability to change colors.
* @return <code>true</code> if the terminal can change colors
*/
public boolean canChange() {
return terminal != null && terminal.getBooleanCapability(InfoCmp.Capability.can_change);
}
/**
* Load the palette from the terminal.
* If the palette has already been loaded, subsequent calls will simply return <code>true</code>.
*
* @return <code>true</code> if the palette has been successfully loaded.
* @throws IOException
*/
public boolean loadPalette() throws IOException {
if (!osc4) {
loadPalette(true);
}
return osc4;
}
protected void loadPalette(boolean doLoad) throws IOException {
if (terminal != null) {
int[] pal = doLoad ? doLoad(terminal) : null;
if (pal != null) {
this.palette = pal;
this.osc4 = true;
} else {
Integer cols = terminal.getNumericCapability(InfoCmp.Capability.max_colors);
if (cols != null) {
if (cols == Colors.DEFAULT_COLORS_88.length) {
this.palette = Colors.DEFAULT_COLORS_88;
} else {
this.palette = Arrays.copyOf(Colors.DEFAULT_COLORS_256, Math.min(cols, 256));
}
} else {
this.palette = Arrays.copyOf(Colors.DEFAULT_COLORS_256, 256);
}
this.osc4 = false;
}
} else {
this.palette = Colors.DEFAULT_COLORS_256;
this.osc4 = false;
}
}
/**
* Get the palette length
* @return the palette length
*/
public int getLength() {
return this.palette.length;
}
/**
* Get a specific color in the palette
* @param index the index of the color
* @return the color at the given index
*/
public int getColor(int index) {
return palette[index];
}
/**
* Change the color of the palette
* @param index the index of the color
* @param color the new color value
*/
public void setColor(int index, int color) {
palette[index] = color;
if (canChange()) {
String initc = terminal.getStringCapability(InfoCmp.Capability.initialize_color);
if (initc != null || osc4) {
// initc expects color in 0..1000 range
int r = (((color >> 16) & 0xFF) * 1000) / 255 + 1;
int g = (((color >> 8) & 0xFF) * 1000) / 255 + 1;
int b = ((color & 0xFF) * 1000) / 255 + 1;
if (initc == null) {
// This is the xterm version
initc = XTERM_INITC;
}
Curses.tputs(terminal.writer(), initc, index, r, g, b);
terminal.writer().flush();
}
}
}
public boolean isReal() {
return osc4;
}
public int round(int r, int g, int b) {
return Colors.roundColor((r << 16) + (g << 8) + b, palette, palette.length, getDist());
}
public int round(int col) {
if (col >= palette.length) {
col = Colors.roundColor(DEFAULT.getColor(col), palette, palette.length, getDist());
}
return col;
}
protected Colors.Distance getDist() {
if (distance == null) {
distance = Colors.getDistance(distanceName);
}
return distance;
}
private static int[] doLoad(Terminal terminal) throws IOException {
PrintWriter writer = terminal.writer();
NonBlockingReader reader = terminal.reader();
int[] palette = new int[256];
for (int i = 0; i < 16; i++) {
StringBuilder req = new StringBuilder(1024);
req.append("\033]4");
for (int j = 0; j < 16; j++) {
req.append(';').append(i * 16 + j).append(";?");
}
req.append("\033\\");
writer.write(req.toString());
writer.flush();
boolean black = true;
for (int j = 0; j < 16; j++) {
if (reader.peek(50) < 0) {
break;
}
if (reader.read(10) != '\033'
|| reader.read(10) != ']'
|| reader.read(10) != '4'
|| reader.read(10) != ';') {
return null;
}
int idx = 0;
int c;
while (true) {
c = reader.read(10);
if (c >= '0' && c <= '9') {
idx = idx * 10 + (c - '0');
} else if (c == ';') {
break;
} else {
return null;
}
}
if (idx > 255) {
return null;
}
if (reader.read(10) != 'r'
|| reader.read(10) != 'g'
|| reader.read(10) != 'b'
|| reader.read(10) != ':') {
return null;
}
StringBuilder sb = new StringBuilder(16);
List<String> rgb = new ArrayList<>();
while (true) {
c = reader.read(10);
if (c == '\007') {
rgb.add(sb.toString());
break;
} else if (c == '\033') {
c = reader.read(10);
if (c == '\\') {
rgb.add(sb.toString());
break;
} else {
return null;
}
} else if (c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
sb.append((char) c);
} else if (c == '/') {
rgb.add(sb.toString());
sb.setLength(0);
}
}
if (rgb.size() != 3) {
return null;
}
double r = Integer.parseInt(rgb.get(0), 16) / ((1 << (4 * rgb.get(0).length())) - 1.0);
double g = Integer.parseInt(rgb.get(1), 16) / ((1 << (4 * rgb.get(1).length())) - 1.0);
double b = Integer.parseInt(rgb.get(2), 16) / ((1 << (4 * rgb.get(2).length())) - 1.0);
palette[idx] = (int)((Math.round(r * 255) << 16) + (Math.round(g * 255) << 8) + Math.round(b * 255));
black &= palette[idx] == 0;
}
if (black) {
break;
}
}
int max = 256;
while (max > 0 && palette[--max] == 0);
return Arrays.copyOfRange(palette, 0, max + 1);
}
}

View File

@ -25,42 +25,92 @@ public class Colors {
* Default 256 colors palette
*/
public static final int[] DEFAULT_COLORS_256 = {
// 16 ansi
0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xc0c0c0,
0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff,
0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f,
0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af,
0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f,
0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af,
0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f,
0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af,
0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f,
0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af,
0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff,
0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f,
0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af,
0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff,
0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f,
0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af,
0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f,
0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af,
0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f,
0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af,
0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff,
0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f,
0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af,
0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
// 6x6x6 color cube
0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff,
0x005f00, 0x005f5f, 0x005f87, 0x005faf, 0x005fd7, 0x005fff,
0x008700, 0x00875f, 0x008787, 0x0087af, 0x0087d7, 0x0087ff,
0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff,
0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff,
0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff,
0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff,
0x5faf00, 0x5faf5f, 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff,
0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, 0x5fd7d7, 0x5fd7ff,
0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff,
0x875f00, 0x875f5f, 0x875f87, 0x875faf, 0x875fd7, 0x875fff,
0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff,
0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff,
0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff,
0x87ff00, 0x87ff5f, 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff,
0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, 0xaf00d7, 0xaf00ff,
0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff,
0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff,
0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff,
0xafd700, 0xafd75f, 0xafd787, 0xafd7af, 0xafd7d7, 0xafd7ff,
0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff,
0xd75f00, 0xd75f5f, 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff,
0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7, 0xd787ff,
0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff,
0xd7ff00, 0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff,
0xff0000, 0xff005f, 0xff0087, 0xff00af, 0xff00d7, 0xff00ff,
0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff,
0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff,
0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff,
0xffd700, 0xffd75f, 0xffd787, 0xffd7af, 0xffd7d7, 0xffd7ff,
0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
// 24 grey ramp
0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e,
0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e,
0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee,
};
/**
* Default 88 colors palette
*/
public static final int[] DEFAULT_COLORS_88 = {
// 16 ansi
0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xc0c0c0,
0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff,
// 4x4x4 color cube
0x000000, 0x00008b, 0x0000cd, 0x0000ff,
0x008b00, 0x008b8b, 0x008bcd, 0x008bff,
0x00cd00, 0x00cd8b, 0x00cdcd, 0x00cdff,
0x00ff00, 0x00ff8b, 0x00ffcd, 0x00ffff,
0x8b0000, 0x8b008b, 0x8b00cd, 0x8b00ff,
0x8b8b00, 0x8b8b8b, 0x8b8bcd, 0x8b8bff,
0x8bcd00, 0x8bcd8b, 0x8bcdcd, 0x8bcdff,
0x8bff00, 0x8bff8b, 0x8bffcd, 0x8bffff,
0xcd0000, 0xcd008b, 0xcd00cd, 0xcd00ff,
0xcd8b00, 0xcd8b8b, 0xcd8bcd, 0xcd8bff,
0xcdcd00, 0xcdcd8b, 0xcdcdcd, 0xcdcdff,
0xcdff00, 0xcdff8b, 0xcdffcd, 0xcdffff,
0xff0000, 0xff008b, 0xff00cd, 0xff00ff,
0xff8b00, 0xff8b8b, 0xff8bcd, 0xff8bff,
0xffcd00, 0xffcd8b, 0xffcdcd, 0xffcdff,
0xffff00, 0xffff8b, 0xffffcd, 0xffffff,
// 8 grey ramp
0x2e2e2e, 0x5c5c5c, 0x737373, 0x8b8b8b, 0xa2a2a2, 0xb9b9b9, 0xd0d0d0, 0xe7e7e7,
};
/** D50 illuminant for CAM color spaces */
public static final double[] D50 = new double[] { 96.422f, 100.0f, 82.521f };
/** D65 illuminant for CAM color spaces */
@ -74,11 +124,11 @@ public class Colors {
public static final double[] darkSurrounding = new double[] { 0.8, 0.525, 0.8 };
/** sRGB encoding environment */
public static final double[] sRGB_encoding_environment = vc(D50, 64.0, 64/5, dimSurrounding);
public static final double[] sRGB_encoding_environment = vc(D50, 64.0, 64.0/5, dimSurrounding);
/** sRGB typical environment */
public static final double[] sRGB_typical_environment = vc(D50, 200.0, 200/5, averageSurrounding);
public static final double[] sRGB_typical_environment = vc(D50, 200.0, 200.0/5, averageSurrounding);
/** Adobe RGB environment */
public static final double[] AdobeRGB_environment = vc(D65, 160.0, 160/5, averageSurrounding);
public static final double[] AdobeRGB_environment = vc(D65, 160.0, 160.0/5, averageSurrounding);
private static int[] COLORS_256 = DEFAULT_COLORS_256;
@ -130,15 +180,16 @@ public class Colors {
return roundColor((r << 16) + (g << 8) + b, COLORS_256, max, (String) null);
}
private static int roundColor(int color, int[] colors, int max, String dist) {
static int roundColor(int color, int[] colors, int max, String dist) {
return roundColor(color, colors, max, getDistance(dist));
}
private interface Distance {
@FunctionalInterface
interface Distance {
double compute(int c1, int c2);
}
private static int roundColor(int color, int[] colors, int max, Distance distance) {
static int roundColor(int color, int[] colors, int max, Distance distance) {
double best_distance = Integer.MAX_VALUE;
int best_index = Integer.MAX_VALUE;
for (int idx = 0; idx < max; idx++) {
@ -151,7 +202,7 @@ public class Colors {
return best_index;
}
private static Distance getDistance(String dist) {
static Distance getDistance(String dist) {
if (dist == null) {
dist = System.getProperty(PROP_COLOR_DISTANCE, "cie76");
}

View File

@ -352,11 +352,82 @@ public final class Curses {
out.append(Integer.toString(toInteger(stack.pop())));
break;
default:
throw new UnsupportedOperationException();
if (ch == ':') {
ch = str.charAt(index++);
}
boolean alternate = false;
boolean left = false;
boolean space = false;
boolean plus = false;
int width = 0;
int prec = -1;
int cnv;
while ("-+# ".indexOf(ch) >= 0) {
switch (ch) {
case '-': left = true; break;
case '+': plus = true; break;
case '#': alternate = true; break;
case ' ': space = true; break;
}
ch = str.charAt(index++);
}
if ("123456789".indexOf(ch) >= 0) {
do {
width = width * 10 + (ch - '0');
ch = str.charAt(index++);
} while ("0123456789".indexOf(ch) >= 0);
}
if (ch == '.') {
prec = 0;
ch = str.charAt(index++);
}
if ("0123456789".indexOf(ch) >= 0) {
do {
prec = prec * 10 + (ch - '0');
ch = str.charAt(index++);
} while ("0123456789".indexOf(ch) >= 0);
}
if ("cdoxXs".indexOf(ch) < 0) {
throw new IllegalArgumentException();
}
cnv = ch;
if (exec) {
String res;
if (cnv == 's') {
res = (String) stack.pop();
if (prec >= 0) {
res = res.substring(0, prec);
}
} else {
int p = toInteger(stack.pop());
StringBuilder fmt = new StringBuilder(16);
fmt.append('%');
if (alternate) {
fmt.append('#');
}
if (plus) {
fmt.append('+');
}
if (space) {
fmt.append(' ');
}
if (prec >= 0) {
fmt.append('0');
fmt.append(prec);
}
fmt.append((char) cnv);
res = String.format(fmt.toString(), p);
}
if (width > res.length()) {
res = String.format("%" + (left ? "-" : "") + width + "s", res);
}
out.append(res);
}
break;
}
break;
case '$':
if (str.charAt(index) == '<') {
if (index < length && str.charAt(index) == '<') {
// We don't honour delays, just skip
int nb = 0;
while ((ch = str.charAt(++index)) != '>') {

View File

@ -72,6 +72,10 @@ public class Display {
public void setDelayLineWrap(boolean v) { delayLineWrap = v; }
public void resize(int rows, int columns) {
if (rows == 0 || columns == 0) {
columns = Integer.MAX_VALUE - 1;
rows = 1;
}
if (this.rows != rows || this.columns != columns) {
this.rows = rows;
this.columns = columns;

View File

@ -619,7 +619,7 @@ public final class InfoCmp {
static {
for (String s : Arrays.asList("dumb", "dumb-color", "ansi", "xterm", "xterm-256color",
"windows", "windows-256color", "windows-conemu", "windows-vtp",
"screen", "screen-256color")) {
"screen", "screen-256color", "rxvt-unicode", "rxvt-unicode-256color", "rxvt-basic", "rxvt")) {
setDefaultInfoCmp(s, () -> loadDefaultInfoCmp(s));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2016, the original author or authors.
* Copyright (c) 2002-2020, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -22,8 +22,9 @@ import java.util.function.Supplier;
* @author <a href="mailto:gnodet@gmail.com">Guillaume Nodet</a>
* @since 2.0
*/
public final class Log
{
public final class Log {
// private static final Logger logger = Logger.getLogger("org.jline");
public static void trace(final Object... messages) {
// log(Level.FINEST, messages);
}
@ -110,7 +111,6 @@ public final class Log
// }
//
// static void logr(final Level level, final Supplier<LogRecord> record) {
// Logger logger = Logger.getLogger("org.jline");
// if (logger.isLoggable(level)) {
// // inform record of the logger-name
// LogRecord tmp = record.get();
@ -120,7 +120,6 @@ public final class Log
// }
//
// static boolean isEnabled(Level level) {
// Logger logger = Logger.getLogger("org.jline");
// return logger.isLoggable(level);
// }

View File

@ -106,6 +106,20 @@ public class NonBlockingPumpInputStream extends NonBlockingInputStream {
return res;
}
@Override
public synchronized int readBuffered(byte[] b) throws IOException {
checkIoException();
int res = wait(readBuffer, 0L);
if (res >= 0) {
res = 0;
while (res < b.length && readBuffer.hasRemaining()) {
b[res++] = (byte) (readBuffer.get() & 0x00FF);
}
}
rewind(readBuffer, writeBuffer);
return res;
}
public synchronized void setIoException(IOException exception) {
this.ioException = exception;
notifyAll();

View File

@ -74,7 +74,11 @@ public class NonBlockingPumpReader extends NonBlockingReader {
// Blocks until more input is available or the reader is closed.
if (!closed && count == 0) {
try {
notEmpty.await(timeout, TimeUnit.MILLISECONDS);
if (timeout > 0L) {
notEmpty.await(timeout, TimeUnit.MILLISECONDS);
} else {
notEmpty.await();
}
} catch (InterruptedException e) {
throw (IOException) new InterruptedIOException().initCause(e);
}

View File

@ -183,7 +183,7 @@ public class PumpReader extends Reader {
}
@Override
public int read(CharBuffer target) throws IOException {
public synchronized int read(CharBuffer target) throws IOException {
if (!target.hasRemaining()) {
return 0;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2016, the original author or authors.
* Copyright (c) 2002-2020, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
@ -9,6 +9,7 @@
package jdk.internal.org.jline.utils;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.Objects;
@ -91,8 +92,12 @@ public final class Signals {
Object signal;
try {
signal = constructor.newInstance(name);
} catch (IllegalArgumentException e) {
Log.trace(() -> "Ignoring unsupported signal " + name);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof IllegalArgumentException) {
Log.trace(() -> "Ignoring unsupported signal " + name);
} else {
Log.debug("Error registering handler for signal ", name, e);
}
return null;
}
Class<?> signalHandlerClass = Class.forName("sun.misc.SignalHandler");

View File

@ -32,6 +32,38 @@ public class StyleResolver {
this.source = requireNonNull(source);
}
/**
* Returns the RGB color for the given name.
* <p>
* Bright color can be specified with: {@code !<color>} or {@code bright-<color>}.
* <p>
* Full xterm256 color can be specified with: {@code ~<color>}.
* RGB colors can be specified with: {@code x<rgb>} or {@code #<rgb>} where {@code rgb} is
* a 24 bits hexadecimal color.
*
* @param name the name of the color
* @return color code, or {@code null} if unable to determine.
*/
private static Integer colorRgb(String name) {
name = name.toLowerCase(Locale.US);
// check hexadecimal color
if (name.charAt(0) == 'x' || name.charAt(0) == '#') {
try {
return Integer.parseInt(name.substring(1), 16);
} catch (NumberFormatException e) {
// log.warning("Invalid hexadecimal color: " + name);
return null;
}
} else {
// load indexed color
Integer color = color(name);
if (color != null && color != -1) {
color = Colors.DEFAULT_COLORS_256[color];
}
return color;
}
}
/**
* Returns the color identifier for the given name.
* <p>
@ -44,21 +76,20 @@ public class StyleResolver {
*/
private static Integer color(String name) {
int flags = 0;
name = name.toLowerCase(Locale.US);
if (name.equals("default")) {
return -1;
}
// extract bright flag from color name
if (name.charAt(0) == '!') {
name = name.substring(1, name.length());
else if (name.charAt(0) == '!') {
name = name.substring(1);
flags = BRIGHT;
} else if (name.startsWith("bright-")) {
name = name.substring(7, name.length());
name = name.substring(7);
flags = BRIGHT;
} else if (name.charAt(0) == '~') {
name = name.substring(1);
try {
// TODO: if the palette is not the default one, should be
// TODO: translate into 24-bits first and let the #toAnsi() call
// TODO: round with the current palette ?
name = name.substring(1, name.length());
return Colors.rgbColor(name);
} catch (IllegalArgumentException e) {
// log.warning("Invalid style-color name: " + name);
@ -297,25 +328,51 @@ public class StyleResolver {
String colorName = parts[1].trim();
// resolve the color-name
Integer color = color(colorName);
if (color == null) {
// log.warning("Invalid color-name: " + colorName);
} else {
// resolve and apply color-mode
switch (colorMode.toLowerCase(Locale.US)) {
case "foreground":
case "fg":
case "f":
return style.foreground(color);
Integer color;
// resolve and apply color-mode
switch (colorMode.toLowerCase(Locale.US)) {
case "foreground":
case "fg":
case "f":
color = color(colorName);
if (color == null) {
// log.warning("Invalid color-name: " + colorName);
break;
}
return color >= 0 ? style.foreground(color) : style.foregroundDefault();
case "background":
case "bg":
case "b":
return style.background(color);
case "background":
case "bg":
case "b":
color = color(colorName);
if (color == null) {
// log.warning("Invalid color-name: " + colorName);
break;
}
return color >= 0 ? style.background(color) : style.backgroundDefault();
default:
// log.warning("Invalid color-mode: " + colorMode);
}
case "foreground-rgb":
case "fg-rgb":
case "f-rgb":
color = colorRgb(colorName);
if (color == null) {
// log.warning("Invalid color-name: " + colorName);
break;
}
return color >= 0 ? style.foregroundRgb(color) : style.foregroundDefault();
case "background-rgb":
case "bg-rgb":
case "b-rgb":
color = colorRgb(colorName);
if (color == null) {
// log.warning("Invalid color-name: " + colorName);
break;
}
return color >= 0 ? style.backgroundRgb(color) : style.backgroundDefault();
default:
// log.warning("Invalid color-mode: " + colorMode);
}
return style;
}

View File

@ -0,0 +1,41 @@
# Reconstructed via infocmp from file: /lib/terminfo/r/rxvt-basic
rxvt-basic|rxvt-m|rxvt terminal base (X Window System),
am, bce, eo, km, mir, msgr, xenl, xon,
cols#80, it#8, lines#24,
acsc=``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
bel=^G, blink=\E[5m, bold=\E[1m, civis=\E[?25l,
clear=\E[H\E[2J, cnorm=\E[?25h, cr=\r,
csr=\E[%i%p1%d;%p2%dr, cub=\E[%p1%dD, cub1=^H,
cud=\E[%p1%dB, cud1=\n, cuf=\E[%p1%dC, cuf1=\E[C,
cup=\E[%i%p1%d;%p2%dH, cuu=\E[%p1%dA, cuu1=\E[A,
dl=\E[%p1%dM, dl1=\E[M, ed=\E[J, el=\E[K, el1=\E[1K,
enacs=\E(B\E)0, flash=\E[?5h\E[?5l, home=\E[H,
hpa=\E[%i%p1%dG, ht=^I, hts=\EH, ich=\E[%p1%d@, ich1=\E[@,
il=\E[%p1%dL, il1=\E[L, ind=\n, is1=\E[?47l\E=\E[?1l,
is2=\E[r\E[m\E[2J\E[H\E[?7h\E[?1;3;4;6l\E[4l,
kDC=\E[3$, kEND=\E[8$, kHOM=\E[7$, kLFT=\E[d, kNXT=\E[6$,
kPRV=\E[5$, kRIT=\E[c, ka1=\EOw, ka3=\EOy, kb2=\EOu, kbs=^?,
kc1=\EOq, kc3=\EOs, kcbt=\E[Z, kcub1=\E[D, kcud1=\E[B,
kcuf1=\E[C, kcuu1=\E[A, kdch1=\E[3~, kel=\E[8\^,
kend=\E[8~, kent=\EOM, kf1=\E[11~, kf10=\E[21~,
kf11=\E[23~, kf12=\E[24~, kf13=\E[25~, kf14=\E[26~,
kf15=\E[28~, kf16=\E[29~, kf17=\E[31~, kf18=\E[32~,
kf19=\E[33~, kf2=\E[12~, kf20=\E[34~, kf21=\E[23$,
kf22=\E[24$, kf23=\E[11\^, kf24=\E[12\^, kf25=\E[13\^,
kf26=\E[14\^, kf27=\E[15\^, kf28=\E[17\^, kf29=\E[18\^,
kf3=\E[13~, kf30=\E[19\^, kf31=\E[20\^, kf32=\E[21\^,
kf33=\E[23\^, kf34=\E[24\^, kf35=\E[25\^, kf36=\E[26\^,
kf37=\E[28\^, kf38=\E[29\^, kf39=\E[31\^, kf4=\E[14~,
kf40=\E[32\^, kf41=\E[33\^, kf42=\E[34\^, kf43=\E[23@,
kf44=\E[24@, kf5=\E[15~, kf6=\E[17~, kf7=\E[18~,
kf8=\E[19~, kf9=\E[20~, kfnd=\E[1~, khome=\E[7~,
kich1=\E[2~, kmous=\E[M, knp=\E[6~, kpp=\E[5~, kslt=\E[4~,
rc=\E8, rev=\E[7m, ri=\EM, rmacs=^O, rmcup=\E[2J\E[?47l\E8,
rmir=\E[4l, rmkx=\E>, rmso=\E[27m, rmul=\E[24m,
rs1=\E>\E[1;3;4;5;6l\E[?7h\E[m\E[r\E[2J\E[H,
rs2=\E[r\E[m\E[2J\E[H\E[?7h\E[?1;3;4;6l\E[4l\E=\E[?1000l\E[?25h,
s0ds=\E(B, s1ds=\E(0, sc=\E7,
sgr=\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;m%?%p9%t\016%e\017%;,
sgr0=\E[0m\017, smacs=^N, smcup=\E7\E[?47h, smir=\E[4h,
smkx=\E=, smso=\E[7m, smul=\E[4m, tbc=\E[3g,
vpa=\E[%i%p1%dd,

View File

@ -0,0 +1,44 @@
# Reconstructed via infocmp from file: /lib/terminfo/r/rxvt-unicode-256color
rxvt-unicode-256color|rxvt-unicode terminal with 256 colors (X Window System),
am, bce, bw, ccc, eo, hs, km, mc5i, mir, msgr, npc, xenl, xon,
btns#5, colors#0x100, cols#80, it#8, lines#24, lm#0, ncv#0,
pairs#0x7fff,
acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
bel=^G, blink=\E[5m, bold=\E[1m, civis=\E[?25l,
clear=\E[H\E[2J, cnorm=\E[?12l\E[?25h, cr=\r,
csr=\E[%i%p1%d;%p2%dr, cub=\E[%p1%dD, cub1=^H,
cud=\E[%p1%dB, cud1=\n, cuf=\E[%p1%dC, cuf1=\E[C,
cup=\E[%i%p1%d;%p2%dH, cuu=\E[%p1%dA, cuu1=\E[A,
cvvis=\E[?12;25h, dch=\E[%p1%dP, dch1=\E[P, dl=\E[%p1%dM,
dl1=\E[M, dsl=\E]2;\007, ech=\E[%p1%dX, ed=\E[J, el=\E[K,
el1=\E[1K, enacs=, flash=\E[?5h$<20/>\E[?5l, fsl=^G,
home=\E[H, hpa=\E[%i%p1%dG, ht=^I, hts=\EH, ich=\E[%p1%d@,
ich1=\E[@, il=\E[%p1%dL, il1=\E[L, ind=\n, indn=\E[%p1%dS,
initc=\E]4;%p1%d;rgb\:%p2%{65535}%*%{1000}%/%4.4X/%p3%{65535}%*%{1000}%/%4.4X/%p4%{65535}%*%{1000}%/%4.4X\E\\,
is1=\E[!p,
is2=\E[r\E[m\E[2J\E[?7;25h\E[?1;3;4;5;6;9;66;1000;1001;1049l\E[4l,
kDC=\E[3$, kEND=\E[8$, kFND=\E[1$, kHOM=\E[7$, kIC=\E[2$,
kLFT=\E[d, kNXT=\E[6$, kPRV=\E[5$, kRIT=\E[c, ka1=\EOw,
ka3=\EOy, kb2=\EOu, kbs=^?, kc1=\EOq, kc3=\EOs, kcbt=\E[Z,
kcub1=\E[D, kcud1=\E[B, kcuf1=\E[C, kcuu1=\E[A,
kdch1=\E[3~, kel=\E[8\^, kend=\E[8~, kent=\EOM, kf1=\E[11~,
kf10=\E[21~, kf11=\E[23~, kf12=\E[24~, kf13=\E[25~,
kf14=\E[26~, kf15=\E[28~, kf16=\E[29~, kf17=\E[31~,
kf18=\E[32~, kf19=\E[33~, kf2=\E[12~, kf20=\E[34~,
kf3=\E[13~, kf4=\E[14~, kf5=\E[15~, kf6=\E[17~, kf7=\E[18~,
kf8=\E[19~, kf9=\E[20~, kfnd=\E[1~, khome=\E[7~,
kich1=\E[2~, kmous=\E[M, knp=\E[6~, kpp=\E[5~, kslt=\E[4~,
mc0=\E[i, mc4=\E[4i, mc5=\E[5i, op=\E[39;49m, rc=\E8,
rev=\E[7m, ri=\EM, rin=\E[%p1%dT, ritm=\E[23m, rmacs=\E(B,
rmam=\E[?7l, rmcup=\E[r\E[?1049l, rmir=\E[4l, rmkx=\E>,
rmso=\E[27m, rmul=\E[24m, rs1=\Ec,
rs2=\E[r\E[m\E[?7;25h\E[?1;3;4;5;6;9;66;1000;1001;1049l\E[4l,
s0ds=\E(B, s1ds=\E(0, s2ds=\E*B, s3ds=\E+B, sc=\E7,
setab=\E[48;5;%p1%dm, setaf=\E[38;5;%p1%dm,
setb=%?%p1%{7}%>%t\E[48;5;%p1%dm%e\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m%;,
setf=%?%p1%{7}%>%t\E[38;5;%p1%dm%e\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m%;,
sgr=\E[%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m%?%p9%t\E(0%e\E(B%;,
sgr0=\E[m\E(B, sitm=\E[3m, smacs=\E(0, smam=\E[?7h,
smcup=\E[?1049h, smir=\E[4h, smkx=\E=, smso=\E[7m,
smul=\E[4m, tbc=\E[3g, tsl=\E]2;, u6=\E[%i%d;%dR, u7=\E[6n,
u8=\E[?1;2c, u9=\E[c, vpa=\E[%i%p1%dd,

View File

@ -0,0 +1,44 @@
# Reconstructed via infocmp from file: /lib/terminfo/r/rxvt-unicode
rxvt-unicode|rxvt-unicode terminal (X Window System),
am, bce, bw, ccc, eo, hs, km, mc5i, mir, msgr, npc, xenl, xon,
btns#5, colors#88, cols#80, it#8, lines#24, lm#0, ncv#0,
pairs#7744,
acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
bel=^G, blink=\E[5m, bold=\E[1m, civis=\E[?25l,
clear=\E[H\E[2J, cnorm=\E[?12l\E[?25h, cr=\r,
csr=\E[%i%p1%d;%p2%dr, cub=\E[%p1%dD, cub1=^H,
cud=\E[%p1%dB, cud1=\n, cuf=\E[%p1%dC, cuf1=\E[C,
cup=\E[%i%p1%d;%p2%dH, cuu=\E[%p1%dA, cuu1=\E[A,
cvvis=\E[?12;25h, dch=\E[%p1%dP, dch1=\E[P, dl=\E[%p1%dM,
dl1=\E[M, dsl=\E]2;\007, ech=\E[%p1%dX, ed=\E[J, el=\E[K,
el1=\E[1K, enacs=, flash=\E[?5h$<20/>\E[?5l, fsl=^G,
home=\E[H, hpa=\E[%i%p1%dG, ht=^I, hts=\EH, ich=\E[%p1%d@,
ich1=\E[@, il=\E[%p1%dL, il1=\E[L, ind=\n, indn=\E[%p1%dS,
initc=\E]4;%p1%d;rgb\:%p2%{65535}%*%{1000}%/%4.4X/%p3%{65535}%*%{1000}%/%4.4X/%p4%{65535}%*%{1000}%/%4.4X\E\\,
is1=\E[!p,
is2=\E[r\E[m\E[2J\E[?7;25h\E[?1;3;4;5;6;9;66;1000;1001;1049l\E[4l,
kDC=\E[3$, kEND=\E[8$, kFND=\E[1$, kHOM=\E[7$, kIC=\E[2$,
kLFT=\E[d, kNXT=\E[6$, kPRV=\E[5$, kRIT=\E[c, ka1=\EOw,
ka3=\EOy, kb2=\EOu, kbs=^?, kc1=\EOq, kc3=\EOs, kcbt=\E[Z,
kcub1=\E[D, kcud1=\E[B, kcuf1=\E[C, kcuu1=\E[A,
kdch1=\E[3~, kel=\E[8\^, kend=\E[8~, kent=\EOM, kf1=\E[11~,
kf10=\E[21~, kf11=\E[23~, kf12=\E[24~, kf13=\E[25~,
kf14=\E[26~, kf15=\E[28~, kf16=\E[29~, kf17=\E[31~,
kf18=\E[32~, kf19=\E[33~, kf2=\E[12~, kf20=\E[34~,
kf3=\E[13~, kf4=\E[14~, kf5=\E[15~, kf6=\E[17~, kf7=\E[18~,
kf8=\E[19~, kf9=\E[20~, kfnd=\E[1~, khome=\E[7~,
kich1=\E[2~, kmous=\E[M, knp=\E[6~, kpp=\E[5~, kslt=\E[4~,
mc0=\E[i, mc4=\E[4i, mc5=\E[5i, op=\E[39;49m, rc=\E8,
rev=\E[7m, ri=\EM, rin=\E[%p1%dT, ritm=\E[23m, rmacs=\E(B,
rmam=\E[?7l, rmcup=\E[r\E[?1049l, rmir=\E[4l, rmkx=\E>,
rmso=\E[27m, rmul=\E[24m, rs1=\Ec,
rs2=\E[r\E[m\E[?7;25h\E[?1;3;4;5;6;9;66;1000;1001;1049l\E[4l,
s0ds=\E(B, s1ds=\E(0, s2ds=\E*B, s3ds=\E+B, sc=\E7,
setab=\E[48;5;%p1%dm, setaf=\E[38;5;%p1%dm,
setb=%?%p1%{7}%>%t\E[48;5;%p1%dm%e\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m%;,
setf=%?%p1%{7}%>%t\E[38;5;%p1%dm%e\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m%;,
sgr=\E[%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m%?%p9%t\E(0%e\E(B%;,
sgr0=\E[m\E(B, sitm=\E[3m, smacs=\E(0, smam=\E[?7h,
smcup=\E[?1049h, smir=\E[4h, smkx=\E=, smso=\E[7m,
smul=\E[4m, tbc=\E[3g, tsl=\E]2;, u6=\E[%i%d;%dR, u7=\E[6n,
u8=\E[?1;2c, u9=\E[c, vpa=\E[%i%p1%dd,

View File

@ -0,0 +1,43 @@
# Reconstructed via infocmp from file: /lib/terminfo/r/rxvt
rxvt|rxvt terminal emulator (X Window System),
am, bce, eo, km, mir, msgr, xenl, xon,
colors#8, cols#80, it#8, lines#24, ncv@, pairs#64,
acsc=``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
bel=^G, blink=\E[5m, bold=\E[1m, civis=\E[?25l,
clear=\E[H\E[2J, cnorm=\E[?25h, cr=\r,
csr=\E[%i%p1%d;%p2%dr, cub=\E[%p1%dD, cub1=^H,
cud=\E[%p1%dB, cud1=\n, cuf=\E[%p1%dC, cuf1=\E[C,
cup=\E[%i%p1%d;%p2%dH, cuu=\E[%p1%dA, cuu1=\E[A,
dl=\E[%p1%dM, dl1=\E[M, ed=\E[J, el=\E[K, el1=\E[1K,
enacs=\E(B\E)0, flash=\E[?5h\E[?5l, home=\E[H,
hpa=\E[%i%p1%dG, ht=^I, hts=\EH, ich=\E[%p1%d@, ich1=\E[@,
il=\E[%p1%dL, il1=\E[L, ind=\n, is1=\E[?47l\E=\E[?1l,
is2=\E[r\E[m\E[2J\E[H\E[?7h\E[?1;3;4;6l\E[4l,
kDC=\E[3$, kEND=\E[8$, kHOM=\E[7$, kLFT=\E[d, kNXT=\E[6$,
kPRV=\E[5$, kRIT=\E[c, ka1=\EOw, ka3=\EOy, kb2=\EOu, kbs=^?,
kc1=\EOq, kc3=\EOs, kcbt=\E[Z, kcub1=\E[D, kcud1=\E[B,
kcuf1=\E[C, kcuu1=\E[A, kdch1=\E[3~, kel=\E[8\^,
kend=\E[8~, kent=\EOM, kf1=\E[11~, kf10=\E[21~,
kf11=\E[23~, kf12=\E[24~, kf13=\E[25~, kf14=\E[26~,
kf15=\E[28~, kf16=\E[29~, kf17=\E[31~, kf18=\E[32~,
kf19=\E[33~, kf2=\E[12~, kf20=\E[34~, kf21=\E[23$,
kf22=\E[24$, kf23=\E[11\^, kf24=\E[12\^, kf25=\E[13\^,
kf26=\E[14\^, kf27=\E[15\^, kf28=\E[17\^, kf29=\E[18\^,
kf3=\E[13~, kf30=\E[19\^, kf31=\E[20\^, kf32=\E[21\^,
kf33=\E[23\^, kf34=\E[24\^, kf35=\E[25\^, kf36=\E[26\^,
kf37=\E[28\^, kf38=\E[29\^, kf39=\E[31\^, kf4=\E[14~,
kf40=\E[32\^, kf41=\E[33\^, kf42=\E[34\^, kf43=\E[23@,
kf44=\E[24@, kf5=\E[15~, kf6=\E[17~, kf7=\E[18~,
kf8=\E[19~, kf9=\E[20~, kfnd=\E[1~, khome=\E[7~,
kich1=\E[2~, kmous=\E[M, knp=\E[6~, kpp=\E[5~, kslt=\E[4~,
op=\E[39;49m, rc=\E8, rev=\E[7m, ri=\EM, rmacs=^O,
rmcup=\E[2J\E[?47l\E8, rmir=\E[4l, rmkx=\E>, rmso=\E[27m,
rmul=\E[24m,
rs1=\E>\E[1;3;4;5;6l\E[?7h\E[m\E[r\E[2J\E[H,
rs2=\E[r\E[m\E[2J\E[H\E[?7h\E[?1;3;4;6l\E[4l\E=\E[?1000l\E[?25h,
s0ds=\E(B, s1ds=\E(0, sc=\E7, setab=\E[4%p1%dm,
setaf=\E[3%p1%dm,
sgr=\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;m%?%p9%t\016%e\017%;,
sgr0=\E[m\017, smacs=^N, smcup=\E7\E[?47h, smir=\E[4h,
smkx=\E=, smso=\E[7m, smul=\E[4m, tbc=\E[3g,
vpa=\E[%i%p1%dd,

View File

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

View File

@ -14,6 +14,7 @@ import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.terminal.impl.jna.win.JnaWinSysTerminal;
import jdk.internal.org.jline.terminal.spi.JnaSupport;
import jdk.internal.org.jline.terminal.spi.Pty;
import jdk.internal.org.jline.utils.OSUtils;
import java.io.IOException;
import java.io.InputStream;
@ -47,4 +48,30 @@ public class JnaSupportImpl implements JnaSupport {
public Terminal winSysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, int codepage, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, Function<InputStream, InputStream> inputStreamWrapper) throws IOException {
return JnaWinSysTerminal.createTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, paused, inputStreamWrapper);
}
@Override
public boolean isWindowsConsole() {
return JnaWinSysTerminal.isWindowsConsole();
}
@Override
public boolean isConsoleOutput() {
if (OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) {
throw new UnsupportedOperationException();
} else if (OSUtils.IS_WINDOWS) {
return JnaWinSysTerminal.isConsoleOutput();
}
throw new UnsupportedOperationException();
}
@Override
public boolean isConsoleInput() {
if (OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) {
throw new UnsupportedOperationException();
} else if (OSUtils.IS_WINDOWS) {
return JnaWinSysTerminal.isConsoleInput();
}
throw new UnsupportedOperationException();
}
}

View File

@ -70,16 +70,42 @@ public class JnaWinSysTerminal extends AbstractWindowsTerminal {
return terminal;
}
public static boolean isWindowsConsole() {
try {
IntByReference mode = new IntByReference();
Kernel32.INSTANCE.GetConsoleMode(consoleOut, mode);
Kernel32.INSTANCE.GetConsoleMode(consoleIn, mode);
return true;
} catch (LastErrorException e) {
return false;
}
}
public static boolean isConsoleOutput() {
try {
IntByReference mode = new IntByReference();
Kernel32.INSTANCE.GetConsoleMode(consoleOut, mode);
return true;
} catch (LastErrorException e) {
return false;
}
}
public static boolean isConsoleInput() {
try {
IntByReference mode = new IntByReference();
Kernel32.INSTANCE.GetConsoleMode(consoleIn, mode);
return true;
} catch (LastErrorException e) {
return false;
}
}
JnaWinSysTerminal(Writer writer, String name, String type, Charset encoding, int codepage, boolean nativeSignals, SignalHandler signalHandler, Function<InputStream, InputStream> inputStreamWrapper) throws IOException {
super(writer, name, type, encoding, codepage, nativeSignals, signalHandler, inputStreamWrapper);
strings.put(InfoCmp.Capability.key_mouse, "\\E[M");
}
@Override
protected int getConsoleOutputCP() {
return Kernel32.INSTANCE.GetConsoleOutputCP();
}
@Override
protected int getConsoleMode() {
IntByReference mode = new IntByReference();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -57,11 +57,6 @@ public class AbstractWindowsTerminalTest {
}
};
var t = new AbstractWindowsTerminal(out, "test", "vt100", null, -1, false, SignalHandler.SIG_DFL, isWrapper) {
@Override
protected int getConsoleOutputCP() {
throw new UnsupportedOperationException("unexpected.");
}
@Override
protected int getConsoleMode() {
return -1;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -34,6 +34,7 @@ import java.io.StringWriter;
import java.nio.charset.Charset;
import jdk.internal.org.jline.terminal.Size;
import jdk.internal.org.jline.terminal.Terminal.SignalHandler;
import jdk.internal.org.jline.terminal.impl.AbstractWindowsTerminal;
public class KeyConversionTest {
@ -58,11 +59,7 @@ public class KeyConversionTest {
void checkKeyConversion(KeyEvent event, String expected) throws IOException {
StringBuilder result = new StringBuilder();
new AbstractWindowsTerminal(new StringWriter(), "", "windows", Charset.forName("UTF-8"),
0, true, null, in -> in) {
@Override
protected int getConsoleOutputCP() {
throw new UnsupportedOperationException("Not supported yet.");
}
0, true, SignalHandler.SIG_DFL, in -> in) {
@Override
protected int getConsoleMode() {
return 0;