blob: d9d43c2597c0aa0b4456af7b86f906fb73cc416e [file] [log] [blame]
/*
* 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.io.*;
import java.text.MessageFormat;
import java.util.*;
/**
* <p>
* A {@link CompletionHandler} that deals with multiple distinct completions
* by outputting the complete list of possibilities to the console. This
* mimics the behavior of the
* <a href="http://www.gnu.org/directory/readline.html">readline</a>
* library.
* </p>
*
* <strong>TODO:</strong>
* <ul>
* <li>handle quotes and escaped quotes</li>
* <li>enable automatic escaping of whitespace</li>
* </ul>
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
*/
public class CandidateListCompletionHandler implements CompletionHandler {
private static ResourceBundle loc = ResourceBundle.
getBundle(CandidateListCompletionHandler.class.getName());
private boolean eagerNewlines = true;
public void setAlwaysIncludeNewline(boolean eagerNewlines) {
this.eagerNewlines = eagerNewlines;
}
public boolean complete(final ConsoleReader reader, final List candidates,
final int pos) throws IOException {
CursorBuffer buf = reader.getCursorBuffer();
// if there is only one completion, then fill in the buffer
if (candidates.size() == 1) {
String value = candidates.get(0).toString();
// fail if the only candidate is the same as the current buffer
if (value.equals(buf.toString())) {
return false;
}
setBuffer(reader, value, pos);
return true;
} else if (candidates.size() > 1) {
String value = getUnambiguousCompletions(candidates);
String bufString = buf.toString();
setBuffer(reader, value, pos);
}
if (eagerNewlines)
reader.printNewline();
printCandidates(reader, candidates, eagerNewlines);
// redraw the current console buffer
reader.drawLine();
return true;
}
public static void setBuffer(ConsoleReader reader, String value, int offset)
throws IOException {
while ((reader.getCursorBuffer().cursor > offset)
&& reader.backspace()) {
;
}
reader.putString(value);
reader.setCursorPosition(offset + value.length());
}
/**
* Print out the candidates. If the size of the candidates
* is greated than the {@link getAutoprintThreshhold},
* they prompt with aq warning.
*
* @param candidates the list of candidates to print
*/
public static final void printCandidates(ConsoleReader reader,
Collection candidates, boolean eagerNewlines)
throws IOException {
Set distinct = new HashSet(candidates);
if (distinct.size() > reader.getAutoprintThreshhold()) {
if (!eagerNewlines)
reader.printNewline();
reader.printString(MessageFormat.format
(loc.getString("display-candidates"), new Object[] {
new Integer(candidates .size())
}) + " ");
reader.flushConsole();
int c;
String noOpt = loc.getString("display-candidates-no");
String yesOpt = loc.getString("display-candidates-yes");
while ((c = reader.readCharacter(new char[] {
yesOpt.charAt(0), noOpt.charAt(0) })) != -1) {
if (noOpt.startsWith
(new String(new char[] { (char) c }))) {
reader.printNewline();
return;
} else if (yesOpt.startsWith
(new String(new char[] { (char) c }))) {
break;
} else {
reader.beep();
}
}
}
// copy the values and make them distinct, without otherwise
// affecting the ordering. Only do it if the sizes differ.
if (distinct.size() != candidates.size()) {
Collection copy = new ArrayList();
for (Iterator i = candidates.iterator(); i.hasNext();) {
Object next = i.next();
if (!(copy.contains(next))) {
copy.add(next);
}
}
candidates = copy;
}
reader.printNewline();
reader.printColumns(candidates);
}
/**
* Returns a root that matches all the {@link String} elements
* of the specified {@link List}, or null if there are
* no commalities. For example, if the list contains
* <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the
* method will return <i>foob</i>.
*/
private final String getUnambiguousCompletions(final List candidates) {
if ((candidates == null) || (candidates.size() == 0)) {
return null;
}
// convert to an array for speed
String[] strings =
(String[]) candidates.toArray(new String[candidates.size()]);
String first = strings[0];
StringBuffer candidate = new StringBuffer();
for (int i = 0; i < first.length(); i++) {
if (startsWith(first.substring(0, i + 1), strings)) {
candidate.append(first.charAt(i));
} else {
break;
}
}
return candidate.toString();
}
/**
* @return true is all the elements of <i>candidates</i>
* start with <i>starts</i>
*/
private final boolean startsWith(final String starts,
final String[] candidates) {
for (int i = 0; i < candidates.length; i++) {
if (!candidates[i].startsWith(starts)) {
return false;
}
}
return true;
}
}