View Javadoc

1   /*
2    * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved.
3    *
4    * This software is distributable under the BSD license. See the terms of the
5    * BSD license in the documentation provided with this software.
6    */
7   package jline;
8   
9   import java.io.*;
10  import java.text.MessageFormat;
11  import java.util.*;
12  
13  /***
14   *  <p>
15   *  A {@link CompletionHandler} that deals with multiple distinct completions
16   *  by outputting the complete list of possibilities to the console. This
17   *  mimics the behavior of the
18   *  <a href="http://www.gnu.org/directory/readline.html">readline</a>
19   *  library.
20   *  </p>
21   *
22   *  <strong>TODO:</strong>
23   *  <ul>
24   *        <li>handle quotes and escaped quotes</li>
25   *        <li>enable automatic escaping of whitespace</li>
26   *  </ul>
27   *
28   *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
29   */
30  public class CandidateListCompletionHandler implements CompletionHandler {
31      private static ResourceBundle loc = ResourceBundle.
32          getBundle(CandidateListCompletionHandler.class.getName());
33  
34      private boolean eagerNewlines = true;
35  
36      public void setAlwaysIncludeNewline(boolean eagerNewlines) {
37          this.eagerNewlines = eagerNewlines;
38      }
39  
40      public boolean complete(final ConsoleReader reader, final List candidates,
41                              final int pos) throws IOException {
42          CursorBuffer buf = reader.getCursorBuffer();
43  
44          // if there is only one completion, then fill in the buffer
45          if (candidates.size() == 1) {
46              String value = candidates.get(0).toString();
47  
48              // fail if the only candidate is the same as the current buffer
49              if (value.equals(buf.toString())) {
50                  return false;
51              }
52  
53              setBuffer(reader, value, pos);
54  
55              return true;
56          } else if (candidates.size() > 1) {
57              String value = getUnambiguousCompletions(candidates);
58              String bufString = buf.toString();
59              setBuffer(reader, value, pos);
60          }
61  
62          if (eagerNewlines)
63              reader.printNewline();
64          printCandidates(reader, candidates, eagerNewlines);
65  
66          // redraw the current console buffer
67          reader.drawLine();
68  
69          return true;
70      }
71  
72      public static void setBuffer(ConsoleReader reader, String value, int offset)
73                             throws IOException {
74          while ((reader.getCursorBuffer().cursor > offset)
75                     && reader.backspace()) {
76              ;
77          }
78  
79          reader.putString(value);
80          reader.setCursorPosition(offset + value.length());
81      }
82  
83      /***
84       *  Print out the candidates. If the size of the candidates
85       *  is greated than the {@link getAutoprintThreshhold},
86       *  they prompt with aq warning.
87       *
88       *  @param  candidates  the list of candidates to print
89       */
90      public static final void printCandidates(ConsoleReader reader,
91                                         Collection candidates, boolean eagerNewlines)
92                                  throws IOException {
93          Set distinct = new HashSet(candidates);
94  
95          if (distinct.size() > reader.getAutoprintThreshhold()) {
96              if (!eagerNewlines)
97                  reader.printNewline();
98              reader.printString(MessageFormat.format
99                  (loc.getString("display-candidates"), new Object[] {
100                     new Integer(candidates .size())
101                     }) + " ");
102 
103             reader.flushConsole();
104 
105             int c;
106 
107             String noOpt = loc.getString("display-candidates-no");
108             String yesOpt = loc.getString("display-candidates-yes");
109 
110             while ((c = reader.readCharacter(new char[] {
111                 yesOpt.charAt(0), noOpt.charAt(0) })) != -1) {
112                 if (noOpt.startsWith
113                     (new String(new char[] { (char) c }))) {
114                     reader.printNewline();
115                     return;
116                 } else if (yesOpt.startsWith
117                     (new String(new char[] { (char) c }))) {
118                     break;
119                 } else {
120                     reader.beep();
121                 }
122             }
123         }
124 
125         // copy the values and make them distinct, without otherwise
126         // affecting the ordering. Only do it if the sizes differ.
127         if (distinct.size() != candidates.size()) {
128             Collection copy = new ArrayList();
129 
130             for (Iterator i = candidates.iterator(); i.hasNext();) {
131                 Object next = i.next();
132 
133                 if (!(copy.contains(next))) {
134                     copy.add(next);
135                 }
136             }
137 
138             candidates = copy;
139         }
140 
141         reader.printNewline();
142         reader.printColumns(candidates);
143     }
144 
145     /***
146      *  Returns a root that matches all the {@link String} elements
147      *  of the specified {@link List}, or null if there are
148      *  no commalities. For example, if the list contains
149      *  <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the
150      *  method will return <i>foob</i>.
151      */
152     private final String getUnambiguousCompletions(final List candidates) {
153         if ((candidates == null) || (candidates.size() == 0)) {
154             return null;
155         }
156 
157         // convert to an array for speed
158         String[] strings =
159             (String[]) candidates.toArray(new String[candidates.size()]);
160 
161         String first = strings[0];
162         StringBuffer candidate = new StringBuffer();
163 
164         for (int i = 0; i < first.length(); i++) {
165             if (startsWith(first.substring(0, i + 1), strings)) {
166                 candidate.append(first.charAt(i));
167             } else {
168                 break;
169             }
170         }
171 
172         return candidate.toString();
173     }
174 
175     /***
176      *  @return  true is all the elements of <i>candidates</i>
177      *                          start with <i>starts</i>
178      */
179     private final boolean startsWith(final String starts,
180                                      final String[] candidates) {
181         for (int i = 0; i < candidates.length; i++) {
182             if (!candidates[i].startsWith(starts)) {
183                 return false;
184             }
185         }
186 
187         return true;
188     }
189 }