001    /**
002     * jline - Java console input library
003     * Copyright (c) 2002,2003 Marc Prud'hommeaux marc@apocalypse.org
004     *
005     * This library is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU Lesser General Public
007     * License as published by the Free Software Foundation; either
008     * version 2.1 of the License, or (at your option) any later version.
009     *
010     * This library is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013     * Lesser General Public License for more details.
014     *
015     * You should have received a copy of the GNU Lesser General Public
016     * License along with this library; if not, write to the Free Software
017     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018     */
019    package jline;
020    
021    import java.io.*;
022    import java.text.*;
023    import java.util.*;
024    
025    /** 
026     *      <p>
027     *      A {@link CompletionHandler} that deals with multiple distinct completions
028     *      by outputting the complete list of possibilities to the console. This
029     *      mimics the behavior of the
030     *      <a href="http://www.gnu.org/directory/readline.html">readline</a>
031     *      library.
032     *      </p>
033     *
034     *  @author  <a href="mailto:marc@apocalypse.org">Marc Prud'hommeaux</a>
035     */
036    public class CandidateListCompletionHandler
037            implements CompletionHandler
038    {
039            private static ResourceBundle loc = ResourceBundle.getBundle (
040                    CandidateListCompletionHandler.class.getName ());
041    
042    
043            public boolean complete (ConsoleReader reader, List candidates, int pos)
044                    throws IOException
045            {
046                    CursorBuffer buf = reader.getCursorBuffer ();
047    
048                    // if there is only one completion, then fill in the buffer
049                    if (candidates.size () == 1)
050                    {
051                            String value = candidates.get (0).toString ();
052    
053                            // fail if the only candidate is the same as the current buffer
054                            if (value.toString ().equals (buf.toString ()))
055                                    return false;
056                            setBuffer (reader, value, pos);
057                            return true;
058                    }
059                    else if (candidates.size () > 1)
060                    {
061                            String value = getUnambiguousCompletions (candidates);
062                            setBuffer (reader, value, pos);
063                    }
064    
065                    reader.printNewline ();
066                    printCandidates (reader, candidates);
067    
068                    // redraw the current console buffer
069                    reader.drawLine ();
070    
071                    return true;
072            }
073    
074    
075            private static void setBuffer (ConsoleReader reader,
076                    String value, int offset)
077                    throws IOException
078            {
079                    while (reader.getCursorBuffer ().cursor >= offset
080                            && reader.backspace ());
081                    reader.putString (value);
082                    reader.setCursorPosition (offset + value.length ());
083            }
084    
085    
086            /** 
087             *  Print out the candidates. If the size of the candidates
088             *  is greated than the {@link getAutoprintThreshhold},
089             *  they prompt with aq warning.
090             *  
091             *  @param  candidates  the list of candidates to print
092             */
093            private final void printCandidates (ConsoleReader reader,
094                    Collection candidates)
095                    throws IOException
096            {
097                    // copy the values and make them distinct, without otherwise
098                    // affecting the ordering
099                    Collection copy = new LinkedList ();
100                    for (Iterator i = candidates.iterator (); i.hasNext (); )
101                    {
102                            Object next = i.next ();
103                            if (!(copy.contains (next)))
104                                    copy.add (next);
105                    }
106    
107                    candidates = copy;
108    
109                    if (candidates.size () > reader.getAutoprintThreshhold ())
110                    {
111                            reader.printString (MessageFormat.format (
112                                    loc.getString ("display-candidates"),
113                                    new Object [] { new Integer (candidates.size ()) } ));
114    
115                            reader.flushConsole ();
116    
117                            int c;
118                            
119                            while ((c = reader.readCharacter ()) != -1)
120                            {
121                                    if (loc.getString ("display-candidates-no")
122                                            .startsWith (new String (new char [] { (char)c })))
123                                    {
124                                            reader.printNewline ();
125                                            return;
126                                    }
127                                    else if (loc.getString ("display-candidates-yes")
128                                            .startsWith (new String (new char [] { (char)c })))
129                                            break;
130                                    else
131                                            reader.beep ();
132                            }
133                    }
134    
135                    reader.printNewline ();
136                    reader.printColumns (copy);
137            }
138    
139    
140    
141    
142            /** 
143             *  Returns a root that matches all the {@link String} elements
144             *  of the specified {@link List}, or null if there are
145             *  no commalities. For example, if the list contains
146             *  <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the
147             *  method will return <i>foob</i>.
148             */
149            private final String getUnambiguousCompletions (List candidates)
150            {
151                    if (candidates == null || candidates.size () == 0)
152                            return null;
153    
154                    // convert to an array for speed
155                    String [] strings = (String [])candidates.toArray (
156                            new String [candidates.size ()]);
157    
158                    String first = strings [0];
159                    StringBuffer candidate = new StringBuffer ();
160                    for (int i = 0; i < first.length (); i++)
161                    {
162                            if (startsWith (first.substring (0, i + 1), strings))
163                                    candidate.append (first.charAt (i));
164                            else
165                                    break;
166                    }
167    
168                    return candidate.toString ();
169            }
170    
171    
172            /** 
173             *  @return  true is all the elements of <i>candidates</i>
174             *                      start with <i>starts</i>
175             */
176            private final boolean startsWith (String starts, String [] candidates)
177            {
178                    // debug ("startsWith: " + starts);
179                    for (int i = 0; i < candidates.length; i++)
180                    {
181                            if (!candidates [i].startsWith (starts))
182                                    return false;
183                    }
184    
185                    return true;
186            }
187    }
188