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