001 /* 002 * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. 003 * 004 * This software is distributable under the BSD license. See the terms of the 005 * BSD license in the documentation provided with this software. 006 */ 007 package jline; 008 009 import java.io.*; 010 import java.util.*; 011 012 /** 013 * A file name completor takes the buffer and issues a list of 014 * potential completions. 015 * 016 * <p> 017 * This completor tries to behave as similar as possible to 018 * <i>bash</i>'s file name completion (using GNU readline) 019 * with the following exceptions: 020 * 021 * <ul> 022 * <li>Candidates that are directories will end with "/"</li> 023 * <li>Wildcard regular expressions are not evaluated or replaced</li> 024 * <li>The "~" character can be used to represent the user's home, 025 * but it cannot complete to other users' homes, since java does 026 * not provide any way of determining that easily</li> 027 * </ul> 028 * 029 * <p>TODO</p> 030 * <ul> 031 * <li>Handle files with spaces in them</li> 032 * <li>Have an option for file type color highlighting</li> 033 * </ul> 034 * 035 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 036 */ 037 public class FileNameCompletor implements Completor { 038 public int complete(final String buf, final int cursor, 039 final List candidates) { 040 String buffer = (buf == null) ? "" : buf; 041 042 String translated = buffer; 043 044 // special character: ~ maps to the user's home directory 045 if (translated.startsWith("~" + File.separator)) { 046 translated = System.getProperty("user.home") 047 + translated.substring(1); 048 } else if (translated.startsWith("~")) { 049 translated = new File(System.getProperty("user.home")).getParentFile() 050 .getAbsolutePath(); 051 } else if (!(translated.startsWith(File.separator))) { 052 translated = new File("").getAbsolutePath() + File.separator 053 + translated; 054 } 055 056 File f = new File(translated); 057 058 final File dir; 059 060 if (translated.endsWith(File.separator)) { 061 dir = f; 062 } else { 063 dir = f.getParentFile(); 064 } 065 066 final File[] entries = (dir == null) ? new File[0] : dir.listFiles(); 067 068 try { 069 return matchFiles(buffer, translated, entries, candidates); 070 } finally { 071 // we want to output a sorted list of files 072 sortFileNames(candidates); 073 } 074 } 075 076 protected void sortFileNames(final List fileNames) { 077 Collections.sort(fileNames); 078 } 079 080 /** 081 * Match the specified <i>buffer</i> to the array of <i>entries</i> 082 * and enter the matches into the list of <i>candidates</i>. This method 083 * can be overridden in a subclass that wants to do more 084 * sophisticated file name completion. 085 * 086 * @param buffer the untranslated buffer 087 * @param translated the buffer with common characters replaced 088 * @param entries the list of files to match 089 * @param candidates the list of candidates to populate 090 * 091 * @return the offset of the match 092 */ 093 public int matchFiles(String buffer, String translated, File[] entries, 094 List candidates) { 095 if (entries == null) { 096 return -1; 097 } 098 099 int matches = 0; 100 101 // first pass: just count the matches 102 for (int i = 0; i < entries.length; i++) { 103 if (entries[i].getAbsolutePath().startsWith(translated)) { 104 matches++; 105 } 106 } 107 108 // green - executable 109 // blue - directory 110 // red - compressed 111 // cyan - symlink 112 for (int i = 0; i < entries.length; i++) { 113 if (entries[i].getAbsolutePath().startsWith(translated)) { 114 String name = 115 entries[i].getName() 116 + (((matches == 1) && entries[i].isDirectory()) 117 ? File.separator : " "); 118 119 /* 120 if (entries [i].isDirectory ()) 121 { 122 name = new ANSIBuffer ().blue (name).toString (); 123 } 124 */ 125 candidates.add(name); 126 } 127 } 128 129 final int index = buffer.lastIndexOf(File.separator); 130 131 return index + File.separator.length(); 132 } 133 }