1 /*
   2  * Copyright (c) 2002-2012, 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 jline.console.completer;
  10 
  11 import jline.internal.Configuration;
  12 
  13 import java.io.File;
  14 import java.util.List;
  15 
  16 import static jline.internal.Preconditions.checkNotNull;
  17 
  18 /**
  19  * A file name completer takes the buffer and issues a list of
  20  * potential completions.
  21  * <p/>
  22  * This completer tries to behave as similar as possible to
  23  * <i>bash</i>'s file name completion (using GNU readline)
  24  * with the following exceptions:
  25  * <p/>
  26  * <ul>
  27  * <li>Candidates that are directories will end with "/"</li>
  28  * <li>Wildcard regular expressions are not evaluated or replaced</li>
  29  * <li>The "~" character can be used to represent the user's home,
  30  * but it cannot complete to other users' homes, since java does
  31  * not provide any way of determining that easily</li>
  32  * </ul>
  33  *
  34  * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
  35  * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
  36  * @since 2.3
  37  */
  38 public class FileNameCompleter
  39     implements Completer
  40 {
  41     // TODO: Handle files with spaces in them
  42 
  43     private static final boolean OS_IS_WINDOWS;
  44 
  45     static {
  46         String os = Configuration.getOsName();
  47         OS_IS_WINDOWS = os.contains("windows");
  48     }
  49 
  50     public int complete(String buffer, final int cursor, final List<CharSequence> candidates) {
  51         // buffer can be null
  52         checkNotNull(candidates);
  53 
  54         if (buffer == null) {
  55             buffer = "";
  56         }
  57 
  58         if (OS_IS_WINDOWS) {
  59             buffer = buffer.replace('/', '\\');
  60         }
  61 
  62         String translated = buffer;
  63 
  64         File homeDir = getUserHome();
  65 
  66         // Special character: ~ maps to the user's home directory
  67         if (translated.startsWith("~" + separator())) {
  68             translated = homeDir.getPath() + translated.substring(1);
  69         }
  70         else if (translated.startsWith("~")) {
  71             translated = homeDir.getParentFile().getAbsolutePath();
  72         }
  73         else if (!(new File(translated).isAbsolute())) {
  74             String cwd = getUserDir().getAbsolutePath();
  75             translated = cwd + separator() + translated;
  76         }
  77 
  78         File file = new File(translated);
  79         final File dir;
  80 
  81         if (translated.endsWith(separator())) {
  82             dir = file;
  83         }
  84         else {
  85             dir = file.getParentFile();
  86         }
  87 
  88         File[] entries = dir == null ? new File[0] : dir.listFiles();
  89 
  90         return matchFiles(buffer, translated, entries, candidates);
  91     }
  92 
  93     protected String separator() {
  94         return File.separator;
  95     }
  96 
  97     protected File getUserHome() {
  98         return Configuration.getUserHome();
  99     }
 100 
 101     protected File getUserDir() {
 102         return new File(".");
 103     }
 104 
 105     protected int matchFiles(final String buffer, final String translated, final File[] files, final List<CharSequence> candidates) {
 106         if (files == null) {
 107             return -1;
 108         }
 109 
 110         int matches = 0;
 111 
 112         // first pass: just count the matches
 113         for (File file : files) {
 114             if (file.getAbsolutePath().startsWith(translated)) {
 115                 matches++;
 116             }
 117         }
 118         for (File file : files) {
 119             if (file.getAbsolutePath().startsWith(translated)) {
 120                 CharSequence name = file.getName() + (matches == 1 && file.isDirectory() ? separator() : " ");
 121                 candidates.add(render(file, name).toString());
 122             }
 123         }
 124 
 125         final int index = buffer.lastIndexOf(separator());
 126 
 127         return index + separator().length();
 128     }
 129 
 130     protected CharSequence render(final File file, final CharSequence name) {
 131         return name;
 132     }
 133 }