Logo Search packages:      
Sourcecode: jline version File versions  Download package

ArgumentCompletor.java

/**
 *    jline - Java console input library
 *    Copyright (c) 2002, 2003, 2004, 2005, Marc Prud'hommeaux mwp1@cornell.edu
 *    All rights reserved.
 *
 *    Redistribution and use in source and binary forms, with or
 *    without modification, are permitted provided that the following
 *    conditions are met:
 *
 *    Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *    Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer
 *    in the documentation and/or other materials provided with
 *    the distribution.
 *
 *    Neither the name of JLine nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 *    BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 *    AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 *    EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 *    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 *    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 *    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 *    AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 *    IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 *    OF THE POSSIBILITY OF SUCH DAMAGE.
 */
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>
 */
00085 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
       */
00099       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
       */
00111       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
       */
00124       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
       */
00137       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
       */
00151       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.
       */
00163       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.
       */
00173       public boolean getStrict ()
      {
            return this.strict;
      }


00179       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) + 1;

            /**
             *    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>
       */
00257       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>
       */
00290       public static abstract 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
             */
00373             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>
       */
00425       public static class WhitespaceArgumentDelimiter
            extends AbstractArgumentDelimiter
      {
            /**
             *  The character is a delimiter if it is whitespace, and the
             *  preceeding character is not an escape character.
             */
00432             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>
       */
00444       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
             */
00459             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;
            }
      }
}


Generated by  Doxygen 1.6.0   Back to index