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