001: /*
002: * Copyright (c) 2002-2007, 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
031: CompletionHandler {
032: private static ResourceBundle loc = ResourceBundle
033: .getBundle(CandidateListCompletionHandler.class.getName());
034:
035: private boolean eagerNewlines = true;
036:
037: public void setAlwaysIncludeNewline(boolean eagerNewlines) {
038: this .eagerNewlines = eagerNewlines;
039: }
040:
041: public boolean complete(final ConsoleReader reader,
042: final List candidates, final int pos) throws IOException {
043: CursorBuffer buf = reader.getCursorBuffer();
044:
045: // if there is only one completion, then fill in the buffer
046: if (candidates.size() == 1) {
047: String value = candidates.get(0).toString();
048:
049: // fail if the only candidate is the same as the current buffer
050: if (value.equals(buf.toString())) {
051: return false;
052: }
053:
054: setBuffer(reader, value, pos);
055:
056: return true;
057: } else if (candidates.size() > 1) {
058: String value = getUnambiguousCompletions(candidates);
059: String bufString = buf.toString();
060: setBuffer(reader, value, pos);
061: }
062:
063: if (eagerNewlines)
064: reader.printNewline();
065: printCandidates(reader, candidates, eagerNewlines);
066:
067: // redraw the current console buffer
068: reader.drawLine();
069:
070: return true;
071: }
072:
073: public static void setBuffer(ConsoleReader reader, String value,
074: int offset) throws IOException {
075: while ((reader.getCursorBuffer().cursor > offset)
076: && reader.backspace()) {
077: ;
078: }
079:
080: reader.putString(value);
081: reader.setCursorPosition(offset + value.length());
082: }
083:
084: /**
085: * Print out the candidates. If the size of the candidates
086: * is greated than the {@link getAutoprintThreshhold},
087: * they prompt with aq warning.
088: *
089: * @param candidates the list of candidates to print
090: */
091: public static final void printCandidates(ConsoleReader reader,
092: Collection candidates, boolean eagerNewlines)
093: throws IOException {
094: Set distinct = new HashSet(candidates);
095:
096: if (distinct.size() > reader.getAutoprintThreshhold()) {
097: if (!eagerNewlines)
098: reader.printNewline();
099: reader.printString(MessageFormat.format(loc
100: .getString("display-candidates"),
101: new Object[] { new Integer(candidates.size()) })
102: + " ");
103:
104: reader.flushConsole();
105:
106: int c;
107:
108: String noOpt = loc.getString("display-candidates-no");
109: String yesOpt = loc.getString("display-candidates-yes");
110:
111: while ((c = reader.readCharacter(new char[] {
112: yesOpt.charAt(0), noOpt.charAt(0) })) != -1) {
113: if (noOpt
114: .startsWith(new String(new char[] { (char) c }))) {
115: reader.printNewline();
116: return;
117: } else if (yesOpt.startsWith(new String(
118: new char[] { (char) c }))) {
119: break;
120: } else {
121: reader.beep();
122: }
123: }
124: }
125:
126: // copy the values and make them distinct, without otherwise
127: // affecting the ordering. Only do it if the sizes differ.
128: if (distinct.size() != candidates.size()) {
129: Collection copy = new ArrayList();
130:
131: for (Iterator i = candidates.iterator(); i.hasNext();) {
132: Object next = i.next();
133:
134: if (!(copy.contains(next))) {
135: copy.add(next);
136: }
137: }
138:
139: candidates = copy;
140: }
141:
142: reader.printNewline();
143: reader.printColumns(candidates);
144: }
145:
146: /**
147: * Returns a root that matches all the {@link String} elements
148: * of the specified {@link List}, or null if there are
149: * no commalities. For example, if the list contains
150: * <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the
151: * method will return <i>foob</i>.
152: */
153: private final String getUnambiguousCompletions(final List candidates) {
154: if ((candidates == null) || (candidates.size() == 0)) {
155: return null;
156: }
157:
158: // convert to an array for speed
159: String[] strings = (String[]) candidates
160: .toArray(new String[candidates.size()]);
161:
162: String first = strings[0];
163: StringBuffer candidate = new StringBuffer();
164:
165: for (int i = 0; i < first.length(); i++) {
166: if (startsWith(first.substring(0, i + 1), strings)) {
167: candidate.append(first.charAt(i));
168: } else {
169: break;
170: }
171: }
172:
173: return candidate.toString();
174: }
175:
176: /**
177: * @return true is all the elements of <i>candidates</i>
178: * start with <i>starts</i>
179: */
180: private final boolean startsWith(final String starts,
181: final String[] candidates) {
182: for (int i = 0; i < candidates.length; i++) {
183: if (!candidates[i].startsWith(starts)) {
184: return false;
185: }
186: }
187:
188: return true;
189: }
190: }
|