/*
 * 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; } } }