001 /*
002 * Copyright (c) 2002-2006, Marc Prud'hommeaux. All rights reserved.
003 *
004 * This software is distributable under the BSD license. See the terms of the
005 * BSD license in the documentation provided with this software.
006 */
007 package jline;
008
009 import java.io.*;
010 import java.text.MessageFormat;
011 import java.util.*;
012
013 /**
014 * <p>
015 * A {@link CompletionHandler} that deals with multiple distinct completions
016 * by outputting the complete list of possibilities to the console. This
017 * mimics the behavior of the
018 * <a href="http://www.gnu.org/directory/readline.html">readline</a>
019 * library.
020 * </p>
021 *
022 * <strong>TODO:</strong>
023 * <ul>
024 * <li>handle quotes and escaped quotes</li>
025 * <li>enable automatic escaping of whitespace</li>
026 * </ul>
027 *
028 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
029 */
030 public class CandidateListCompletionHandler implements CompletionHandler {
031 private static ResourceBundle loc = ResourceBundle.
032 getBundle(CandidateListCompletionHandler.class.getName());
033
034 public boolean complete(final ConsoleReader reader, final List candidates,
035 final int pos) throws IOException {
036 CursorBuffer buf = reader.getCursorBuffer();
037
038 // if there is only one completion, then fill in the buffer
039 if (candidates.size() == 1) {
040 String value = candidates.get(0).toString();
041
042 // fail if the only candidate is the same as the current buffer
043 if (value.equals(buf.toString())) {
044 return false;
045 }
046
047 setBuffer(reader, value, pos);
048
049 return true;
050 } else if (candidates.size() > 1) {
051 String value = getUnambiguousCompletions(candidates);
052 String bufString = buf.toString();
053 setBuffer(reader, value, pos);
054
055 if (bufString.length() - pos != value.length())
056 return true;
057 }
058
059 reader.printNewline();
060 printCandidates(reader, candidates);
061
062 // redraw the current console buffer
063 reader.drawLine();
064
065 return true;
066 }
067
068 private static void setBuffer(ConsoleReader reader, String value, int offset)
069 throws IOException {
070 while ((reader.getCursorBuffer().cursor > offset)
071 && reader.backspace()) {
072 ;
073 }
074
075 reader.putString(value);
076 reader.setCursorPosition(offset + value.length());
077 }
078
079 /**
080 * Print out the candidates. If the size of the candidates
081 * is greated than the {@link getAutoprintThreshhold},
082 * they prompt with aq warning.
083 *
084 * @param candidates the list of candidates to print
085 */
086 private final void printCandidates(ConsoleReader reader,
087 Collection candidates)
088 throws IOException {
089 Set distinct = new HashSet(candidates);
090
091 if (distinct.size() > reader.getAutoprintThreshhold()) {
092 reader.printString(MessageFormat.format
093 (loc.getString("display-candidates"), new Object[] {
094 new Integer(candidates .size())
095 }) + " ");
096
097 reader.flushConsole();
098
099 int c;
100
101 String noOpt = loc.getString("display-candidates-no");
102 String yesOpt = loc.getString("display-candidates-yes");
103
104 while ((c = reader.readCharacter(new char[] {
105 yesOpt.charAt(0), noOpt.charAt(0) })) != -1) {
106 if (noOpt.startsWith
107 (new String(new char[] { (char) c }))) {
108 reader.printNewline();
109 return;
110 } else if (yesOpt.startsWith
111 (new String(new char[] { (char) c }))) {
112 break;
113 } else {
114 reader.beep();
115 }
116 }
117 }
118
119 // copy the values and make them distinct, without otherwise
120 // affecting the ordering. Only do it if the sizes differ.
121 if (distinct.size() != candidates.size()) {
122 Collection copy = new ArrayList();
123
124 for (Iterator i = candidates.iterator(); i.hasNext();) {
125 Object next = i.next();
126
127 if (!(copy.contains(next))) {
128 copy.add(next);
129 }
130 }
131
132 candidates = copy;
133 }
134
135 reader.printNewline();
136 reader.printColumns(candidates);
137 }
138
139 /**
140 * Returns a root that matches all the {@link String} elements
141 * of the specified {@link List}, or null if there are
142 * no commalities. For example, if the list contains
143 * <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the
144 * method will return <i>foob</i>.
145 */
146 private final String getUnambiguousCompletions(final List candidates) {
147 if ((candidates == null) || (candidates.size() == 0)) {
148 return null;
149 }
150
151 // convert to an array for speed
152 String[] strings =
153 (String[]) candidates.toArray(new String[candidates.size()]);
154
155 String first = strings[0];
156 StringBuffer candidate = new StringBuffer();
157
158 for (int i = 0; i < first.length(); i++) {
159 if (startsWith(first.substring(0, i + 1), strings)) {
160 candidate.append(first.charAt(i));
161 } else {
162 break;
163 }
164 }
165
166 return candidate.toString();
167 }
168
169 /**
170 * @return true is all the elements of <i>candidates</i>
171 * start with <i>starts</i>
172 */
173 private final boolean startsWith(final String starts,
174 final String[] candidates) {
175 for (int i = 0; i < candidates.length; i++) {
176 if (!candidates[i].startsWith(starts)) {
177 return false;
178 }
179 }
180
181 return true;
182 }
183 }