/*
* 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;
}
}