Skip to content

Commit

Permalink
Various fixes to the file/dir completion UX
Browse files Browse the repository at this point in the history
  • Loading branch information
grugnog committed Mar 24, 2013
1 parent 9383833 commit 10c4771
Showing 1 changed file with 60 additions and 29 deletions.
89 changes: 60 additions & 29 deletions includes/complete.inc
Original file line number Diff line number Diff line change
Expand Up @@ -290,58 +290,89 @@ function drush_complete_match($last_word, $values) {
*/
function drush_complete_match_file($last_word, $files) {
$return = array();
$firstchar = '';
$full_paths = TRUE;
if (isset($last_word) && $last_word[0] == '~') {
if ($last_word[0] == '~') {
// Complete does not do tilde expansion, so we do it here.
$parts = explode('/', $last_word);
// We shell out (unquoted) to expand the tilde.
drush_shell_exec('echo ' . $parts[0]);
$output = drush_shell_exec_output();
$parts[0] = $output[0];
$last_word = implode('/', $parts);
drush_shell_exec('echo ' . $last_word);
return drush_shell_exec_output();
}

$dir = '';
if (substr($last_word, -1) == '/' && is_dir($last_word)) {
// If we exactly match a trailing directory, then we use that as the base
// for the listing. We only do this if a trailing slash is present, since at
// this stage it is still possible there are other directories that start
// with this string.
$dir = $last_word;
}
else {
// Otherwise we discard the last part of the path (this is matched against
// the list later), and use that as our base.
$dir = dirname($last_word);
if (empty($dir) || $dir == '.' && $last_word != '.' && substr($last_word, 0, 2) != './') {
// We are looking at the current working directory, so unless the user is
// actually specifying a leading dot we leave the path empty.
$dir = '';
}
else {
// In all other cases we need to add a trailing slash.
$dir .= '/';
}
}

foreach ($files as $spec) {
// We always include GLOB_MARK, as an easy way to detect directories.
$flags = GLOB_MARK;
if (isset($spec['flags'])) {
$flags = $spec['flags'] | GLOB_MARK;
}
$listing = glob($last_word . $spec['pattern'], $flags);
foreach ($listing as $item) {
// Detect if the initial characters of the file/dirs to be listing differ.
// If they do, we return a list of just their names. If they all have the
// same first character we return full paths, to prevent the shell
// replacing the current path with just the matching character(s).
$char = $item[strrpos($last_word, '/') + 1];
if (empty($firstchar)) {
$firstchar = $char;
}
else if ($firstchar !== $char) {
$full_paths = FALSE;
}
$return[] = $item;
}
}
// If we don't need to return full paths, shorten them appropriately.
if ($full_paths == FALSE) {
foreach ($return as $id => $item) {
$return[$id] = substr($return[$id], strrpos($last_word, '/') + 1);
}
$listing = glob($dir . $spec['pattern'], $flags);
$return = array_merge($return, drush_complete_match($last_word, $listing));
}
// If we are returning a single item (which will become part of the final
// command), we need to use the full path, and we need to escape it
// appropriately.
if (count($return) == 1) {
// Escape common shell metacharacters (we don't use escapeshellarg as it
// single quotes everything, even when unnecessary).
$item = array_pop($return);
$item = preg_replace('/[ |&;()<>]/', "\\\\$0", $item);
if (substr($item, -1) !== '/') {
// Insert a space after files, since the argument is complete.
$item = $item . ' ';
}
$return = array($item);
}
else {
$firstchar = TRUE;
if ($last_word[0] == '/') {
// If we are working with absolute paths, we need to check if the first
// character of all the completions matches. If it does, then we pass a
// full path for each match, so the shell completes as far as it can,
// matching the behaviour with relative paths.
$pos = strlen($last_word);
foreach ($return as $id => $item) {
if ($item[$pos] !== $return[0][$pos]) {
$firstchar = FALSE;
continue;
}
}
}
foreach ($return as $id => $item) {
// For directories we leave the path alone.
$slash_pos = strpos($last_word, '/');
if ($slash_pos === 0 && $firstchar) {
// With absolute paths where completions share initial characters, we
// pass in a resolved path.
$return[$id] = realpath($item);
}
else if ($slash_pos !== FALSE && $dir != './') {
// For files, we pass only the file name, ignoring the false match when
// the user is using a single dot relative path.
$return[$id] = basename($item);
}
}
}
return $return;
}

Expand Down

0 comments on commit 10c4771

Please sign in to comment.