001 /** 002 * jline - Java console input library 003 * Copyright (c) 2002-2006, Marc Prud'hommeaux <mwp1@cornell.edu> 004 * All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or 007 * without modification, are permitted provided that the following 008 * conditions are met: 009 * 010 * Redistributions of source code must retain the above copyright 011 * notice, this list of conditions and the following disclaimer. 012 * 013 * Redistributions in binary form must reproduce the above copyright 014 * notice, this list of conditions and the following disclaimer 015 * in the documentation and/or other materials provided with 016 * the distribution. 017 * 018 * Neither the name of JLine nor the names of its contributors 019 * may be used to endorse or promote products derived from this 020 * software without specific prior written permission. 021 * 022 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 023 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 024 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 025 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 026 * EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 027 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 028 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 029 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 030 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 031 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 032 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 033 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 034 * OF THE POSSIBILITY OF SUCH DAMAGE. 035 */ 036 package jline; 037 038 import java.io.*; 039 import java.util.*; 040 import java.text.MessageFormat; 041 042 /** 043 * <p> 044 * A {@link CompletionHandler} that deals with multiple distinct completions 045 * by outputting the complete list of possibilities to the console. This 046 * mimics the behavior of the 047 * <a href="http://www.gnu.org/directory/readline.html">readline</a> 048 * library. 049 * </p> 050 * 051 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 052 */ 053 public class CandidateListCompletionHandler 054 implements CompletionHandler 055 { 056 private static ResourceBundle loc = ResourceBundle.getBundle ( 057 CandidateListCompletionHandler.class.getName ()); 058 059 060 public boolean complete (final ConsoleReader reader, 061 final List candidates, final int pos) 062 throws IOException 063 { 064 CursorBuffer buf = reader.getCursorBuffer (); 065 066 // if there is only one completion, then fill in the buffer 067 if (candidates.size () == 1) 068 { 069 String value = candidates.get (0).toString (); 070 071 // fail if the only candidate is the same as the current buffer 072 if (value.equals (buf.toString ())) 073 return false; 074 setBuffer (reader, value, pos); 075 return true; 076 } 077 else if (candidates.size () > 1) 078 { 079 String value = getUnambiguousCompletions (candidates); 080 String bufString = buf.toString (); 081 setBuffer (reader, value, pos); 082 083 // if we have changed the buffer, then just return withough 084 // printing out all the subsequent candidates 085 if (bufString.length () - pos + 1 != value.length ()) 086 return true; 087 } 088 089 reader.printNewline (); 090 printCandidates (reader, candidates); 091 092 // redraw the current console buffer 093 reader.drawLine (); 094 095 return true; 096 } 097 098 099 private static void setBuffer (ConsoleReader reader, 100 String value, int offset) 101 throws IOException 102 { 103 while (reader.getCursorBuffer ().cursor >= offset 104 && reader.backspace ()); 105 reader.putString (value); 106 reader.setCursorPosition (offset + value.length ()); 107 } 108 109 110 /** 111 * Print out the candidates. If the size of the candidates 112 * is greated than the {@link getAutoprintThreshhold}, 113 * they prompt with aq warning. 114 * 115 * @param candidates the list of candidates to print 116 */ 117 private final void printCandidates (ConsoleReader reader, 118 Collection candidates) 119 throws IOException 120 { 121 Set distinct = new HashSet (candidates); 122 123 if (distinct.size () > reader.getAutoprintThreshhold ()) 124 { 125 reader.printString (MessageFormat.format ( 126 loc.getString ("display-candidates"), 127 new Object [] { new Integer (candidates.size ()) } ) + " "); 128 129 reader.flushConsole (); 130 131 int c; 132 133 String noOpt = loc.getString ("display-candidates-no"); 134 String yesOpt = loc.getString ("display-candidates-yes"); 135 136 while ((c = reader.readCharacter ( 137 new char[] { yesOpt.charAt (0), noOpt.charAt (0) })) != -1) 138 { 139 if (noOpt.startsWith (new String (new char[] {(char)c}))) 140 { 141 reader.printNewline (); 142 return; 143 } 144 else if (yesOpt.startsWith (new String (new char[] {(char)c}))) 145 { 146 break; 147 } 148 else 149 { 150 reader.beep (); 151 } 152 } 153 } 154 155 // copy the values and make them distinct, without otherwise 156 // affecting the ordering. Only do it if the sizes differ. 157 if (distinct.size () != candidates.size ()) 158 { 159 Collection copy = new ArrayList (); 160 for (Iterator i = candidates.iterator (); i.hasNext (); ) 161 { 162 Object next = i.next (); 163 if (!(copy.contains (next))) 164 copy.add (next); 165 } 166 167 candidates = copy; 168 } 169 170 reader.printNewline (); 171 reader.printColumns (candidates); 172 } 173 174 175 176 177 /** 178 * Returns a root that matches all the {@link String} elements 179 * of the specified {@link List}, or null if there are 180 * no commalities. For example, if the list contains 181 * <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the 182 * method will return <i>foob</i>. 183 */ 184 private final String getUnambiguousCompletions (final List candidates) 185 { 186 if (candidates == null || candidates.size () == 0) 187 return null; 188 189 // convert to an array for speed 190 String [] strings = (String [])candidates.toArray ( 191 new String [candidates.size ()]); 192 193 String first = strings [0]; 194 StringBuffer candidate = new StringBuffer (); 195 for (int i = 0; i < first.length (); i++) 196 { 197 if (startsWith (first.substring (0, i + 1), strings)) 198 candidate.append (first.charAt (i)); 199 else 200 break; 201 } 202 203 return candidate.toString (); 204 } 205 206 207 /** 208 * @return true is all the elements of <i>candidates</i> 209 * start with <i>starts</i> 210 */ 211 private final boolean startsWith (final String starts, 212 final String [] candidates) 213 { 214 for (int i = 0; i < candidates.length; i++) 215 { 216 if (!candidates [i].startsWith (starts)) 217 return false; 218 } 219 220 return true; 221 } 222 } 223