summaryrefslogtreecommitdiff
path: root/src/src/main/java/jline/ArgumentCompletor.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/src/main/java/jline/ArgumentCompletor.java')
-rw-r--r--src/src/main/java/jline/ArgumentCompletor.java439
1 files changed, 439 insertions, 0 deletions
diff --git a/src/src/main/java/jline/ArgumentCompletor.java b/src/src/main/java/jline/ArgumentCompletor.java
new file mode 100644
index 0000000..5493ad8
--- /dev/null
+++ b/src/src/main/java/jline/ArgumentCompletor.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved.
+ *
+ * This software is distributable under the BSD license. See the terms of the
+ * BSD license in the documentation provided with this software.
+ */
+package jline;
+
+import java.util.*;
+
+/**
+ * A {@link Completor} implementation that invokes a child completor
+ * using the appropriate <i>separator</i> argument. This
+ * can be used instead of the individual completors having to
+ * know about argument parsing semantics.
+ * <p>
+ * <strong>Example 1</strong>: Any argument of the command line can
+ * use file completion.
+ * <p>
+ * <pre>
+ * consoleReader.addCompletor (new ArgumentCompletor (
+ * new {@link FileNameCompletor} ()))
+ * </pre>
+ * <p>
+ * <strong>Example 2</strong>: The first argument of the command line
+ * can be completed with any of "foo", "bar", or "baz", and remaining
+ * arguments can be completed with a file name.
+ * <p>
+ * <pre>
+ * consoleReader.addCompletor (new ArgumentCompletor (
+ * new {@link SimpleCompletor} (new String [] { "foo", "bar", "baz"})));
+ * consoleReader.addCompletor (new ArgumentCompletor (
+ * new {@link FileNameCompletor} ()));
+ * </pre>
+ *
+ * <p>
+ * When the argument index is past the last embedded completors, the last
+ * completors is always used. To disable this behavior, have the last
+ * completor be a {@link NullCompletor}. For example:
+ * </p>
+ *
+ * <pre>
+ * consoleReader.addCompletor (new ArgumentCompletor (
+ * new {@link SimpleCompletor} (new String [] { "foo", "bar", "baz"}),
+ * new {@link SimpleCompletor} (new String [] { "xxx", "yyy", "xxx"}),
+ * new {@link NullCompletor}
+ * ));
+ * </pre>
+ * <p>
+ * TODO: handle argument quoting and escape characters
+ * </p>
+ *
+ * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
+ */
+public class ArgumentCompletor implements Completor {
+ final Completor[] completors;
+ final ArgumentDelimiter delim;
+ boolean strict = true;
+
+ /**
+ * Constuctor: create a new completor with the default
+ * argument separator of " ".
+ *
+ * @param completor the embedded completor
+ */
+ public ArgumentCompletor(final Completor completor) {
+ this(new Completor[] {
+ completor
+ });
+ }
+
+ /**
+ * Constuctor: create a new completor with the default
+ * argument separator of " ".
+ *
+ * @param completors the List of completors to use
+ */
+ public ArgumentCompletor(final List completors) {
+ this((Completor[]) completors.toArray(new Completor[completors.size()]));
+ }
+
+ /**
+ * Constuctor: create a new completor with the default
+ * argument separator of " ".
+ *
+ * @param completors the embedded argument completors
+ */
+ public ArgumentCompletor(final Completor[] completors) {
+ this(completors, new WhitespaceArgumentDelimiter());
+ }
+
+ /**
+ * Constuctor: create a new completor with the specified
+ * argument delimiter.
+ *
+ * @param completor the embedded completor
+ * @param delim the delimiter for parsing arguments
+ */
+ public ArgumentCompletor(final Completor completor,
+ final ArgumentDelimiter delim) {
+ this(new Completor[] {
+ completor
+ }, delim);
+ }
+
+ /**
+ * Constuctor: create a new completor with the specified
+ * argument delimiter.
+ *
+ * @param completors the embedded completors
+ * @param delim the delimiter for parsing arguments
+ */
+ public ArgumentCompletor(final Completor[] completors,
+ final ArgumentDelimiter delim) {
+ this.completors = completors;
+ this.delim = delim;
+ }
+
+ /**
+ * 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 succees
+ * if all the completions from arguments 0-(N-1) also succeed.
+ */
+ public boolean getStrict() {
+ return this.strict;
+ }
+
+ public int complete(final String buffer, final int cursor,
+ final List candidates) {
+ ArgumentList list = delim.delimit(buffer, cursor);
+ int argpos = list.getArgumentPosition();
+ int argIndex = list.getCursorArgumentIndex();
+
+ if (argIndex < 0) {
+ return -1;
+ }
+
+ final Completor comp;
+
+ // if we are beyond the end of the completors, just use the last one
+ if (argIndex >= completors.length) {
+ comp = completors[completors.length - 1];
+ } else {
+ comp = completors[argIndex];
+ }
+
+ // ensure that all the previous completors are successful before
+ // allowing this completor to pass (only if strict is true).
+ for (int i = 0; getStrict() && (i < argIndex); i++) {
+ Completor sub =
+ completors[(i >= completors.length) ? (completors.length - 1) : i];
+ String[] args = list.getArguments();
+ String arg = ((args == null) || (i >= args.length)) ? "" : args[i];
+
+ List subCandidates = new LinkedList();
+
+ if (sub.complete(arg, arg.length(), subCandidates) == -1) {
+ return -1;
+ }
+
+ if (subCandidates.size() == 0) {
+ return -1;
+ }
+ }
+
+ int ret = comp.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++) {
+ String val = candidates.get(i).toString();
+
+ while ((val.length() > 0)
+ && delim.isDelimiter(val, val.length() - 1)) {
+ val = val.substring(0, val.length() - 1);
+ }
+
+ candidates.set(i, val);
+ }
+ }
+
+ ConsoleReader.debug("Completing " + buffer + "(pos=" + cursor + ") "
+ + "with: " + candidates + ": offset=" + pos);
+
+ return pos;
+ }
+
+ /**
+ * The {@link ArgumentCompletor.ArgumentDelimiter} allows custom
+ * breaking up of a {@link String} into individual arguments in
+ * order to dispatch the arguments to the nested {@link Completor}.
+ *
+ * @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.
+ *
+ * @param buffer the buffer to split
+ * @param argumentPosition the current position of the
+ * cursor in the buffer
+ * @return the tokens
+ */
+ ArgumentList delimit(String buffer, int argumentPosition);
+
+ /**
+ * 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(String buffer, int pos);
+ }
+
+ /**
+ * 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 = new char[] { '\'', '"' };
+ private char[] escapeChars = new char[] { '\\' };
+
+ public void setQuoteChars(final char[] quoteChars) {
+ this.quoteChars = quoteChars;
+ }
+
+ public char[] getQuoteChars() {
+ return this.quoteChars;
+ }
+
+ public void setEscapeChars(final char[] escapeChars) {
+ this.escapeChars = escapeChars;
+ }
+
+ public char[] getEscapeChars() {
+ return this.escapeChars;
+ }
+
+ public ArgumentList delimit(final String buffer, final int cursor) {
+ List args = new LinkedList();
+ StringBuffer arg = new StringBuffer();
+ int argpos = -1;
+ int bindex = -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 ((i == buffer.length()) || isDelimiter(buffer, i)) {
+ if (arg.length() > 0) {
+ args.add(arg.toString());
+ arg.setLength(0); // reset the arg
+ }
+ } else {
+ arg.append(buffer.charAt(i));
+ }
+ }
+
+ return new ArgumentList((String[]) 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
+ * {@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 String buffer, final int pos) {
+ if (isQuoted(buffer, pos)) {
+ return false;
+ }
+
+ if (isEscaped(buffer, pos)) {
+ return false;
+ }
+
+ return isDelimiterChar(buffer, pos);
+ }
+
+ public boolean isQuoted(final String buffer, final int pos) {
+ return false;
+ }
+
+ public boolean isEscaped(final String 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 - 1); // escape escape
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 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(String buffer, int pos);
+ }
+
+ /**
+ * {@link ArgumentCompletor.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
+ * preceeding character is not an escape character.
+ */
+ public boolean isDelimiterChar(String buffer, int pos) {
+ return Character.isWhitespace(buffer.charAt(pos));
+ }
+ }
+
+ /**
+ * 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;
+
+ /**
+ * @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(String[] arguments, int cursorArgumentIndex,
+ int argumentPosition, int bufferPosition) {
+ this.arguments = arguments;
+ this.cursorArgumentIndex = cursorArgumentIndex;
+ this.argumentPosition = argumentPosition;
+ this.bufferPosition = bufferPosition;
+ }
+
+ public void setCursorArgumentIndex(int cursorArgumentIndex) {
+ this.cursorArgumentIndex = cursorArgumentIndex;
+ }
+
+ public int getCursorArgumentIndex() {
+ return this.cursorArgumentIndex;
+ }
+
+ public String getCursorArgument() {
+ if ((cursorArgumentIndex < 0)
+ || (cursorArgumentIndex >= arguments.length)) {
+ return null;
+ }
+
+ return arguments[cursorArgumentIndex];
+ }
+
+ public void setArgumentPosition(int argumentPosition) {
+ this.argumentPosition = argumentPosition;
+ }
+
+ public int getArgumentPosition() {
+ return this.argumentPosition;
+ }
+
+ public void setArguments(String[] arguments) {
+ this.arguments = arguments;
+ }
+
+ public String[] getArguments() {
+ return this.arguments;
+ }
+
+ public void setBufferPosition(int bufferPosition) {
+ this.bufferPosition = bufferPosition;
+ }
+
+ public int getBufferPosition() {
+ return this.bufferPosition;
+ }
+ }
+}