View Javadoc

1   package org.lsst.ccs.command;
2   
3   import java.util.HashSet;
4   import java.util.List;
5   import java.util.Set;
6   import java.util.SortedSet;
7   import java.util.TreeSet;
8   
9   /**
10   * Performs command completion, based on the information available in the
11   * command dictionary. This is designed to be jline compatible, but not actually
12   * implement jline's command completer to avoid adding a permanent dependency on
13   * jline.
14   *
15   * @author tonyj
16   */
17  public class DictionaryCompleter {
18      
19      private final Dictionary dict;
20  
21      /**
22       * Create a command completer for the given dictionary.
23       *
24       * @param dict
25       */
26      public DictionaryCompleter(Dictionary dict) {
27          this.dict = dict;
28      }
29  
30      /**
31       * Generate a list of possible command completions.
32       *
33       * @param buffer The current content of the input line
34       * @param index The index within the input line of the cursor
35       * @param list A list which this routine should fill with possible command
36       * completions.
37       * @return The position at which the cursor should be positioned prior to
38       * adding any of the returned items to the buffer.
39       */
40      public int complete(String buffer, int index, List<CharSequence> list) {
41          TokenizedCommand tc = new TokenizedCommand(buffer.substring(0, index));
42          int argCount = tc.getArgumentCount();
43          boolean endsWithWhiteSpace = !tc.isEmpty() && Character.isWhitespace(buffer.charAt(index - 1));
44          if (endsWithWhiteSpace) {
45              argCount++;
46          }
47          if (tc.isEmpty()) {
48              SortedSet<CharSequence> set = new TreeSet<>();
49              for (DictionaryCommand def : dict) {
50                  String command = def.getCommandName();
51                  command += " ";
52                  set.add(command);
53                  for (String alias : def.getAliases()) {
54                      alias += " ";
55                      set.add(alias);
56                  }
57              }
58              
59              list.addAll(set);
60          } else if (argCount == 0) {
61              String prefix = tc.getCommand();
62              SortedSet<CharSequence> set = new TreeSet<>();
63              Set<DictionaryCommand> uniqueCommands = new HashSet<>();
64              for (DictionaryCommand def : dict) {
65                  String command = def.getCommandName();
66                  if (command.startsWith(prefix)) {
67                      command += " ";
68                      set.add(command);
69                      uniqueCommands.add(def);
70                  }
71                  for (String alias : def.getAliases()) {
72                      if (alias.startsWith(prefix)) {
73                          alias += " ";
74                          set.add(alias);
75                          uniqueCommands.add(def);
76                      }
77                  }
78              }
79              // Special case, if there are >1 matching commands, but they are all just
80              // aliases for the same command, only return the actual command
81              if (set.size() > 1 && uniqueCommands.size() == 1) {
82                  list.add(uniqueCommands.iterator().next().getCommandName()+" ");
83              } else {
84                  list.addAll(set);
85              }
86              return tc.getCommandLocation();
87          } else {
88              String command = tc.getCommand();
89              String lastArg = endsWithWhiteSpace ? "" : tc.getArgument(argCount - 1);
90              for (DictionaryCommand def : dict) {
91                  if (def.matchesCommandOrAlias(command) && (def.getArguments().length >= argCount || def.isVarArgs())) {
92                      final DictionaryArgument currentArg = def.getArguments()[Math.min(argCount, def.getArguments().length) - 1];
93                      List<String> values = currentArg.getValues();
94                      if (values.isEmpty()) {
95                          // The argument is not an enumeration, return argument description, prefixed with # 
96                          list.add("# " + currentArg.getName() + " " + currentArg.getSimpleType() + " " + currentArg.getDescription());
97                      } else {
98                          // The argument is an enumeration, list possible values    
99                          for (String value : values) {
100                             if (startsWithIgnoreCase(value,lastArg)) {
101                                 value += " ";
102                                 list.add(value);
103                             }
104                         }
105                     }
106                 }
107             }
108             return endsWithWhiteSpace ? index : tc.getArgumentLocation(argCount - 1);
109         }
110         return index;
111     }
112     private static boolean startsWithIgnoreCase(String reference, String test) {
113         if (test.length()>reference.length()) return false;
114         return reference.substring(0,test.length()).equalsIgnoreCase(test);
115     }
116 }