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 }