/*
* Copyright (c) 2002-2012, 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.
*
* http://www.opensource.org/licenses/bsd-license.php
*/
package jdk.internal.jline.console.completer;
import jdk.internal.jline.internal.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import static jdk.internal.jline.internal.Preconditions.checkNotNull;
A Completer
implementation that invokes a child completer using the appropriate separator argument.
This can be used instead of the individual completers having to know about argument parsing semantics.
Author: Marc Prud'hommeaux, Jason Dillon Since: 2.3
/**
* A {@link Completer} implementation that invokes a child completer using the appropriate <i>separator</i> argument.
* This can be used instead of the individual completers having to know about argument parsing semantics.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.3
*/
public class ArgumentCompleter
implements Completer
{
private final ArgumentDelimiter delimiter;
private final List<Completer> completers = new ArrayList<Completer>();
private boolean strict = true;
Create a new completer with the specified argument delimiter.
Params: - delimiter – The delimiter for parsing arguments
- completers – The embedded completers
/**
* Create a new completer with the specified argument delimiter.
*
* @param delimiter The delimiter for parsing arguments
* @param completers The embedded completers
*/
public ArgumentCompleter(final ArgumentDelimiter delimiter, final Collection<Completer> completers) {
this.delimiter = checkNotNull(delimiter);
checkNotNull(completers);
this.completers.addAll(completers);
}
Create a new completer with the specified argument delimiter.
Params: - delimiter – The delimiter for parsing arguments
- completers – The embedded completers
/**
* Create a new completer with the specified argument delimiter.
*
* @param delimiter The delimiter for parsing arguments
* @param completers The embedded completers
*/
public ArgumentCompleter(final ArgumentDelimiter delimiter, final Completer... completers) {
this(delimiter, Arrays.asList(completers));
}
Create a new completer with the default WhitespaceArgumentDelimiter
. Params: - completers – The embedded completers
/**
* Create a new completer with the default {@link WhitespaceArgumentDelimiter}.
*
* @param completers The embedded completers
*/
public ArgumentCompleter(final Completer... completers) {
this(new WhitespaceArgumentDelimiter(), completers);
}
Create a new completer with the default WhitespaceArgumentDelimiter
. Params: - completers – The embedded completers
/**
* Create a new completer with the default {@link WhitespaceArgumentDelimiter}.
*
* @param completers The embedded completers
*/
public ArgumentCompleter(final List<Completer> completers) {
this(new WhitespaceArgumentDelimiter(), completers);
}
If true, a completion at argument index N will only succeed
if all the completions from 0-(N-1) also succeed.
/**
* If true, a completion at argument index N will only succeed
* if all the completions from 0-(N-1) also succeed.
*/
public void setStrict(final boolean strict) {
this.strict = strict;
}
Returns whether a completion at argument index N will success
if all the completions from arguments 0-(N-1) also succeed.
Returns: True if strict. Since: 2.3
/**
* Returns whether a completion at argument index N will success
* if all the completions from arguments 0-(N-1) also succeed.
*
* @return True if strict.
* @since 2.3
*/
public boolean isStrict() {
return this.strict;
}
Since: 2.3
/**
* @since 2.3
*/
public ArgumentDelimiter getDelimiter() {
return delimiter;
}
Since: 2.3
/**
* @since 2.3
*/
public List<Completer> getCompleters() {
return completers;
}
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
// buffer can be null
checkNotNull(candidates);
ArgumentDelimiter delim = getDelimiter();
ArgumentList list = delim.delimit(buffer, cursor);
int argpos = list.getArgumentPosition();
int argIndex = list.getCursorArgumentIndex();
if (argIndex < 0) {
return -1;
}
List<Completer> completers = getCompleters();
Completer completer;
// if we are beyond the end of the completers, just use the last one
if (argIndex >= completers.size()) {
completer = completers.get(completers.size() - 1);
}
else {
completer = completers.get(argIndex);
}
// ensure that all the previous completers are successful before allowing this completer to pass (only if strict).
for (int i = 0; isStrict() && (i < argIndex); i++) {
Completer sub = completers.get(i >= completers.size() ? (completers.size() - 1) : i);
String[] args = list.getArguments();
String arg = (args == null || i >= args.length) ? "" : args[i];
List<CharSequence> subCandidates = new LinkedList<CharSequence>();
if (sub.complete(arg, arg.length(), subCandidates) == -1) {
return -1;
}
if (subCandidates.size() == 0) {
return -1;
}
}
int ret = completer.complete(list.getCursorArgument(), argpos, candidates);
if (ret == -1) {
return -1;
}
int pos = ret + list.getBufferPosition() - argpos;
// Special case: when completing in the middle of a line, and the area under the cursor is a delimiter,
// then trim any delimiters from the candidates, since we do not need to have an extra delimiter.
//
// E.g., if we have a completion for "foo", and we enter "f bar" into the buffer, and move to after the "f"
// and hit TAB, we want "foo bar" instead of "foo bar".
if ((cursor != buffer.length()) && delim.isDelimiter(buffer, cursor)) {
for (int i = 0; i < candidates.size(); i++) {
CharSequence val = candidates.get(i);
while (val.length() > 0 && delim.isDelimiter(val, val.length() - 1)) {
val = val.subSequence(0, val.length() - 1);
}
candidates.set(i, val);
}
}
Log.trace("Completing ", buffer, " (pos=", cursor, ") with: ", candidates, ": offset=", pos);
return pos;
}
The ArgumentDelimiter
allows custom breaking up of a String
into individual arguments in order to dispatch the arguments to the nested Completer
. Author: Marc Prud'hommeaux
/**
* The {@link ArgumentCompleter.ArgumentDelimiter} allows custom breaking up of a {@link String} into individual
* arguments in order to dispatch the arguments to the nested {@link Completer}.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
*/
public static interface ArgumentDelimiter
{
Break the specified buffer into individual tokens that can be completed on their own.
Params: - buffer – The buffer to split
- pos – The current position of the cursor in the buffer
Returns: The tokens
/**
* Break the specified buffer into individual tokens that can be completed on their own.
*
* @param buffer The buffer to split
* @param pos The current position of the cursor in the buffer
* @return The tokens
*/
ArgumentList delimit(CharSequence buffer, int pos);
Returns true if the specified character is a whitespace parameter.
Params: - buffer – The complete command buffer
- pos – The index of the character in the buffer
Returns: True if the character should be a delimiter
/**
* Returns true if the specified character is a whitespace parameter.
*
* @param buffer The complete command buffer
* @param pos The index of the character in the buffer
* @return True if the character should be a delimiter
*/
boolean isDelimiter(CharSequence buffer, int pos);
}
Abstract implementation of a delimiter that uses the isDelimiter
method to determine if a particular character should be used as a delimiter. Author: Marc Prud'hommeaux
/**
* Abstract implementation of a delimiter that uses the {@link #isDelimiter} method to determine if a particular
* character should be used as a delimiter.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
*/
public abstract static class AbstractArgumentDelimiter
implements ArgumentDelimiter
{
private char[] quoteChars = {'\'', '"'};
private char[] escapeChars = {'\\'};
public void setQuoteChars(final char[] chars) {
this.quoteChars = chars;
}
public char[] getQuoteChars() {
return this.quoteChars;
}
public void setEscapeChars(final char[] chars) {
this.escapeChars = chars;
}
public char[] getEscapeChars() {
return this.escapeChars;
}
public ArgumentList delimit(final CharSequence buffer, final int cursor) {
List<String> args = new LinkedList<String>();
StringBuilder arg = new StringBuilder();
int argpos = -1;
int bindex = -1;
int quoteStart = -1;
for (int i = 0; (buffer != null) && (i < buffer.length()); i++) {
// once we reach the cursor, set the
// position of the selected index
if (i == cursor) {
bindex = args.size();
// the position in the current argument is just the
// length of the current argument
argpos = arg.length();
}
if (quoteStart < 0 && isQuoteChar(buffer, i)) {
// Start a quote block
quoteStart = i;
} else if (quoteStart >= 0) {
// In a quote block
if (buffer.charAt(quoteStart) == buffer.charAt(i) && !isEscaped(buffer, i)) {
// End the block; arg could be empty, but that's fine
args.add(arg.toString());
arg.setLength(0);
quoteStart = -1;
} else if (!isEscapeChar(buffer, i)) {
// Take the next character
arg.append(buffer.charAt(i));
}
} else {
// Not in a quote block
if (isDelimiter(buffer, i)) {
if (arg.length() > 0) {
args.add(arg.toString());
arg.setLength(0); // reset the arg
}
} else if (!isEscapeChar(buffer, i)) {
arg.append(buffer.charAt(i));
}
}
}
if (cursor == buffer.length()) {
bindex = args.size();
// the position in the current argument is just the
// length of the current argument
argpos = arg.length();
}
if (arg.length() > 0) {
args.add(arg.toString());
}
return new ArgumentList(args.toArray(new String[args.size()]), bindex, argpos, cursor);
}
Returns true if the specified character is a whitespace parameter. Check to ensure that the character is not escaped by any of getQuoteChars
, and is not escaped by ant of the getEscapeChars
, and returns true from isDelimiterChar
. Params: - buffer – The complete command buffer
- pos – The index of the character in the buffer
Returns: True if the character should be a delimiter
/**
* Returns true if the specified character is a whitespace parameter. Check to ensure that the character is not
* escaped by any of {@link #getQuoteChars}, and is not escaped by ant of the {@link #getEscapeChars}, and
* returns true from {@link #isDelimiterChar}.
*
* @param buffer The complete command buffer
* @param pos The index of the character in the buffer
* @return True if the character should be a delimiter
*/
public boolean isDelimiter(final CharSequence buffer, final int pos) {
return !isQuoted(buffer, pos) && !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos);
}
public boolean isQuoted(final CharSequence buffer, final int pos) {
return false;
}
public boolean isQuoteChar(final CharSequence buffer, final int pos) {
if (pos < 0) {
return false;
}
for (int i = 0; (quoteChars != null) && (i < quoteChars.length); i++) {
if (buffer.charAt(pos) == quoteChars[i]) {
return !isEscaped(buffer, pos);
}
}
return false;
}
Check if this character is a valid escape char (i.e. one that has not been escaped)
Params: - buffer –
- pos –
Returns:
/**
* Check if this character is a valid escape char (i.e. one that has not been escaped)
*
* @param buffer
* @param pos
* @return
*/
public boolean isEscapeChar(final CharSequence buffer, final int pos) {
if (pos < 0) {
return false;
}
for (int i = 0; (escapeChars != null) && (i < escapeChars.length); i++) {
if (buffer.charAt(pos) == escapeChars[i]) {
return !isEscaped(buffer, pos); // escape escape
}
}
return false;
}
Check if a character is escaped (i.e. if the previous character is an escape)
Params: - buffer –
the buffer to check in
- pos –
the position of the character to check
Returns: true if the character at the specified position in the given buffer is an escape character and the character immediately preceding it is not an
escape character.
/**
* Check if a character is escaped (i.e. if the previous character is an escape)
*
* @param buffer
* the buffer to check in
* @param pos
* the position of the character to check
* @return true if the character at the specified position in the given buffer is an escape character and the character immediately preceding it is not an
* escape character.
*/
public boolean isEscaped(final CharSequence buffer, final int pos) {
if (pos <= 0) {
return false;
}
return isEscapeChar(buffer, pos - 1);
}
Returns true if the character at the specified position if a delimiter. This method will only be called if the character is not enclosed in any of the getQuoteChars
, and is not escaped by ant of the getEscapeChars
. To perform escaping manually, override isDelimiter
instead. /**
* Returns true if the character at the specified position if a delimiter. This method will only be called if
* the character is not enclosed in any of the {@link #getQuoteChars}, and is not escaped by ant of the
* {@link #getEscapeChars}. To perform escaping manually, override {@link #isDelimiter} instead.
*/
public abstract boolean isDelimiterChar(CharSequence buffer, int pos);
}
ArgumentDelimiter
implementation that counts all whitespace (as reported by Character.isWhitespace
) as being a delimiter. Author: Marc Prud'hommeaux
/**
* {@link ArgumentCompleter.ArgumentDelimiter} implementation that counts all whitespace (as reported by
* {@link Character#isWhitespace}) as being a delimiter.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
*/
public static class WhitespaceArgumentDelimiter
extends AbstractArgumentDelimiter
{
The character is a delimiter if it is whitespace, and the
preceding character is not an escape character.
/**
* The character is a delimiter if it is whitespace, and the
* preceding character is not an escape character.
*/
@Override
public boolean isDelimiterChar(final CharSequence buffer, final int pos) {
return Character.isWhitespace(buffer.charAt(pos));
}
}
The result of a delimited buffer.
Author: Marc Prud'hommeaux
/**
* The result of a delimited buffer.
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
*/
public static class ArgumentList
{
private String[] arguments;
private int cursorArgumentIndex;
private int argumentPosition;
private int bufferPosition;
Params: - arguments – The array of tokens
- cursorArgumentIndex – The token index of the cursor
- argumentPosition – The position of the cursor in the current token
- bufferPosition – The position of the cursor in the whole buffer
/**
* @param arguments The array of tokens
* @param cursorArgumentIndex The token index of the cursor
* @param argumentPosition The position of the cursor in the current token
* @param bufferPosition The position of the cursor in the whole buffer
*/
public ArgumentList(final String[] arguments, final int cursorArgumentIndex, final int argumentPosition, final int bufferPosition) {
this.arguments = checkNotNull(arguments);
this.cursorArgumentIndex = cursorArgumentIndex;
this.argumentPosition = argumentPosition;
this.bufferPosition = bufferPosition;
}
public void setCursorArgumentIndex(final int i) {
this.cursorArgumentIndex = i;
}
public int getCursorArgumentIndex() {
return this.cursorArgumentIndex;
}
public String getCursorArgument() {
if ((cursorArgumentIndex < 0) || (cursorArgumentIndex >= arguments.length)) {
return null;
}
return arguments[cursorArgumentIndex];
}
public void setArgumentPosition(final int pos) {
this.argumentPosition = pos;
}
public int getArgumentPosition() {
return this.argumentPosition;
}
public void setArguments(final String[] arguments) {
this.arguments = arguments;
}
public String[] getArguments() {
return this.arguments;
}
public void setBufferPosition(final int pos) {
this.bufferPosition = pos;
}
public int getBufferPosition() {
return this.bufferPosition;
}
}
}