001 /** 002 * jline - Java console input library 003 * Copyright (c) 2002,2003 Marc Prud'hommeaux mwp1@cornell.edu 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library; if not, write to the Free Software 017 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018 */ 019 package jline; 020 021 import java.io.*; 022 import java.util.*; 023 import java.text.MessageFormat; 024 025 /** 026 * <p> 027 * A {@link CompletionHandler} that deals with multiple distinct completions 028 * by outputting the complete list of possibilities to the console. This 029 * mimics the behavior of the 030 * <a href="http://www.gnu.org/directory/readline.html">readline</a> 031 * library. 032 * </p> 033 * 034 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 035 */ 036 public class CandidateListCompletionHandler 037 implements CompletionHandler 038 { 039 private static ResourceBundle loc = ResourceBundle.getBundle ( 040 CandidateListCompletionHandler.class.getName ()); 041 042 043 public boolean complete (final ConsoleReader reader, 044 final List candidates, final int pos) 045 throws IOException 046 { 047 CursorBuffer buf = reader.getCursorBuffer (); 048 049 // if there is only one completion, then fill in the buffer 050 if (candidates.size () == 1) 051 { 052 String value = candidates.get (0).toString (); 053 054 // fail if the only candidate is the same as the current buffer 055 if (value.equals (buf.toString ())) 056 return false; 057 setBuffer (reader, value, pos); 058 return true; 059 } 060 else if (candidates.size () > 1) 061 { 062 String value = getUnambiguousCompletions (candidates); 063 String bufString = buf.toString (); 064 setBuffer (reader, value, pos); 065 066 // if we have changed the buffer, then just return withough 067 // printing out all the subsequent candidates 068 if (bufString.length () - pos + 1 != value.length ()) 069 return true; 070 } 071 072 reader.printNewline (); 073 printCandidates (reader, candidates); 074 075 // redraw the current console buffer 076 reader.drawLine (); 077 078 return true; 079 } 080 081 082 private static void setBuffer (ConsoleReader reader, 083 String value, int offset) 084 throws IOException 085 { 086 while (reader.getCursorBuffer ().cursor >= offset 087 && reader.backspace ()); 088 reader.putString (value); 089 reader.setCursorPosition (offset + value.length ()); 090 } 091 092 093 /** 094 * Print out the candidates. If the size of the candidates 095 * is greated than the {@link getAutoprintThreshhold}, 096 * they prompt with aq warning. 097 * 098 * @param candidates the list of candidates to print 099 */ 100 private final void printCandidates (ConsoleReader reader, 101 Collection candidates) 102 throws IOException 103 { 104 Set distinct = new HashSet (candidates); 105 106 if (distinct.size () > reader.getAutoprintThreshhold ()) 107 { 108 reader.printString (MessageFormat.format ( 109 loc.getString ("display-candidates"), 110 new Object [] { new Integer (candidates.size ()) } ) + " "); 111 112 reader.flushConsole (); 113 114 int c; 115 116 String noOpt = loc.getString ("display-candidates-no"); 117 String yesOpt = loc.getString ("display-candidates-yes"); 118 119 while ((c = reader.readCharacter ( 120 new char[] { yesOpt.charAt (0), noOpt.charAt (0) })) != -1) 121 { 122 if (noOpt.startsWith (new String (new char[] {(char)c}))) 123 { 124 reader.printNewline (); 125 return; 126 } 127 else if (yesOpt.startsWith (new String (new char[] {(char)c}))) 128 { 129 break; 130 } 131 else 132 { 133 reader.beep (); 134 } 135 } 136 } 137 138 // copy the values and make them distinct, without otherwise 139 // affecting the ordering. Only do it if the sizes differ. 140 if (distinct.size () != candidates.size ()) 141 { 142 Collection copy = new ArrayList (); 143 for (Iterator i = candidates.iterator (); i.hasNext (); ) 144 { 145 Object next = i.next (); 146 if (!(copy.contains (next))) 147 copy.add (next); 148 } 149 150 candidates = copy; 151 } 152 153 reader.printNewline (); 154 reader.printColumns (candidates); 155 } 156 157 158 159 160 /** 161 * Returns a root that matches all the {@link String} elements 162 * of the specified {@link List}, or null if there are 163 * no commalities. For example, if the list contains 164 * <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the 165 * method will return <i>foob</i>. 166 */ 167 private final String getUnambiguousCompletions (final List candidates) 168 { 169 if (candidates == null || candidates.size () == 0) 170 return null; 171 172 // convert to an array for speed 173 String [] strings = (String [])candidates.toArray ( 174 new String [candidates.size ()]); 175 176 String first = strings [0]; 177 StringBuffer candidate = new StringBuffer (); 178 for (int i = 0; i < first.length (); i++) 179 { 180 if (startsWith (first.substring (0, i + 1), strings)) 181 candidate.append (first.charAt (i)); 182 else 183 break; 184 } 185 186 return candidate.toString (); 187 } 188 189 190 /** 191 * @return true is all the elements of <i>candidates</i> 192 * start with <i>starts</i> 193 */ 194 private final boolean startsWith (final String starts, 195 final String [] candidates) 196 { 197 for (int i = 0; i < candidates.length; i++) 198 { 199 if (!candidates [i].startsWith (starts)) 200 return false; 201 } 202 203 return true; 204 } 205 } 206