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