diff --git a/src/main/java/jline/console/completer/ArgumentCompleter.java b/src/main/java/jline/console/completer/ArgumentCompleter.java index 21e035b4..34a537b0 100644 --- a/src/main/java/jline/console/completer/ArgumentCompleter.java +++ b/src/main/java/jline/console/completer/ArgumentCompleter.java @@ -131,10 +131,16 @@ public int complete(final String buffer, final int cursor, final List= completers.size() ? (completers.size() - 1) : i); + if (sub == null) { + continue; + } String[] args = list.getArguments(); String arg = (args == null || i >= args.length) ? "" : args[i]; @@ -149,11 +155,30 @@ public int complete(final String buffer, final int cursor, final List subCandidates = new LinkedList(); + int ret = completer.complete(list.getCursorArgument(), argpos, subCandidates); if (ret == -1) { return -1; } + if (list.argHyphen == null) { + // Since the completer does not know whether it has to escape or not, assume it has not escaped. + // what needs escaping is every char that would cause the completion create more than one argument. + for (CharSequence subCandidate : subCandidates) { + candidates.add(getDelimiter().escapeArgument(subCandidate)); + } + } else { + for (CharSequence subCandidate : subCandidates) { + if (getDelimiter().isDelimiter(subCandidate, subCandidate.length() - 1)) { + candidates.add(subCandidate.subSequence(0, subCandidate.length() - 1).toString() + + list.argHyphen + subCandidate.charAt(subCandidate.length() - 1)); + } else { + // must not add hyphen as completion might not be finished, + // e.g. filepath completion can go into next subfolder + candidates.add(subCandidate.toString()); + } + } + } int pos = ret + list.getBufferPosition() - argpos; @@ -205,6 +230,16 @@ public static interface ArgumentDelimiter * @return True if the character should be a delimiter */ boolean isDelimiter(CharSequence buffer, int pos); + + /** + * Returns a modification of argument where escaping characters have been added as necessary + * such that isDelimiter() is false for all characters but the last, which may be true or false + * (delimiting towards the following argument). + * + * @param argument A string that represents an unescaped argument without trailing delimiters. + * @return The argument escaped + */ + CharSequence escapeArgument(CharSequence argument); } /** @@ -286,54 +321,53 @@ public ArgumentList delimit(final CharSequence buffer, final int cursor) { // length of the current argument argpos = arg.length(); } + CharSequence argHyphen = null; if (arg.length() > 0) { + // still in open quote block + if (quoteStart >= 0) { + argHyphen = buffer.subSequence(quoteStart, quoteStart + 1); + } args.add(arg.toString()); } - return new ArgumentList(args.toArray(new String[args.size()]), bindex, argpos, cursor); + return new ArgumentList(args.toArray(new String[args.size()]), bindex, argpos, cursor, argHyphen); } /** * Returns true if the specified character is a whitespace parameter. Check to ensure that the character is not - * escaped by any of {@link #getQuoteChars}, and is not escaped by ant of the {@link #getEscapeChars}, and - * returns true from {@link #isDelimiterChar}. + * escaped by any {@link #getEscapeChars}, and returns true from {@link #isDelimiterChar}. + * Whether it delimits arguments or is within a quote context is decided elsewhere. * * @param buffer The complete command buffer * @param pos The index of the character in the buffer * @return True if the character should be a delimiter */ public boolean isDelimiter(final CharSequence buffer, final int pos) { - return !isQuoted(buffer, pos) && !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos); - } - - public boolean isQuoted(final CharSequence buffer, final int pos) { - return false; - } - - public boolean isQuoteChar(final CharSequence buffer, final int pos) { if (pos < 0) { return false; } - for (int i = 0; (quoteChars != null) && (i < quoteChars.length); i++) { - if (buffer.charAt(pos) == quoteChars[i]) { - return !isEscaped(buffer, pos); - } - } + return isDelimiterChar(buffer, pos) && !isEscaped(buffer, pos); + } - return false; + public boolean isQuoteChar(final CharSequence buffer, final int pos) { + return isUnescapedCharInArray(buffer, pos, quoteChars); } /** * Check if this character is a valid escape char (i.e. one that has not been escaped) */ public boolean isEscapeChar(final CharSequence buffer, final int pos) { + return isUnescapedCharInArray(buffer, pos, escapeChars); + } + + protected boolean isUnescapedCharInArray(final CharSequence buffer, final int pos, char[] array) { if (pos < 0) { return false; } - for (int i = 0; (escapeChars != null) && (i < escapeChars.length); i++) { - if (buffer.charAt(pos) == escapeChars[i]) { + for (int i = 0; (array != null) && (i < array.length); i++) { + if (buffer.charAt(pos) == array[i]) { return !isEscaped(buffer, pos); // escape escape } } @@ -359,6 +393,21 @@ public boolean isEscaped(final CharSequence buffer, final int pos) { return isEscapeChar(buffer, pos - 1); } + public CharSequence escapeArgument(CharSequence argument) { + if (escapeChars == null || escapeChars.length == 0) { + return argument; + } + StringBuilder builder = new StringBuilder(argument.length()); + for (int i = 0; (argument != null) && (i < argument.length() - 1); i++) { + if ((isDelimiterChar(argument, i)) || isEscapeChar(argument, i) || isQuoteChar(argument, i)) { + builder.append(escapeChars[0]); + } + builder.append(argument.charAt(i)); + } + builder.append(argument.charAt(argument.length() - 1)); + return builder.toString(); + } + /** * Returns true if the character at the specified position if a delimiter. This method will only be called if * the character is not enclosed in any of the {@link #getQuoteChars}, and is not escaped by ant of the @@ -382,6 +431,9 @@ public static class WhitespaceArgumentDelimiter */ @Override public boolean isDelimiterChar(final CharSequence buffer, final int pos) { + if (pos < 0) { + return false; + } return Character.isWhitespace(buffer.charAt(pos)); } } @@ -393,6 +445,7 @@ public boolean isDelimiterChar(final CharSequence buffer, final int pos) { */ public static class ArgumentList { + private String[] arguments; private int cursorArgumentIndex; @@ -401,17 +454,25 @@ public static class ArgumentList private int bufferPosition; + private final CharSequence argHyphen; + /** * @param arguments The array of tokens * @param cursorArgumentIndex The token index of the cursor * @param argumentPosition The position of the cursor in the current token * @param bufferPosition The position of the cursor in the whole buffer + * @param argHyphen The opening hyphen of last argument if not closed, else null */ - public ArgumentList(final String[] arguments, final int cursorArgumentIndex, final int argumentPosition, final int bufferPosition) { + public ArgumentList(final String[] arguments, final int cursorArgumentIndex, final int argumentPosition, final int bufferPosition, final CharSequence argHyphen) { this.arguments = checkNotNull(arguments); this.cursorArgumentIndex = cursorArgumentIndex; this.argumentPosition = argumentPosition; this.bufferPosition = bufferPosition; + this.argHyphen = argHyphen; + } + + public ArgumentList(final String[] arguments, final int cursorArgumentIndex, final int argumentPosition, final int bufferPosition) { + this(arguments, cursorArgumentIndex, argumentPosition, bufferPosition, null); } public void setCursorArgumentIndex(final int i) { diff --git a/src/main/java/jline/console/completer/FileNameCompleter.java b/src/main/java/jline/console/completer/FileNameCompleter.java index 49b1c1ac..3b3fe95e 100644 --- a/src/main/java/jline/console/completer/FileNameCompleter.java +++ b/src/main/java/jline/console/completer/FileNameCompleter.java @@ -38,10 +38,48 @@ public class FileNameCompleter implements Completer { - // TODO: Handle files with spaces in them private static final boolean OS_IS_WINDOWS; + /** + * If true, will folders as final completion result + */ + private boolean completeFolders = false; + + /** + * If false, will not offer files + */ + private boolean completeFiles = true; + + /** + * whether to append a blank after full completion (depending on whether more arguments will follow this one) + */ + private boolean printSpaceAfterFullCompletion = true; + + public boolean getCompleteFolders() { + return completeFolders; + } + + public void setCompleteFolders(boolean completeFolders) { + this.completeFolders = completeFolders; + } + + public boolean getCompleteFiles() { + return completeFiles; + } + + public void setCompleteFiles(boolean completeFiles) { + this.completeFiles = completeFiles; + } + + public boolean getPrintSpaceAfterFullCompletion() { + return printSpaceAfterFullCompletion; + } + + public void setPrintSpaceAfterFullCompletion(boolean printSpaceAfterFullCompletion) { + this.printSpaceAfterFullCompletion = printSpaceAfterFullCompletion; + } + static { String os = Configuration.getOsName(); OS_IS_WINDOWS = os.contains("windows"); @@ -61,14 +99,15 @@ public int complete(String buffer, final int cursor, final List ca String translated = buffer; - File homeDir = getUserHome(); - - // Special character: ~ maps to the user's home directory - if (translated.startsWith("~" + separator())) { - translated = homeDir.getPath() + translated.substring(1); - } - else if (translated.startsWith("~")) { - translated = homeDir.getParentFile().getAbsolutePath(); + // Special character: ~ maps to the user's home directory in most OSs + if (!OS_IS_WINDOWS && translated.startsWith("~")) { + File homeDir = getUserHome(); + if (translated.startsWith("~" + separator())) { + translated = homeDir.getPath() + translated.substring(1); + } + else { + translated = homeDir.getParentFile().getAbsolutePath(); + } } else if (!(new File(translated).isAbsolute())) { String cwd = getUserDir().getAbsolutePath(); @@ -102,23 +141,40 @@ protected File getUserDir() { return new File("."); } - protected int matchFiles(final String buffer, final String translated, final File[] files, final List candidates) { + protected int matchFiles(final String buffer, final String prefix, final File[] files, final List candidates) { if (files == null) { return -1; } - int matches = 0; - // first pass: just count the matches for (File file : files) { - if (file.getAbsolutePath().startsWith(translated)) { - matches++; + if (!completeFiles && !file.isDirectory()) { + continue; } - } - for (File file : files) { - if (file.getAbsolutePath().startsWith(translated)) { - CharSequence name = file.getName() + (matches == 1 && file.isDirectory() ? separator() : " "); - candidates.add(render(file, name).toString()); + if (ignoreFile(file)) { + continue; + } + if (file.getAbsolutePath().startsWith(prefix)) { + String renderedName = render(file, file.getName()).toString(); + if (file.isDirectory()) { + // add first candidate folder with separator for file/subfolder + if (completeFiles || hasSubfolders(file)) { + // render separator only if has subfolders + candidates.add(renderedName + separator()); + } + // add second candidate (folder itself) + if (completeFolders) { + if (printSpaceAfterFullCompletion) { + renderedName += ' '; + } + candidates.add(renderedName); + } + } else { + if (printSpaceAfterFullCompletion) { + renderedName += ' '; + } + candidates.add(renderedName); + } } } @@ -127,6 +183,20 @@ protected int matchFiles(final String buffer, final String translated, final Fil return index + separator().length(); } + // hook to extend Filename COmpleter to exclude certain files / folders + protected boolean ignoreFile(File file) { + return false; + } + + protected boolean hasSubfolders(File dir) { + for (File file : dir.listFiles()) { + if (file.isDirectory()) { + return true; + } + } + return false; + } + protected CharSequence render(final File file, final CharSequence name) { return name; } diff --git a/src/main/java/jline/internal/Configuration.java b/src/main/java/jline/internal/Configuration.java index 1a74b2ce..26846763 100644 --- a/src/main/java/jline/internal/Configuration.java +++ b/src/main/java/jline/internal/Configuration.java @@ -58,7 +58,13 @@ private static Properties initProperties() { private static void loadProperties(final URL url, final Properties props) throws IOException { Log.debug("Loading properties from: ", url); - InputStream input = url.openStream(); + InputStream input; + try { + input = url.openStream(); + } catch (IOException e) { + Log.debug("Could not load properties from " + url + " : " + e.getMessage()); + return; + } try { props.load(new BufferedInputStream(input)); } diff --git a/src/test/java/jline/console/ConsoleReaderTestSupport.java b/src/test/java/jline/console/ConsoleReaderTestSupport.java index eabb32fd..09c2d231 100644 --- a/src/test/java/jline/console/ConsoleReaderTestSupport.java +++ b/src/test/java/jline/console/ConsoleReaderTestSupport.java @@ -11,6 +11,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.List; import jline.TerminalSupport; import org.junit.Before; @@ -83,7 +84,7 @@ protected void assertPosition(int pos, final Buffer buffer, final boolean clear) // noop } - assertEquals(pos, console.getCursorPosition ()); + assertEquals(pos, console.getCursorPosition()); } /** @@ -115,6 +116,11 @@ protected void assertLine(final String expected, final Buffer buffer, assertEquals(expected, prevLine); } + protected void assertEqualSet(List l1, List l2) { + assertTrue(l1.containsAll(l2) && l2.containsAll(l1)); + } + + private String getKeyForAction(final Operation key) { switch (key) { case BACKWARD_WORD: return "\u001Bb"; diff --git a/src/test/java/jline/console/completer/ArgumentCompleterTest.java b/src/test/java/jline/console/completer/ArgumentCompleterTest.java index 2da519a8..fad3eb14 100644 --- a/src/test/java/jline/console/completer/ArgumentCompleterTest.java +++ b/src/test/java/jline/console/completer/ArgumentCompleterTest.java @@ -13,6 +13,12 @@ import jline.console.completer.StringsCompleter; import org.junit.Test; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; + + /** * Tests for {@link jline.console.completer.ArgumentCompleter}. * @@ -65,4 +71,63 @@ public void test2() throws Exception { assertBuffer("some foo ", new Buffer("some fo").tab()); } -} \ No newline at end of file + + @Test + public void testQuoted() throws Exception { + ArgumentCompleter argCompleter = new ArgumentCompleter( + new StringsCompleter("bar"), + new StringsCompleter("foo ")); + console.addCompleter(argCompleter); + + assertBuffer("'bar' 'foo' ", new Buffer("'bar' 'f").tab()); + } + + @Test + public void testArgumentDelimiter() throws Exception { + ArgumentCompleter.WhitespaceArgumentDelimiter wsDelimiter = new ArgumentCompleter.WhitespaceArgumentDelimiter(); + String buffer = "\"a\\\"a2\"'b\\'b2'c\\ c2 d\\\\d2\\e"; + List expected = Arrays.asList("a\"a2", "b'b2", "c c2", "d\\d2e"); + assertEquals(expected, Arrays.asList(wsDelimiter.delimit(buffer, buffer.length()).getArguments())); + assertEquals("a\\ b\\\"c\\\'d\\\\e", wsDelimiter.escapeArgument("a b\"c'd\\e")); + assertEquals("a\\ b\\\"c\\\'d\\\\e ", wsDelimiter.escapeArgument("a b\"c'd\\e ")); + } + + @Test + public void testEscaping() throws Exception { + ArgumentCompleter argCompleter = new ArgumentCompleter( + new StringsCompleter("bar"), + new StringsCompleter("foo foo2")); + console.addCompleter(argCompleter); + + assertBuffer("bar foo\\ foo2 ", new Buffer("bar f").tab()); + } + + @Test + public void testEscapingQuoted() throws Exception { + ArgumentCompleter argCompleter = new ArgumentCompleter( + new StringsCompleter("bar"), + new StringsCompleter("foo foo2")); + console.addCompleter(argCompleter); + boolean backup = ((CandidateListCompletionHandler) console.getCompletionHandler()).getPrintSpaceAfterFullCompletion(); + try { + ((CandidateListCompletionHandler) console.getCompletionHandler()).setPrintSpaceAfterFullCompletion(false); + assertBuffer("bar 'foo foo2", new Buffer("bar 'f").tab()); + } finally { + ((CandidateListCompletionHandler) console.getCompletionHandler()).setPrintSpaceAfterFullCompletion(backup); + } + } + + @Test + public void testEscapingQuotedBlank() throws Exception { + ArgumentCompleter argCompleter = new ArgumentCompleter( + new StringsCompleter("bar"), + new StringsCompleter("foo foo2 ")); + console.addCompleter(argCompleter); + boolean backup = ((CandidateListCompletionHandler) console.getCompletionHandler()).getPrintSpaceAfterFullCompletion(); + try { + assertBuffer("bar 'foo foo2' ", new Buffer("bar 'f").tab()); + } finally { + ((CandidateListCompletionHandler) console.getCompletionHandler()).setPrintSpaceAfterFullCompletion(backup); + } + } +} diff --git a/src/test/java/jline/console/completer/FileNameCompleterTest.java b/src/test/java/jline/console/completer/FileNameCompleterTest.java new file mode 100644 index 00000000..9b8dc1e2 --- /dev/null +++ b/src/test/java/jline/console/completer/FileNameCompleterTest.java @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2002-2015, the original author or authors. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * http://www.opensource.org/licenses/bsd-license.php + */ +package jline.console.completer; + +import jline.console.ConsoleReaderTestSupport; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for {@link FileNameCompleter}. + */ +public class FileNameCompleterTest + extends ConsoleReaderTestSupport +{ + @Rule + public TemporaryFolder testFolder = new TemporaryFolder(); + + @Test + public void testCompletionDefaults() throws IOException { + FileNameCompleter completor = new FileNameCompleter(); + String filename = "file.txt"; + String foldername = "folder"; + testFolder.newFile(filename); + testFolder.newFolder(foldername); + + String buffer = testFolder.getRoot().getAbsolutePath() + File.separator; + List candidates = new ArrayList(); + assertEquals(buffer.length(), completor.complete(buffer, 0, candidates)); + assertEqualSet(Arrays.asList(filename + " ", foldername + File.separator), candidates); + } + + @Test + public void testCompletionSingleFile() throws IOException { + FileNameCompleter completor = new FileNameCompleter(); + String filename = "file.txt"; + testFolder.newFile(filename); + + String buffer = testFolder.getRoot().getAbsolutePath() + File.separator; + List candidates = new ArrayList(); + assertEquals(buffer.length(), completor.complete(buffer, 0, candidates)); + assertEquals(Collections.singletonList(filename + " "), candidates); + } + + @Test + public void testCompletionSingleFileNoSuffix() throws IOException { + FileNameCompleter completor = new FileNameCompleter(); + completor.setPrintSpaceAfterFullCompletion(false); + String filename = "file.txt"; + testFolder.newFile(filename); + + String buffer = testFolder.getRoot().getAbsolutePath() + File.separator; + List candidates = new ArrayList(); + assertEquals(buffer.length(), completor.complete(buffer, 0, candidates)); + assertEquals(Collections.singletonList(filename), candidates); + } + + @Test + public void testCompletionSingleFolder() throws IOException { + FileNameCompleter completor = new FileNameCompleter(); + String foldername = "folder"; + testFolder.newFolder(foldername); + + String buffer = testFolder.getRoot().getAbsolutePath() + File.separator; + List candidates = new ArrayList(); + assertEquals(buffer.length(), completor.complete(buffer, 0, candidates)); + assertEquals(Collections.singletonList(foldername + File.separator), candidates); + } + + @Test + public void testCompletionLeadingHyphen() throws IOException { + FileNameCompleter completor = new FileNameCompleter(); + String filename = "file.txt"; + String foldername = "folder"; + testFolder.newFile(filename); + testFolder.newFolder(foldername); + + String buffer = testFolder.getRoot().getAbsolutePath() + File.separator; + List candidates = new ArrayList(); + assertEquals(buffer.length(), completor.complete(buffer, 0, candidates)); + assertEqualSet(Arrays.asList(filename + " ", foldername + File.separator), candidates); + } + + @Test + public void testCompletionNoBlankSuffix() throws IOException { + FileNameCompleter completor = new FileNameCompleter(); + completor.setPrintSpaceAfterFullCompletion(false); + String filename = "file.txt"; + String foldername = "folder"; + testFolder.newFile(filename); + testFolder.newFolder(foldername); + + String buffer = testFolder.getRoot().getAbsolutePath() + File.separator; + List candidates = new ArrayList(); + assertEquals(buffer.length(), completor.complete(buffer, 0, candidates)); + assertEqualSet(Arrays.asList(filename, foldername + File.separator), candidates); + } + + @Test + public void testCompletionFoldersOnly() throws IOException { + FileNameCompleter completor = new FileNameCompleter(); + completor.setCompleteFolders(true); + completor.setCompleteFiles(false); + String filename = "file.txt"; + String foldername = "folder"; + testFolder.newFile(filename); + testFolder.newFolder(foldername); + + String buffer = testFolder.getRoot().getAbsolutePath() + File.separator; + List candidates = new ArrayList(); + assertEquals(buffer.length(), completor.complete(buffer, 0, candidates)); + assertEquals(Collections.singletonList(foldername + ' '), candidates); + } + + @Test + public void testCompletionFoldersAndFiles() throws IOException { + FileNameCompleter completor = new FileNameCompleter(); + completor.setCompleteFolders(true); + String filename = "file.txt"; + String foldername = "folder"; + testFolder.newFile(filename); + testFolder.newFolder(foldername); + + String buffer = testFolder.getRoot().getAbsolutePath() + File.separator; + List candidates = new ArrayList(); + assertEquals(buffer.length(), completor.complete(buffer, 0, candidates)); + + assertEqualSet(Arrays.asList(filename + ' ', foldername + ' ', foldername + File.separator), candidates); + } + + @Test + public void testCompletionFolderNoBlankSuffix() throws IOException { + FileNameCompleter completor = new FileNameCompleter(); + completor.setPrintSpaceAfterFullCompletion(false); + completor.setCompleteFolders(true); + completor.setCompleteFiles(false); + String filename = "file.txt"; + String foldername = "folder"; + testFolder.newFile(filename); + testFolder.newFolder(foldername); + + String buffer = testFolder.getRoot().getAbsolutePath() + File.separator; + List candidates = new ArrayList(); + assertEquals(buffer.length(), completor.complete(buffer, 0, candidates)); + assertEquals(Collections.singletonList(foldername), candidates); + } + + + @Test + public void testCompletionWithPrefix() throws IOException { + FileNameCompleter completor = new FileNameCompleter(); + String filename = "the file.txt"; + String foldername = "the folder"; + testFolder.newFile(filename); + testFolder.newFolder(foldername); + + String buffer = testFolder.getRoot().getAbsolutePath() + File.separator; + List candidates = new ArrayList(); + assertEquals(buffer.length(), completor.complete(buffer + "the", 0, candidates)); + assertEqualSet(Arrays.asList(filename + " ", foldername + File.separator), candidates); + } + + @Test + public void testCompletionRelativePath() throws IOException { + FileNameCompleter completor = new FileNameCompleter() { + protected File getUserDir() { + // simulate being in temporary folder + return testFolder.getRoot(); + } + }; + //completor.setHandleLeadingHyphen(true); + String filename = "the file.txt"; + String foldername = "the folder"; + testFolder.newFile(filename); + testFolder.newFolder(foldername); + + String buffer = ""; + List candidates = new ArrayList(); + assertEquals(buffer.length(), completor.complete(buffer + "the", 0, candidates)); + assertEqualSet(Arrays.asList(filename + " ", foldername + File.separator), candidates); + } + + @Test + public void testNestedSubfolders() throws IOException { + FileNameCompleter completor = new FileNameCompleter() { + protected File getUserDir() { + // simulate being in temporary folder + return testFolder.getRoot(); + } + }; + String foldername = "the folder"; + String subfoldername = "the subfolder"; + File folder = testFolder.newFolder(foldername); + File subfolder = new File(folder, subfoldername); + subfolder.mkdir(); + + String buffer = ""; + List candidates = new ArrayList(); + // completion must start on hyphen, not after hyphen! + assertEquals(buffer.length(), completor.complete(buffer, 0, candidates)); + assertEquals(Collections.singletonList(folder.getName() + File.separator), candidates); + } + + @Test + public void testNestedSubfoldersFolderOnly() throws IOException { + FileNameCompleter completor = new FileNameCompleter() { + protected File getUserDir() { + // simulate being in temporary folder + return testFolder.getRoot(); + } + }; + completor.setCompleteFolders(true); + completor.setCompleteFiles(false); + String foldername = "the folder"; + String subfoldername = "the subfolder"; + File folder = testFolder.newFolder(foldername); + File subfolder = new File(folder, subfoldername); + subfolder.mkdir(); + + String buffer = ""; + List candidates = new ArrayList(); + // completion must start on hyphen, not after hyphen! + assertEquals(buffer.length(), completor.complete(buffer, 0, candidates)); + assertEqualSet(Arrays.asList(folder.getName() + File.separator, folder.getName() + " "), candidates); + } + + @Test + public void testNestedSubfoldersFolderOnlyNoSpace() throws IOException { + FileNameCompleter completor = new FileNameCompleter() { + protected File getUserDir() { + // simulate being in temporary folder + return testFolder.getRoot(); + } + }; + completor.setCompleteFolders(true); + completor.setCompleteFiles(false); + completor.setPrintSpaceAfterFullCompletion(false); + String foldername = "the folder"; + String subfoldername = "the subfolder"; + File folder = testFolder.newFolder(foldername); + File subfolder = new File(folder, subfoldername); + subfolder.mkdir(); + + String buffer = ""; + List candidates = new ArrayList(); + // completion must start on hyphen, not after hyphen! + assertEquals(buffer.length(), completor.complete(buffer, 0, candidates)); + assertEqualSet(Arrays.asList(folder.getName() + File.separator, folder.getName()), candidates); + } + + @Test + public void testCompletionIgnoreOne() throws IOException { + FileNameCompleter completor = new FileNameCompleter() { + @Override + protected boolean ignoreFile(File file) { + return file.getName().endsWith(".pdf"); + } + }; + String filename = "file.txt"; + testFolder.newFile(filename); + String filename2 = "file.pdf"; + testFolder.newFile(filename2); + + String buffer = testFolder.getRoot().getAbsolutePath() + File.separator; + List candidates = new ArrayList(); + assertEquals(buffer.length(), completor.complete(buffer, 0, candidates)); + assertEquals(Collections.singletonList(filename + " "), candidates); + } + + @Test + public void testMatchFiles_Unix() { + if(! System.getProperty("os.name").startsWith("Windows")) { + FileNameCompleter completer = new FileNameCompleter(); + List candidates = new ArrayList(); + int resultIndex = completer.matchFiles("foo/bar", "/foo/bar", + new File[]{new File("/foo/baroo"), new File("/foo/barbee")}, candidates); + assertEquals("foo/".length(), resultIndex); + assertEquals(Arrays.asList("baroo ", "barbee "), candidates); + } + } +}