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