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 */
9package jdk.internal.jline.console.completer;
10
11import jdk.internal.jline.internal.Configuration;
12
13import java.io.File;
14import java.util.List;
15
16import static jdk.internal.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 */
38public 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}
134