--- /dev/null 2015-04-26 06:51:08.003313989 -0700 +++ new/jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/CandidateListCompletionHandler.java 2015-07-02 08:21:36.284694611 -0700 @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2002-2012, the original author or authors. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * http://www.opensource.org/licenses/bsd-license.php + */ +package jdk.internal.jline.console.completer; + +import jdk.internal.jline.console.ConsoleReader; +import jdk.internal.jline.console.CursorBuffer; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.Set; + +/** + * 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 + * readline library. + * + * @author Marc Prud'hommeaux + * @author Jason Dillon + * @since 2.3 + */ +public class CandidateListCompletionHandler + implements CompletionHandler +{ + // TODO: handle quotes and escaped quotes && enable automatic escaping of whitespace + + 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) { + CharSequence value = candidates.get(0); + + // 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); + setBuffer(reader, value, pos); + } + + printCandidates(reader, candidates); + + // redraw the current console buffer + reader.drawLine(); + + return true; + } + + public static void setBuffer(final ConsoleReader reader, final CharSequence value, final int offset) throws + IOException + { + while ((reader.getCursorBuffer().cursor > offset) && reader.backspace()) { + // empty + } + + reader.putString(value); + reader.setCursorPosition(offset + value.length()); + } + + /** + * Print out the candidates. If the size of the candidates is greater than the + * {@link ConsoleReader#getAutoprintThreshold}, they prompt with a warning. + * + * @param candidates the list of candidates to print + */ + public static void printCandidates(final ConsoleReader reader, Collection candidates) throws + IOException + { + Set distinct = new HashSet(candidates); + + if (distinct.size() > reader.getAutoprintThreshold()) { + //noinspection StringConcatenation + reader.print(Messages.DISPLAY_CANDIDATES.format(candidates.size())); + reader.flush(); + + int c; + + String noOpt = Messages.DISPLAY_CANDIDATES_NO.format(); + String yesOpt = Messages.DISPLAY_CANDIDATES_YES.format(); + char[] allowed = {yesOpt.charAt(0), noOpt.charAt(0)}; + + while ((c = reader.readCharacter(allowed)) != -1) { + String tmp = new String(new char[]{(char) c}); + + if (noOpt.startsWith(tmp)) { + reader.println(); + return; + } + else if (yesOpt.startsWith(tmp)) { + 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 (CharSequence next : candidates) { + if (!copy.contains(next)) { + copy.add(next); + } + } + + candidates = copy; + } + + reader.println(); + reader.printColumns(candidates); + } + + /** + * Returns a root that matches all the {@link String} elements of the specified {@link List}, + * or null if there are no commonalities. For example, if the list contains + * foobar, foobaz, foobuz, the method will return foob. + */ + private String getUnambiguousCompletions(final List candidates) { + if (candidates == null || candidates.isEmpty()) { + return null; + } + + // convert to an array for speed + String[] strings = candidates.toArray(new String[candidates.size()]); + + String first = strings[0]; + StringBuilder candidate = new StringBuilder(); + + 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 candidates start with starts + */ + private boolean startsWith(final String starts, final String[] candidates) { + for (String candidate : candidates) { + if (!candidate.startsWith(starts)) { + return false; + } + } + + return true; + } + + private static enum Messages + { + DISPLAY_CANDIDATES, + DISPLAY_CANDIDATES_YES, + DISPLAY_CANDIDATES_NO,; + + private static final + ResourceBundle + bundle = + ResourceBundle.getBundle(CandidateListCompletionHandler.class.getName(), Locale.getDefault()); + + public String format(final Object... args) { + if (bundle == null) + return ""; + else + return String.format(bundle.getString(name()), args); + } + } +}