001 /** 002 * jline - Java console input library 003 * Copyright (c) 2002,2003 Marc Prud'hommeaux marc@apocalypse.org 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:marc@apocalypse.org">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.toString ().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 setBuffer (reader, value, pos); 063 } 064 065 reader.printNewline (); 066 printCandidates (reader, candidates); 067 068 // redraw the current console buffer 069 reader.drawLine (); 070 071 return true; 072 } 073 074 075 private static void setBuffer (ConsoleReader reader, 076 String value, int offset) 077 throws IOException 078 { 079 while (reader.getCursorBuffer ().cursor >= offset 080 && reader.backspace ()); 081 reader.putString (value); 082 reader.setCursorPosition (offset + value.length ()); 083 } 084 085 086 /** 087 * Print out the candidates. If the size of the candidates 088 * is greated than the {@link getAutoprintThreshhold}, 089 * they prompt with aq warning. 090 * 091 * @param candidates the list of candidates to print 092 */ 093 private final void printCandidates (ConsoleReader reader, 094 Collection candidates) 095 throws IOException 096 { 097 // copy the values and make them distinct, without otherwise 098 // affecting the ordering 099 Collection copy = new LinkedList (); 100 for (Iterator i = candidates.iterator (); i.hasNext (); ) 101 { 102 Object next = i.next (); 103 if (!(copy.contains (next))) 104 copy.add (next); 105 } 106 107 candidates = copy; 108 109 if (candidates.size () > reader.getAutoprintThreshhold ()) 110 { 111 reader.printString (MessageFormat.format ( 112 loc.getString ("display-candidates"), 113 new Object [] { new Integer (candidates.size ()) } )); 114 115 reader.flushConsole (); 116 117 int c; 118 119 while ((c = reader.readCharacter ()) != -1) 120 { 121 if (loc.getString ("display-candidates-no") 122 .startsWith (new String (new char [] { (char)c }))) 123 { 124 reader.printNewline (); 125 return; 126 } 127 else if (loc.getString ("display-candidates-yes") 128 .startsWith (new String (new char [] { (char)c }))) 129 break; 130 else 131 reader.beep (); 132 } 133 } 134 135 reader.printNewline (); 136 reader.printColumns (copy); 137 } 138 139 140 141 142 /** 143 * Returns a root that matches all the {@link String} elements 144 * of the specified {@link List}, or null if there are 145 * no commalities. For example, if the list contains 146 * <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the 147 * method will return <i>foob</i>. 148 */ 149 private final String getUnambiguousCompletions (List candidates) 150 { 151 if (candidates == null || candidates.size () == 0) 152 return null; 153 154 // convert to an array for speed 155 String [] strings = (String [])candidates.toArray ( 156 new String [candidates.size ()]); 157 158 String first = strings [0]; 159 StringBuffer candidate = new StringBuffer (); 160 for (int i = 0; i < first.length (); i++) 161 { 162 if (startsWith (first.substring (0, i + 1), strings)) 163 candidate.append (first.charAt (i)); 164 else 165 break; 166 } 167 168 return candidate.toString (); 169 } 170 171 172 /** 173 * @return true is all the elements of <i>candidates</i> 174 * start with <i>starts</i> 175 */ 176 private final boolean startsWith (String starts, String [] candidates) 177 { 178 // debug ("startsWith: " + starts); 179 for (int i = 0; i < candidates.length; i++) 180 { 181 if (!candidates [i].startsWith (starts)) 182 return false; 183 } 184 185 return true; 186 } 187 } 188