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