src/jdk.internal.le/share/classes/jdk/internal/jline/console/completer/CandidateListCompletionHandler.java
changeset 53333 fd6de53a0d6e
parent 53332 ab474ef0a0ac
parent 53010 086dfcfc3731
child 53334 b94283cb226b
equal deleted inserted replaced
53332:ab474ef0a0ac 53333:fd6de53a0d6e
     1 /*
       
     2  * Copyright (c) 2002-2016, the original author or authors.
       
     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  * http://www.opensource.org/licenses/bsd-license.php
       
     8  */
       
     9 package jdk.internal.jline.console.completer;
       
    10 
       
    11 import jdk.internal.jline.console.ConsoleReader;
       
    12 import jdk.internal.jline.console.CursorBuffer;
       
    13 import jdk.internal.jline.internal.Ansi;
       
    14 
       
    15 import java.io.IOException;
       
    16 import java.util.ArrayList;
       
    17 import java.util.Collection;
       
    18 import java.util.HashSet;
       
    19 import java.util.List;
       
    20 import java.util.Locale;
       
    21 import java.util.ResourceBundle;
       
    22 import java.util.Set;
       
    23 
       
    24 /**
       
    25  * A {@link CompletionHandler} that deals with multiple distinct completions
       
    26  * by outputting the complete list of possibilities to the console. This
       
    27  * mimics the behavior of the
       
    28  * <a href="http://www.gnu.org/directory/readline.html">readline</a> library.
       
    29  *
       
    30  * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
       
    31  * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
       
    32  * @since 2.3
       
    33  */
       
    34 public class CandidateListCompletionHandler
       
    35     implements CompletionHandler
       
    36 {
       
    37     private boolean printSpaceAfterFullCompletion = true;
       
    38     private boolean stripAnsi;
       
    39 
       
    40     public boolean getPrintSpaceAfterFullCompletion() {
       
    41         return printSpaceAfterFullCompletion;
       
    42     }
       
    43 
       
    44     public void setPrintSpaceAfterFullCompletion(boolean printSpaceAfterFullCompletion) {
       
    45         this.printSpaceAfterFullCompletion = printSpaceAfterFullCompletion;
       
    46     }
       
    47 
       
    48     public boolean isStripAnsi() {
       
    49         return stripAnsi;
       
    50     }
       
    51 
       
    52     public void setStripAnsi(boolean stripAnsi) {
       
    53         this.stripAnsi = stripAnsi;
       
    54     }
       
    55 
       
    56     // TODO: handle quotes and escaped quotes && enable automatic escaping of whitespace
       
    57 
       
    58     public boolean complete(final ConsoleReader reader, final List<CharSequence> candidates, final int pos) throws
       
    59         IOException
       
    60     {
       
    61         CursorBuffer buf = reader.getCursorBuffer();
       
    62 
       
    63         // if there is only one completion, then fill in the buffer
       
    64         if (candidates.size() == 1) {
       
    65             String value = Ansi.stripAnsi(candidates.get(0).toString());
       
    66 
       
    67             if (buf.cursor == buf.buffer.length()
       
    68                     && printSpaceAfterFullCompletion
       
    69                     && !value.endsWith(" ")) {
       
    70                 value += " ";
       
    71             }
       
    72 
       
    73             // fail if the only candidate is the same as the current buffer
       
    74             if (value.equals(buf.toString())) {
       
    75                 return false;
       
    76             }
       
    77 
       
    78             setBuffer(reader, value, pos);
       
    79 
       
    80             return true;
       
    81         }
       
    82         else if (candidates.size() > 1) {
       
    83             String value = getUnambiguousCompletions(candidates);
       
    84             setBuffer(reader, value, pos);
       
    85         }
       
    86 
       
    87         printCandidates(reader, candidates);
       
    88 
       
    89         // redraw the current console buffer
       
    90         reader.drawLine();
       
    91 
       
    92         return true;
       
    93     }
       
    94 
       
    95     public static void setBuffer(final ConsoleReader reader, final CharSequence value, final int offset) throws
       
    96         IOException
       
    97     {
       
    98         while ((reader.getCursorBuffer().cursor > offset) && reader.backspace()) {
       
    99             // empty
       
   100         }
       
   101 
       
   102         reader.putString(value);
       
   103         reader.setCursorPosition(offset + value.length());
       
   104     }
       
   105 
       
   106     /**
       
   107      * Print out the candidates. If the size of the candidates is greater than the
       
   108      * {@link ConsoleReader#getAutoprintThreshold}, they prompt with a warning.
       
   109      *
       
   110      * @param candidates the list of candidates to print
       
   111      */
       
   112     public static void printCandidates(final ConsoleReader reader, Collection<CharSequence> candidates) throws
       
   113         IOException
       
   114     {
       
   115         Set<CharSequence> distinct = new HashSet<CharSequence>(candidates);
       
   116 
       
   117         if (distinct.size() > reader.getAutoprintThreshold()) {
       
   118             //noinspection StringConcatenation
       
   119             reader.println();
       
   120             reader.print(Messages.DISPLAY_CANDIDATES.format(distinct.size()));
       
   121             reader.flush();
       
   122 
       
   123             int c;
       
   124 
       
   125             String noOpt = Messages.DISPLAY_CANDIDATES_NO.format();
       
   126             String yesOpt = Messages.DISPLAY_CANDIDATES_YES.format();
       
   127             char[] allowed = {yesOpt.charAt(0), noOpt.charAt(0)};
       
   128 
       
   129             while ((c = reader.readCharacter(allowed)) != -1) {
       
   130                 String tmp = new String(new char[]{(char) c});
       
   131 
       
   132                 if (noOpt.startsWith(tmp)) {
       
   133                     reader.println();
       
   134                     return;
       
   135                 }
       
   136                 else if (yesOpt.startsWith(tmp)) {
       
   137                     break;
       
   138                 }
       
   139                 else {
       
   140                     reader.beep();
       
   141                 }
       
   142             }
       
   143         }
       
   144 
       
   145         // copy the values and make them distinct, without otherwise affecting the ordering. Only do it if the sizes differ.
       
   146         if (distinct.size() != candidates.size()) {
       
   147             Collection<CharSequence> copy = new ArrayList<CharSequence>();
       
   148 
       
   149             for (CharSequence next : candidates) {
       
   150                 if (!copy.contains(next)) {
       
   151                     copy.add(next);
       
   152                 }
       
   153             }
       
   154 
       
   155             candidates = copy;
       
   156         }
       
   157 
       
   158         reader.println();
       
   159         reader.printColumns(candidates);
       
   160     }
       
   161 
       
   162     /**
       
   163      * Returns a root that matches all the {@link String} elements of the specified {@link List},
       
   164      * or null if there are no commonalities. For example, if the list contains
       
   165      * <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the method will return <i>foob</i>.
       
   166      */
       
   167     private String getUnambiguousCompletions(final List<CharSequence> candidates) {
       
   168         if (candidates == null || candidates.isEmpty()) {
       
   169             return null;
       
   170         }
       
   171 
       
   172         if (candidates.size() == 1) {
       
   173             return candidates.get(0).toString();
       
   174         }
       
   175 
       
   176         // convert to an array for speed
       
   177         String first = null;
       
   178         String[] strings = new String[candidates.size() - 1];
       
   179         for (int i = 0; i < candidates.size(); i++) {
       
   180             String str = candidates.get(i).toString();
       
   181             if (stripAnsi) {
       
   182                 str = Ansi.stripAnsi(str);
       
   183             }
       
   184             if (first == null) {
       
   185                 first = str;
       
   186             } else {
       
   187                 strings[i - 1] = str;
       
   188             }
       
   189         }
       
   190 
       
   191         StringBuilder candidate = new StringBuilder();
       
   192 
       
   193         for (int i = 0; i < first.length(); i++) {
       
   194             if (startsWith(first.substring(0, i + 1), strings)) {
       
   195                 candidate.append(first.charAt(i));
       
   196             }
       
   197             else {
       
   198                 break;
       
   199             }
       
   200         }
       
   201 
       
   202         return candidate.toString();
       
   203     }
       
   204 
       
   205     /**
       
   206      * @return true is all the elements of <i>candidates</i> start with <i>starts</i>
       
   207      */
       
   208     private static boolean startsWith(final String starts, final String[] candidates) {
       
   209         for (String candidate : candidates) {
       
   210             if (!candidate.toLowerCase().startsWith(starts.toLowerCase())) {
       
   211                 return false;
       
   212             }
       
   213         }
       
   214 
       
   215         return true;
       
   216     }
       
   217 
       
   218     private static enum Messages
       
   219     {
       
   220         DISPLAY_CANDIDATES,
       
   221         DISPLAY_CANDIDATES_YES,
       
   222         DISPLAY_CANDIDATES_NO,;
       
   223 
       
   224         private static final
       
   225         ResourceBundle
       
   226             bundle =
       
   227             ResourceBundle.getBundle(CandidateListCompletionHandler.class.getName(), Locale.getDefault());
       
   228 
       
   229         public String format(final Object... args) {
       
   230             if (bundle == null)
       
   231                 return "";
       
   232             else
       
   233                 return String.format(bundle.getString(name()), args);
       
   234         }
       
   235     }
       
   236 }