2005-09-20 21:26:39 +08:00
/** \file wildcard.c
2005-11-29 22:33:52 +08:00
2006-01-04 20:51:02 +08:00
Fish needs it ' s own globbing implementation to support
tab - expansion of globbed parameters . Also provides recursive
wildcards using * * .
2005-09-20 21:26:39 +08:00
*/
2015-07-25 23:14:25 +08:00
# include "config.h" // IWYU pragma: keep
2005-09-20 21:26:39 +08:00
# include <stdlib.h>
# include <wchar.h>
# include <unistd.h>
# include <sys/stat.h>
# include <dirent.h>
# include <errno.h>
2007-02-25 17:05:24 +08:00
# include <string.h>
2012-08-07 17:50:12 +08:00
# include <set>
2015-07-25 23:14:25 +08:00
# include <assert.h>
# include <stddef.h>
# include <wctype.h>
# include <string>
# include <utility>
2006-02-28 21:17:16 +08:00
# include "fallback.h"
2005-09-20 21:26:39 +08:00
# include "wutil.h"
# include "common.h"
# include "wildcard.h"
# include "complete.h"
# include "reader.h"
# include "expand.h"
2012-02-10 08:06:24 +08:00
# include <map>
2005-09-20 21:26:39 +08:00
2007-02-25 17:05:24 +08:00
/**
Description for generic executable
*/
# define COMPLETE_EXEC_DESC _( L"Executable" )
/**
Description for link to executable
*/
# define COMPLETE_EXEC_LINK_DESC _( L"Executable link" )
/**
Description for regular file
*/
# define COMPLETE_FILE_DESC _( L"File" )
/**
Description for character device
*/
# define COMPLETE_CHAR_DESC _( L"Character device" )
/**
Description for block device
*/
# define COMPLETE_BLOCK_DESC _( L"Block device" )
/**
Description for fifo buffer
*/
# define COMPLETE_FIFO_DESC _( L"Fifo" )
/**
Description for symlink
*/
# define COMPLETE_SYMLINK_DESC _( L"Symbolic link" )
/**
Description for symlink
*/
# define COMPLETE_DIRECTORY_SYMLINK_DESC _( L"Symbolic link to directory" )
/**
Description for Rotten symlink
*/
# define COMPLETE_ROTTEN_SYMLINK_DESC _( L"Rotten symbolic link" )
/**
Description for symlink loop
*/
# define COMPLETE_LOOP_SYMLINK_DESC _( L"Symbolic link loop" )
/**
Description for socket files
*/
# define COMPLETE_SOCKET_DESC _( L"Socket" )
/**
Description for directories
*/
# define COMPLETE_DIRECTORY_DESC _( L"Directory" )
2015-08-04 06:51:27 +08:00
/* Finds an internal (ANY_STRING, etc.) style wildcard, or wcstring::npos */
static size_t wildcard_find ( const wchar_t * wc )
{
for ( size_t i = 0 ; wc [ i ] ! = L ' \0 ' ; i + + )
{
if ( wc [ i ] = = ANY_CHAR | | wc [ i ] = = ANY_STRING | | wc [ i ] = = ANY_STRING_RECURSIVE )
{
return i ;
}
}
return wcstring : : npos ;
}
2014-08-25 05:28:31 +08:00
// Implementation of wildcard_has. Needs to take the length to handle embedded nulls (#1631)
static bool wildcard_has_impl ( const wchar_t * str , size_t len , bool internal )
2005-09-20 21:26:39 +08:00
{
2014-08-25 05:28:31 +08:00
assert ( str ! = NULL ) ;
const wchar_t * end = str + len ;
2012-11-19 08:30:30 +08:00
if ( internal )
2012-11-18 18:23:22 +08:00
{
2014-08-25 05:28:31 +08:00
for ( ; str < end ; str + + )
2012-11-19 08:30:30 +08:00
{
if ( ( * str = = ANY_CHAR ) | | ( * str = = ANY_STRING ) | | ( * str = = ANY_STRING_RECURSIVE ) )
2013-05-26 06:41:18 +08:00
return true ;
2012-11-19 08:30:30 +08:00
}
2012-11-18 18:23:22 +08:00
}
2012-11-19 08:30:30 +08:00
else
2012-11-18 18:23:22 +08:00
{
2012-11-19 08:30:30 +08:00
wchar_t prev = 0 ;
2014-08-25 05:28:31 +08:00
for ( ; str < end ; str + + )
2012-11-19 08:30:30 +08:00
{
if ( ( ( * str = = L ' * ' ) | | ( * str = = L ' ? ' ) ) & & ( prev ! = L ' \\ ' ) )
2013-05-26 06:41:18 +08:00
return true ;
2012-11-19 08:30:30 +08:00
prev = * str ;
}
2012-11-18 18:23:22 +08:00
}
2013-05-26 06:41:18 +08:00
return false ;
2005-09-20 21:26:39 +08:00
}
2014-08-25 05:28:31 +08:00
bool wildcard_has ( const wchar_t * str , bool internal )
{
assert ( str ! = NULL ) ;
return wildcard_has_impl ( str , wcslen ( str ) , internal ) ;
}
bool wildcard_has ( const wcstring & str , bool internal )
{
return wildcard_has_impl ( str . data ( ) , str . size ( ) , internal ) ;
}
2005-09-20 21:26:39 +08:00
/**
Check whether the string str matches the wildcard string wc .
2012-11-18 18:23:22 +08:00
2005-09-20 21:26:39 +08:00
\ param str String to be matched .
\ param wc The wildcard .
2012-11-18 18:23:22 +08:00
\ param is_first Whether files beginning with dots should not be matched against wildcards .
2005-09-20 21:26:39 +08:00
*/
2016-02-20 07:45:12 +08:00
static enum fuzzy_match_type_t wildcard_match_internal ( const wchar_t * str , const wchar_t * wc , bool leading_dots_fail_to_match , bool is_first )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
if ( * str = = 0 & & * wc = = 0 )
2015-08-02 07:02:46 +08:00
{
/* We're done */
2015-08-04 15:32:34 +08:00
return fuzzy_match_exact ;
2015-08-02 07:02:46 +08:00
}
2012-11-18 18:23:22 +08:00
2015-08-04 15:32:34 +08:00
/* Hackish fix for #270 . Prevent wildcards from matching . or .., but we must still allow literal matches. */
2013-04-21 03:41:02 +08:00
if ( leading_dots_fail_to_match & & is_first & & contains ( str , L " . " , L " .. " ) )
2012-10-16 09:16:47 +08:00
{
/* The string is '.' or '..'. Return true if the wildcard exactly matches. */
2015-08-04 15:32:34 +08:00
return wcscmp ( str , wc ) ? fuzzy_match_none : fuzzy_match_exact ;
}
2012-11-19 08:30:30 +08:00
if ( * wc = = ANY_STRING | | * wc = = ANY_STRING_RECURSIVE )
2012-11-18 18:23:22 +08:00
{
2012-11-19 08:30:30 +08:00
/* Ignore hidden file */
2013-04-21 03:41:02 +08:00
if ( leading_dots_fail_to_match & & is_first & & * str = = L ' . ' )
2012-11-19 08:30:30 +08:00
{
2015-08-04 15:32:34 +08:00
return fuzzy_match_none ;
2012-11-19 08:30:30 +08:00
}
2015-08-02 07:02:46 +08:00
/* Common case of * at the end. In that case we can early out since we know it will match. */
if ( wc [ 1 ] = = L ' \0 ' )
{
2015-08-04 15:32:34 +08:00
return fuzzy_match_exact ;
2015-08-02 07:02:46 +08:00
}
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
/* Try all submatches */
do
{
2016-02-20 07:45:12 +08:00
enum fuzzy_match_type_t subresult = wildcard_match_internal ( str , wc + 1 , leading_dots_fail_to_match , false ) ;
2015-08-04 15:32:34 +08:00
if ( subresult ! = fuzzy_match_none )
2015-08-08 04:28:47 +08:00
{
2015-08-04 15:32:34 +08:00
return subresult ;
2015-08-08 04:28:47 +08:00
}
2015-08-04 15:32:34 +08:00
} while ( * str + + ! = 0 ) ;
return fuzzy_match_none ;
2012-11-19 08:30:30 +08:00
}
else if ( * str = = 0 )
2012-11-18 18:23:22 +08:00
{
2012-11-19 08:30:30 +08:00
/*
End of string , but not end of wildcard , and the next wildcard
element is not a ' * ' , so this is not a match .
*/
2015-08-04 15:32:34 +08:00
return fuzzy_match_none ;
2012-11-18 18:23:22 +08:00
}
2015-08-02 07:02:46 +08:00
else if ( * wc = = ANY_CHAR )
2012-11-18 18:23:22 +08:00
{
2012-11-19 08:30:30 +08:00
if ( is_first & & * str = = L ' . ' )
{
2015-08-04 15:32:34 +08:00
return fuzzy_match_none ;
2012-11-19 08:30:30 +08:00
}
2012-11-18 18:23:22 +08:00
2016-02-20 07:45:12 +08:00
return wildcard_match_internal ( str + 1 , wc + 1 , leading_dots_fail_to_match , false ) ;
2012-11-19 08:30:30 +08:00
}
2015-08-02 07:02:46 +08:00
else if ( * wc = = * str )
{
2016-02-20 07:45:12 +08:00
return wildcard_match_internal ( str + 1 , wc + 1 , leading_dots_fail_to_match , false ) ;
2015-08-02 07:02:46 +08:00
}
2012-11-18 18:23:22 +08:00
2015-08-04 15:32:34 +08:00
return fuzzy_match_none ;
2005-09-20 21:26:39 +08:00
}
2015-08-04 06:51:27 +08:00
/* This does something horrible refactored from an even more horrible function */
static wcstring resolve_description ( wcstring * completion , const wchar_t * explicit_desc , wcstring ( * desc_func ) ( const wcstring & ) )
2005-09-20 21:26:39 +08:00
{
2015-08-04 06:51:27 +08:00
size_t complete_sep_loc = completion - > find ( PROG_COMPLETE_SEP ) ;
if ( complete_sep_loc ! = wcstring : : npos )
2012-11-18 18:23:22 +08:00
{
2015-08-04 06:51:27 +08:00
/* This completion has an embedded description, do not use the generic description */
const wcstring description = completion - > substr ( complete_sep_loc + 1 ) ;
completion - > resize ( complete_sep_loc ) ;
return description ;
2012-11-18 18:23:22 +08:00
}
2015-08-04 06:51:27 +08:00
else
2012-11-18 18:23:22 +08:00
{
2015-08-04 06:51:27 +08:00
const wcstring func_result = ( desc_func ? desc_func ( * completion ) : wcstring ( ) ) ;
if ( ! func_result . empty ( ) )
2012-11-19 08:30:30 +08:00
{
2015-08-04 06:51:27 +08:00
return func_result ;
}
else
{
return explicit_desc ? explicit_desc : L " " ;
2012-11-19 08:30:30 +08:00
}
2013-05-26 06:41:18 +08:00
}
2015-08-04 06:51:27 +08:00
}
2013-06-02 16:14:26 +08:00
2016-02-20 07:45:12 +08:00
/* A transient parameter pack needed by wildcard_complete. */
2015-08-04 06:51:27 +08:00
struct wc_complete_pack_t
{
const wcstring & orig ; // the original string, transient
const wchar_t * desc ; // literal description
wcstring ( * desc_func ) ( const wcstring & ) ; // function for generating descriptions
expand_flags_t expand_flags ;
2016-02-20 07:45:12 +08:00
wc_complete_pack_t ( const wcstring & str , const wchar_t * des , wcstring ( * df ) ( const wcstring & ) , expand_flags_t fl ) :
orig ( str ) ,
desc ( des ) ,
desc_func ( df ) ,
expand_flags ( fl )
{ }
2015-08-04 06:51:27 +08:00
} ;
2015-08-04 13:09:25 +08:00
/* Weirdly specific and non-reusable helper function that makes its one call site much clearer */
static bool has_prefix_match ( const std : : vector < completion_t > * comps , size_t first )
{
if ( comps ! = NULL )
{
const size_t after_count = comps - > size ( ) ;
for ( size_t j = first ; j < after_count ; j + + )
{
if ( comps - > at ( j ) . match . type < = fuzzy_match_prefix )
{
return true ;
}
}
}
return false ;
}
2015-08-04 06:51:27 +08:00
/**
Matches the string against the wildcard , and if the wildcard is a
possible completion of the string , the remainder of the string is
inserted into the out vector .
We ignore ANY_STRING_RECURSIVE here . The consequence is that you cannot
tab complete * * wildcards . This is historic behavior .
*/
static bool wildcard_complete_internal ( const wchar_t * str ,
const wchar_t * wc ,
const wc_complete_pack_t & params ,
complete_flags_t flags ,
std : : vector < completion_t > * out ,
bool is_first_call = false )
{
assert ( str ! = NULL ) ;
assert ( wc ! = NULL ) ;
/* Maybe early out for hidden files. We require that the wildcard match these exactly (i.e. a dot); ANY_STRING not allowed */
if ( is_first_call & & str [ 0 ] = = L ' . ' & & wc [ 0 ] ! = L ' . ' )
2013-06-02 16:14:26 +08:00
{
2015-08-04 06:51:27 +08:00
return false ;
}
/* Locate the next wildcard character position, e.g. ANY_CHAR or ANY_STRING */
2015-08-08 04:28:47 +08:00
const size_t next_wc_char_pos = wildcard_find ( wc ) ;
2015-08-04 06:51:27 +08:00
2015-08-04 08:17:15 +08:00
/* Maybe we have no more wildcards at all. This includes the empty string. */
2015-08-04 06:51:27 +08:00
if ( next_wc_char_pos = = wcstring : : npos )
{
string_fuzzy_match_t match = string_fuzzy_match_string ( wc , str ) ;
/* If we're allowing fuzzy match, any match is OK. Otherwise we require a prefix match. */
bool match_acceptable ;
if ( params . expand_flags & EXPAND_FUZZY_MATCH )
2012-11-19 08:30:30 +08:00
{
2015-08-04 06:51:27 +08:00
match_acceptable = match . type ! = fuzzy_match_none ;
}
else
{
match_acceptable = match_type_shares_prefix ( match . type ) ;
2012-11-19 08:30:30 +08:00
}
2015-08-04 06:51:27 +08:00
2015-08-04 13:09:25 +08:00
if ( match_acceptable & & out ! = NULL )
2015-08-04 06:51:27 +08:00
{
/* Wildcard complete */
bool full_replacement = match_type_requires_full_replacement ( match . type ) | | ( flags & COMPLETE_REPLACES_TOKEN ) ;
/* If we are not replacing the token, be careful to only store the part of the string after the wildcard */
assert ( ! full_replacement | | wcslen ( wc ) < = wcslen ( str ) ) ;
wcstring out_completion = full_replacement ? params . orig : str + wcslen ( wc ) ;
wcstring out_desc = resolve_description ( & out_completion , params . desc , params . desc_func ) ;
/* Note: out_completion may be empty if the completion really is empty, e.g. tab-completing 'foo' when a file 'foo' exists. */
complete_flags_t local_flags = flags | ( full_replacement ? COMPLETE_REPLACES_TOKEN : 0 ) ;
append_completion ( out , out_completion , out_desc , local_flags , match ) ;
}
return match_acceptable ;
2013-05-26 06:41:18 +08:00
}
2015-08-04 06:51:27 +08:00
else if ( next_wc_char_pos > 0 )
2013-05-26 06:41:18 +08:00
{
2015-08-04 06:51:27 +08:00
/* Here we have a non-wildcard prefix. Note that we don't do fuzzy matching for stuff before a wildcard, so just do case comparison and then recurse. */
if ( wcsncmp ( str , wc , next_wc_char_pos ) = = 0 )
2012-11-19 08:30:30 +08:00
{
2015-08-04 06:51:27 +08:00
// Normal match
2015-08-04 13:09:25 +08:00
return wildcard_complete_internal ( str + next_wc_char_pos , wc + next_wc_char_pos , params , flags , out ) ;
2015-08-04 06:51:27 +08:00
}
else if ( wcsncasecmp ( str , wc , next_wc_char_pos ) = = 0 )
{
// Case insensitive match
2015-08-04 13:09:25 +08:00
return wildcard_complete_internal ( str + next_wc_char_pos , wc + next_wc_char_pos , params , flags | COMPLETE_REPLACES_TOKEN , out ) ;
2012-11-19 08:30:30 +08:00
}
else
{
2015-08-04 06:51:27 +08:00
// No match
2015-08-04 13:09:25 +08:00
return false ;
2012-11-19 08:30:30 +08:00
}
2015-08-04 13:09:25 +08:00
assert ( 0 & & " Unreachable code reached " ) ;
2012-11-19 08:30:30 +08:00
}
2015-08-04 06:51:27 +08:00
else
2012-11-19 08:30:30 +08:00
{
2015-08-04 06:51:27 +08:00
/* Our first character is a wildcard. */
assert ( next_wc_char_pos = = 0 ) ;
switch ( wc [ 0 ] )
2012-11-19 08:30:30 +08:00
{
2015-08-04 06:51:27 +08:00
case ANY_CHAR :
2013-09-11 17:33:44 +08:00
{
2015-08-04 06:51:27 +08:00
if ( str [ 0 ] = = L ' \0 ' )
{
return false ;
}
else
{
return wildcard_complete_internal ( str + 1 , wc + 1 , params , flags , out ) ;
}
break ;
}
case ANY_STRING :
{
2015-08-08 04:28:47 +08:00
/* Hackish. If this is the last character of the wildcard, then just complete with the empty string. This fixes cases like "f*<tab>" -> "f*o" */
if ( wc [ 1 ] = = L ' \0 ' )
{
return wildcard_complete_internal ( L " " , L " " , params , flags , out ) ;
}
2015-08-04 13:09:25 +08:00
/* Try all submatches. #929: if the recursive call gives us a prefix match, just stop. This is sloppy - what we really want to do is say, once we've seen a match of a particular type, ignore all matches of that type further down the string, such that the wildcard produces the "minimal match.". */
2015-08-04 06:51:27 +08:00
bool has_match = false ;
2015-08-04 13:09:25 +08:00
for ( size_t i = 0 ; str [ i ] ! = L ' \0 ' ; i + + )
2013-09-11 17:33:44 +08:00
{
2015-08-04 13:09:25 +08:00
const size_t before_count = out ? out - > size ( ) : 0 ;
2015-08-04 06:51:27 +08:00
if ( wildcard_complete_internal ( str + i , wc + 1 , params , flags , out ) )
2013-09-11 17:33:44 +08:00
{
2015-08-04 13:09:25 +08:00
/* We found a match */
2015-08-04 06:51:27 +08:00
has_match = true ;
2015-08-04 13:09:25 +08:00
/* If out is NULL, we don't care about the actual matches. If out is not NULL but we have a prefix match, stop there. */
if ( out = = NULL | | has_prefix_match ( out , before_count ) )
2015-08-04 06:51:27 +08:00
{
2015-08-04 13:09:25 +08:00
break ;
2015-08-04 06:51:27 +08:00
}
2013-09-11 17:33:44 +08:00
}
}
2015-08-04 06:51:27 +08:00
return has_match ;
2013-09-11 17:33:44 +08:00
}
2015-08-04 06:51:27 +08:00
case ANY_STRING_RECURSIVE :
/* We don't even try with this one */
return false ;
default :
assert ( 0 & & " Unreachable code reached " ) ;
return false ;
2012-11-19 08:30:30 +08:00
}
}
2015-08-04 06:51:27 +08:00
assert ( 0 & & " Unreachable code reached " ) ;
2005-09-20 21:26:39 +08:00
}
2012-05-09 17:33:42 +08:00
bool wildcard_complete ( const wcstring & str ,
2012-11-19 08:30:30 +08:00
const wchar_t * wc ,
const wchar_t * desc ,
wcstring ( * desc_func ) ( const wcstring & ) ,
2015-07-28 09:45:47 +08:00
std : : vector < completion_t > * out ,
2013-05-26 06:41:18 +08:00
expand_flags_t expand_flags ,
complete_flags_t flags )
2005-09-20 21:26:39 +08:00
{
2015-08-04 13:09:25 +08:00
// Note out may be NULL
assert ( wc ! = NULL ) ;
2016-02-20 07:45:12 +08:00
wc_complete_pack_t params ( str , desc , desc_func , expand_flags ) ;
2015-08-04 06:51:27 +08:00
return wildcard_complete_internal ( str . c_str ( ) , wc , params , flags , out , true /* first call */ ) ;
2005-09-20 21:26:39 +08:00
}
2013-04-21 03:41:02 +08:00
bool wildcard_match ( const wcstring & str , const wcstring & wc , bool leading_dots_fail_to_match )
2005-09-20 21:26:39 +08:00
{
2016-02-20 07:45:12 +08:00
enum fuzzy_match_type_t match = wildcard_match_internal ( str . c_str ( ) , wc . c_str ( ) , leading_dots_fail_to_match , true /* first */ ) ;
2015-08-04 15:32:34 +08:00
return match ! = fuzzy_match_none ;
2005-09-20 21:26:39 +08:00
}
/**
2015-08-04 08:17:15 +08:00
Obtain a description string for the file specified by the filename .
The returned value is a string constant and should not be free ' d .
\ param filename The file for which to find a description string
\ param lstat_res The result of calling lstat on the file
\ param lbuf The struct buf output of calling lstat on the file
\ param stat_res The result of calling stat on the file
\ param buf The struct buf output of calling stat on the file
\ param err The errno value after a failed stat call on the file .
*/
2007-02-25 17:05:24 +08:00
2012-11-19 08:30:30 +08:00
static wcstring file_get_desc ( const wcstring & filename ,
int lstat_res ,
2015-07-20 17:34:57 +08:00
const struct stat & lbuf ,
2012-11-19 08:30:30 +08:00
int stat_res ,
2016-01-04 04:12:42 +08:00
const struct stat & buf ,
2012-11-19 08:30:30 +08:00
int err )
2007-02-25 17:05:24 +08:00
{
2015-08-04 08:17:15 +08:00
2012-11-19 08:30:30 +08:00
if ( ! lstat_res )
2012-11-18 18:23:22 +08:00
{
2012-11-19 08:30:30 +08:00
if ( S_ISLNK ( lbuf . st_mode ) )
2012-11-18 18:23:22 +08:00
{
2012-11-19 08:30:30 +08:00
if ( ! stat_res )
{
if ( S_ISDIR ( buf . st_mode ) )
{
return COMPLETE_DIRECTORY_SYMLINK_DESC ;
}
else
{
2015-08-04 08:17:15 +08:00
if ( buf . st_mode & ( S_IXUSR | S_IXGRP | S_IXOTH ) )
2012-11-19 08:30:30 +08:00
{
2015-08-04 08:17:15 +08:00
2012-11-19 08:30:30 +08:00
if ( waccess ( filename , X_OK ) = = 0 )
{
/*
2015-08-04 08:17:15 +08:00
Weird group permissions and other such
issues make it non - trivial to find out
if we can actually execute a file using
the result from stat . It is much safer
to use the access function , since it
tells us exactly what we want to know .
*/
2012-11-19 08:30:30 +08:00
return COMPLETE_EXEC_LINK_DESC ;
}
}
}
2015-08-04 08:17:15 +08:00
2012-11-19 08:30:30 +08:00
return COMPLETE_SYMLINK_DESC ;
2015-08-04 08:17:15 +08:00
2012-11-18 18:23:22 +08:00
}
2012-11-19 08:30:30 +08:00
else
{
switch ( err )
{
2012-11-19 16:31:03 +08:00
case ENOENT :
{
return COMPLETE_ROTTEN_SYMLINK_DESC ;
}
2015-08-04 08:17:15 +08:00
2012-11-19 16:31:03 +08:00
case ELOOP :
{
return COMPLETE_LOOP_SYMLINK_DESC ;
}
2012-11-19 08:30:30 +08:00
}
/*
2015-08-04 08:17:15 +08:00
On unknown errors we do nothing . The file will be
given the default ' File ' description or one based on the suffix .
*/
2012-11-19 08:30:30 +08:00
}
2015-08-04 08:17:15 +08:00
2012-11-19 08:30:30 +08:00
}
else if ( S_ISCHR ( buf . st_mode ) )
2012-11-18 18:23:22 +08:00
{
2012-11-19 08:30:30 +08:00
return COMPLETE_CHAR_DESC ;
2012-11-18 18:23:22 +08:00
}
2012-11-19 08:30:30 +08:00
else if ( S_ISBLK ( buf . st_mode ) )
{
return COMPLETE_BLOCK_DESC ;
}
else if ( S_ISFIFO ( buf . st_mode ) )
2012-11-18 18:23:22 +08:00
{
2012-11-19 08:30:30 +08:00
return COMPLETE_FIFO_DESC ;
}
else if ( S_ISSOCK ( buf . st_mode ) )
{
return COMPLETE_SOCKET_DESC ;
}
else if ( S_ISDIR ( buf . st_mode ) )
{
return COMPLETE_DIRECTORY_DESC ;
}
else
{
2015-08-04 08:17:15 +08:00
if ( buf . st_mode & ( S_IXUSR | S_IXGRP | S_IXGRP ) )
2012-11-19 08:30:30 +08:00
{
2015-08-04 08:17:15 +08:00
2012-11-19 08:30:30 +08:00
if ( waccess ( filename , X_OK ) = = 0 )
{
/*
2015-08-04 08:17:15 +08:00
Weird group permissions and other such issues
make it non - trivial to find out if we can
actually execute a file using the result from
stat . It is much safer to use the access
function , since it tells us exactly what we want
to know .
*/
2012-11-19 08:30:30 +08:00
return COMPLETE_EXEC_DESC ;
}
}
2012-11-18 18:23:22 +08:00
}
}
2015-08-04 08:17:15 +08:00
2012-11-19 08:30:30 +08:00
return COMPLETE_FILE_DESC ;
2007-02-25 17:05:24 +08:00
}
2015-08-04 08:17:15 +08:00
/** Test if the given file is an executable (if EXECUTABLES_ONLY) or directory (if DIRECTORIES_ONLY).
If it matches , call wildcard_complete ( ) with some description that we make up .
Note that the filename came from a readdir ( ) call , so we know it exists .
*/
static bool wildcard_test_flags_then_complete ( const wcstring & filepath ,
const wcstring & filename ,
const wchar_t * wc ,
expand_flags_t expand_flags ,
std : : vector < completion_t > * out )
2015-08-04 13:09:25 +08:00
{
/* Check if it will match before stat() */
if ( ! wildcard_complete ( filename , wc , NULL , NULL , NULL , expand_flags , 0 ) )
{
return false ;
}
2015-08-04 08:17:15 +08:00
struct stat lstat_buf = { } , stat_buf = { } ;
int stat_res = - 1 ;
int stat_errno = 0 ;
int lstat_res = lwstat ( filepath , & lstat_buf ) ;
if ( lstat_res < 0 )
{
/* lstat failed */
2012-11-18 18:23:22 +08:00
}
else
{
2015-08-04 08:17:15 +08:00
if ( S_ISLNK ( lstat_buf . st_mode ) )
2012-11-19 08:30:30 +08:00
{
2015-08-04 08:17:15 +08:00
stat_res = wstat ( filepath , & stat_buf ) ;
if ( stat_res < 0 )
2012-11-19 08:30:30 +08:00
{
2015-08-04 08:17:15 +08:00
/*
In order to differentiate between e . g . rotten symlinks
and symlink loops , we also need to know the error status of wstat .
*/
stat_errno = errno ;
2012-11-19 08:30:30 +08:00
}
}
else
{
2015-08-04 08:17:15 +08:00
stat_buf = lstat_buf ;
2012-11-19 08:30:30 +08:00
stat_res = lstat_res ;
}
2012-11-18 18:23:22 +08:00
}
2015-08-04 08:17:15 +08:00
const long long file_size = stat_res = = 0 ? stat_buf . st_size : 0 ;
const bool is_directory = stat_res = = 0 & & S_ISDIR ( stat_buf . st_mode ) ;
const bool is_executable = stat_res = = 0 & & S_ISREG ( stat_buf . st_mode ) ;
if ( expand_flags & DIRECTORIES_ONLY )
2012-11-19 08:30:30 +08:00
{
2015-08-04 08:17:15 +08:00
if ( ! is_directory )
2012-08-16 09:20:44 +08:00
{
2015-08-04 08:17:15 +08:00
return false ;
2012-08-16 09:20:44 +08:00
}
2012-11-19 08:30:30 +08:00
}
2015-08-04 08:17:15 +08:00
if ( expand_flags & EXECUTABLES_ONLY )
2012-11-18 18:23:22 +08:00
{
2015-08-04 08:17:15 +08:00
if ( ! is_executable | | waccess ( filepath , X_OK ) ! = 0 )
2012-11-19 08:30:30 +08:00
{
2013-05-26 06:41:18 +08:00
return false ;
2012-11-19 08:30:30 +08:00
}
2012-11-18 18:23:22 +08:00
}
2015-08-04 08:17:15 +08:00
/* Compute the description */
wcstring desc ;
2016-02-18 07:00:38 +08:00
if ( ! ( expand_flags & EXPAND_NO_DESCRIPTIONS ) )
2012-11-19 08:30:30 +08:00
{
2015-08-04 08:17:15 +08:00
desc = file_get_desc ( filepath , lstat_res , lstat_buf , stat_res , stat_buf , stat_errno ) ;
if ( file_size > = 0 )
2014-09-20 15:26:10 +08:00
{
2015-08-04 08:17:15 +08:00
if ( ! desc . empty ( ) )
desc . append ( L " , " ) ;
desc . append ( format_size ( file_size ) ) ;
2014-09-20 15:26:10 +08:00
}
2012-11-19 08:30:30 +08:00
}
2015-08-04 08:17:15 +08:00
2016-02-18 07:00:38 +08:00
/* Append a / if this is a directory. Note this requirement may be the only reason we have to call stat() in some cases. */
2015-08-04 08:17:15 +08:00
if ( is_directory )
{
return wildcard_complete ( filename + L ' / ' , wc , desc . c_str ( ) , NULL , out , expand_flags , COMPLETE_NO_SPACE ) ;
}
else
{
return wildcard_complete ( filename , wc , desc . c_str ( ) , NULL , out , expand_flags , 0 ) ;
}
2012-08-07 17:50:12 +08:00
}
2015-08-02 07:02:46 +08:00
class wildcard_expander_t
{
2016-02-08 17:29:23 +08:00
/* Prefix, i.e. effective working directory */
const wcstring prefix ;
/* The original base we are expanding */
2015-08-04 08:17:15 +08:00
const wcstring original_base ;
/* Original wildcard we are expanding. */
const wchar_t * const original_wildcard ;
2015-08-04 14:34:56 +08:00
/* the set of items we have resolved, used to efficiently avoid duplication */
2015-08-02 07:02:46 +08:00
std : : set < wcstring > completion_set ;
/* the set of file IDs we have visited, used to avoid symlink loops */
std : : set < file_id_t > visited_files ;
/* flags controlling expansion */
const expand_flags_t flags ;
/* resolved items get inserted into here. This is transient of course. */
2015-08-04 14:34:56 +08:00
std : : vector < completion_t > * resolved_completions ;
2015-08-02 07:02:46 +08:00
/* whether we have been interrupted */
bool did_interrupt ;
/* whether we have successfully added any completions */
bool did_add ;
/* We are a trailing slash - expand at the end */
void expand_trailing_slash ( const wcstring & base_dir ) ;
/* Given a directory base_dir, which is opened as base_dir_fp, expand an intermediate segment of the wildcard.
Treat ANY_STRING_RECURSIVE as ANY_STRING .
wc_segment is the wildcard segment for this directory
wc_remainder is the wildcard for subdirectories
*/
void expand_intermediate_segment ( const wcstring & base_dir , DIR * base_dir_fp , const wcstring & wc_segment , const wchar_t * wc_remainder ) ;
2015-08-08 04:28:47 +08:00
/* Given a directory base_dir, which is opened as base_dir_fp, expand an intermediate literal segment.
Use a fuzzy matching algorithm .
*/
void expand_literal_intermediate_segment_with_fuzz ( const wcstring & base_dir , DIR * base_dir_fp , const wcstring & wc_segment , const wchar_t * wc_remainder ) ;
2015-08-02 07:02:46 +08:00
/* Given a directory base_dir, which is opened as base_dir_fp, expand the last segment of the wildcard.
Treat ANY_STRING_RECURSIVE as ANY_STRING .
wc is the wildcard segment to use for matching
wc_remainder is the wildcard for subdirectories
*/
void expand_last_segment ( const wcstring & base_dir , DIR * base_dir_fp , const wcstring & wc ) ;
2015-08-04 06:58:37 +08:00
/* Indicate whether we should cancel wildcard expansion. This latches 'interrupt' */
bool interrupted ( )
{
if ( ! did_interrupt )
{
did_interrupt = ( is_main_thread ( ) ? reader_interrupted ( ) : reader_thread_job_is_stale ( ) ) ;
}
return did_interrupt ;
}
2015-08-04 14:34:56 +08:00
void add_expansion_result ( const wcstring & result )
{
/* This function is only for the non-completions case */
assert ( ! ( this - > flags & EXPAND_FOR_COMPLETIONS ) ) ;
if ( this - > completion_set . insert ( result ) . second )
{
append_completion ( this - > resolved_completions , result ) ;
this - > did_add = true ;
}
}
2016-02-18 07:00:38 +08:00
/* Given a start point as an absolute path, for any directory that has exactly one non-hidden entity in it which is itself a directory, return that. The result is a relative path. For example, if start_point is '/usr' we may return 'local/bin/'.
The result does not have a leading slash , but does have a trailing slash if non - empty . */
wcstring descend_unique_hierarchy ( const wcstring & start_point )
{
assert ( ! start_point . empty ( ) & & start_point . at ( 0 ) = = L ' / ' ) ;
wcstring unique_hierarchy ;
wcstring abs_unique_hierarchy = start_point ;
bool stop_descent = false ;
DIR * dir ;
while ( ! stop_descent & & ( dir = wopendir ( abs_unique_hierarchy ) ) )
{
/* We keep track of the single unique_entry entry. If we get more than one, it's not unique and we stop the descent. */
wcstring unique_entry ;
bool child_is_dir ;
wcstring child_entry ;
while ( wreaddir_resolving ( dir , abs_unique_hierarchy , child_entry , & child_is_dir ) )
{
if ( child_entry . empty ( ) | | child_entry . at ( 0 ) = = L ' . ' )
{
/* Either hidden, or . and .. entries. Skip them. */
continue ;
}
else if ( child_is_dir & & unique_entry . empty ( ) )
{
/* First candidate */
unique_entry = child_entry ;
}
else
{
/* We either have two or more candidates, or the child is not a directory. We're done. */
stop_descent = true ;
break ;
}
}
/* We stop if we got two or more entries; also stop if we got zero. */
if ( unique_entry . empty ( ) )
{
stop_descent = true ;
}
if ( ! stop_descent )
{
/* We have an entry in the unique hierarchy! */
append_path_component ( unique_hierarchy , unique_entry ) ;
unique_hierarchy . push_back ( L ' / ' ) ;
append_path_component ( abs_unique_hierarchy , unique_entry ) ;
abs_unique_hierarchy . push_back ( L ' / ' ) ;
}
closedir ( dir ) ;
}
return unique_hierarchy ;
}
2015-08-04 14:58:20 +08:00
void try_add_completion_result ( const wcstring & filepath , const wcstring & filename , const wcstring & wildcard )
2015-08-04 14:34:56 +08:00
{
/* This function is only for the completions case */
assert ( this - > flags & EXPAND_FOR_COMPLETIONS ) ;
2016-02-18 07:00:38 +08:00
wcstring abs_path = this - > prefix ;
append_path_component ( abs_path , filepath ) ;
2015-08-04 14:46:41 +08:00
size_t before = this - > resolved_completions - > size ( ) ;
2016-02-18 07:00:38 +08:00
if ( wildcard_test_flags_then_complete ( abs_path , filename , wildcard . c_str ( ) , this - > flags , this - > resolved_completions ) )
2015-08-04 14:34:56 +08:00
{
2015-08-04 14:46:41 +08:00
/* Hack. We added this completion result based on the last component of the wildcard.
Prepend all prior components of the wildcard to each completion that replaces its token . */
2015-08-04 14:58:20 +08:00
size_t wc_len = wildcard . size ( ) ;
size_t orig_wc_len = wcslen ( this - > original_wildcard ) ;
assert ( wc_len < = orig_wc_len ) ;
const wcstring wc_base ( this - > original_wildcard , orig_wc_len - wc_len ) ;
2015-08-04 14:46:41 +08:00
size_t after = this - > resolved_completions - > size ( ) ;
for ( size_t i = before ; i < after ; i + + )
{
completion_t & c = this - > resolved_completions - > at ( i ) ;
c . prepend_token_prefix ( wc_base ) ;
c . prepend_token_prefix ( this - > original_base ) ;
}
2016-02-18 07:00:38 +08:00
/* Hack. Implement EXPAND_SPECIAL_CD by descending the deepest unique hierarchy we can, and then appending any components to each new result. */
if ( flags & EXPAND_SPECIAL_CD )
{
wcstring unique_hierarchy = this - > descend_unique_hierarchy ( abs_path ) ;
if ( ! unique_hierarchy . empty ( ) )
{
for ( size_t i = before ; i < after ; i + + )
{
completion_t & c = this - > resolved_completions - > at ( i ) ;
c . completion . append ( unique_hierarchy ) ;
}
}
}
2015-08-04 14:34:56 +08:00
this - > did_add = true ;
}
}
2016-02-08 17:29:23 +08:00
/* Helper to resolve using our prefix */
DIR * open_dir ( const wcstring & base_dir ) const
2015-08-02 07:02:46 +08:00
{
2016-02-08 17:29:23 +08:00
wcstring path = this - > prefix ;
append_path_component ( path , base_dir ) ;
return wopendir ( path ) ;
2015-08-02 07:02:46 +08:00
}
public :
2016-02-20 07:45:12 +08:00
wildcard_expander_t ( const wcstring & pref , const wcstring & orig_base , const wchar_t * orig_wc , expand_flags_t f , std : : vector < completion_t > * r ) :
2016-02-08 17:29:23 +08:00
prefix ( pref ) ,
2015-08-04 08:17:15 +08:00
original_base ( orig_base ) ,
original_wildcard ( orig_wc ) ,
flags ( f ) ,
2015-08-04 14:34:56 +08:00
resolved_completions ( r ) ,
2015-08-04 08:17:15 +08:00
did_interrupt ( false ) ,
did_add ( false )
2015-08-02 07:02:46 +08:00
{
2015-08-04 14:34:56 +08:00
assert ( resolved_completions ! = NULL ) ;
2015-08-02 07:02:46 +08:00
/* Insert initial completions into our set to avoid duplicates */
2015-08-04 14:34:56 +08:00
for ( std : : vector < completion_t > : : const_iterator iter = resolved_completions - > begin ( ) ; iter ! = resolved_completions - > end ( ) ; + + iter )
2015-08-02 07:02:46 +08:00
{
this - > completion_set . insert ( iter - > completion ) ;
}
}
/* Do wildcard expansion. This is recursive. */
void expand ( const wcstring & base_dir , const wchar_t * wc ) ;
2015-08-04 06:58:37 +08:00
int status_code ( ) const
2015-08-02 07:02:46 +08:00
{
2015-08-04 06:58:37 +08:00
if ( this - > did_interrupt )
2015-08-02 07:02:46 +08:00
{
2015-08-04 06:58:37 +08:00
return - 1 ;
}
else
{
return this - > did_add ? 1 : 0 ;
2015-08-02 07:02:46 +08:00
}
}
} ;
void wildcard_expander_t : : expand_trailing_slash ( const wcstring & base_dir )
{
if ( interrupted ( ) )
{
return ;
}
2015-08-04 07:36:10 +08:00
if ( ! ( flags & EXPAND_FOR_COMPLETIONS ) )
2015-08-02 07:02:46 +08:00
{
/* Trailing slash and not accepting incomplete, e.g. `echo /tmp/`. Insert this file if it exists. */
2015-09-28 04:32:12 +08:00
if ( waccess ( base_dir , F_OK ) = = 0 )
2015-08-02 07:02:46 +08:00
{
2015-08-04 14:34:56 +08:00
this - > add_expansion_result ( base_dir ) ;
2015-08-02 07:02:46 +08:00
}
}
else
{
/* Trailing slashes and accepting incomplete, e.g. `echo /tmp/<tab>`. Everything is added. */
DIR * dir = open_dir ( base_dir ) ;
if ( dir )
{
wcstring next ;
while ( wreaddir ( dir , next ) & & ! interrupted ( ) )
{
if ( ! next . empty ( ) & & next . at ( 0 ) ! = L ' . ' )
{
2015-08-04 14:34:56 +08:00
this - > try_add_completion_result ( base_dir + next , next , L " " ) ;
2015-08-02 07:02:46 +08:00
}
}
closedir ( dir ) ;
}
}
}
void wildcard_expander_t : : expand_intermediate_segment ( const wcstring & base_dir , DIR * base_dir_fp , const wcstring & wc_segment , const wchar_t * wc_remainder )
{
wcstring name_str ;
2015-08-09 05:52:04 +08:00
while ( ! interrupted ( ) & & wreaddir_for_dirs ( base_dir_fp , & name_str ) )
2015-08-02 07:02:46 +08:00
{
/* Note that it's critical we ignore leading dots here, else we may descend into . and .. */
if ( ! wildcard_match ( name_str , wc_segment , true ) )
{
/* Doesn't match the wildcard for this segment, skip it */
continue ;
}
wcstring full_path = base_dir + name_str ;
struct stat buf ;
if ( 0 ! = wstat ( full_path , & buf ) | | ! S_ISDIR ( buf . st_mode ) )
{
/* We either can't stat it, or we did but it's not a directory */
continue ;
}
const file_id_t file_id = file_id_t : : file_id_from_stat ( & buf ) ;
if ( ! this - > visited_files . insert ( file_id ) . second )
{
/* Symlink loop! This directory was already visited, so skip it */
continue ;
}
/* We made it through. Perform normal wildcard expansion on this new directory, starting at our tail_wc, which includes the ANY_STRING_RECURSIVE guy. */
full_path . push_back ( L ' / ' ) ;
this - > expand ( full_path , wc_remainder ) ;
2015-09-28 08:19:17 +08:00
/* Now remove the visited file. This is for #2414: only directories "beneath" us should be considered visited. */
this - > visited_files . erase ( file_id ) ;
2015-08-02 07:02:46 +08:00
}
}
2015-08-08 04:28:47 +08:00
void wildcard_expander_t : : expand_literal_intermediate_segment_with_fuzz ( const wcstring & base_dir , DIR * base_dir_fp , const wcstring & wc_segment , const wchar_t * wc_remainder )
{
// This only works with tab completions
// Ordinary wildcard expansion should never go fuzzy
wcstring name_str ;
2015-08-09 05:52:04 +08:00
while ( ! interrupted ( ) & & wreaddir_for_dirs ( base_dir_fp , & name_str ) )
2015-08-08 04:28:47 +08:00
{
/* Don't bother with . and .. */
if ( contains ( name_str , L " . " , L " .. " ) )
{
continue ;
}
// Skip cases that don't match or match exactly
// The match-exactly case was handled directly in expand()
const string_fuzzy_match_t match = string_fuzzy_match_string ( wc_segment , name_str ) ;
if ( match . type = = fuzzy_match_none | | match . type = = fuzzy_match_exact )
{
continue ;
}
wcstring new_full_path = base_dir + name_str ;
new_full_path . push_back ( L ' / ' ) ;
struct stat buf ;
if ( 0 ! = wstat ( new_full_path , & buf ) | | ! S_ISDIR ( buf . st_mode ) )
{
/* We either can't stat it, or we did but it's not a directory */
continue ;
}
// Ok, this directory matches. Recurse to it.
// Then perform serious surgery on each result!
// Each result was computed with a prefix of original_wildcard
// We need to replace our segment of that with our name_str
// We also have to mark the completion as replacing and fuzzy
const size_t before = this - > resolved_completions - > size ( ) ;
this - > expand ( new_full_path , wc_remainder ) ;
const size_t after = this - > resolved_completions - > size ( ) ;
assert ( before < = after ) ;
for ( size_t i = before ; i < after ; i + + )
{
completion_t * c = & this - > resolved_completions - > at ( i ) ;
// Mark the completion as replacing
if ( ! ( c - > flags & COMPLETE_REPLACES_TOKEN ) )
{
c - > flags | = COMPLETE_REPLACES_TOKEN ;
c - > prepend_token_prefix ( this - > original_wildcard ) ;
c - > prepend_token_prefix ( this - > original_base ) ;
}
// Ok, it's now replacing and is prefixed with the segment base, plus our original wildcard
// Replace our segment with name_str
// Our segment starts at the length of the original wildcard, minus what we have left to process, minus the length of our segment
// This logic is way too picky. Need to clean this up.
// One possibility is to send the "resolved wildcard" along with the actual wildcard
const size_t original_wildcard_len = wcslen ( this - > original_wildcard ) ;
const size_t wc_remainder_len = wcslen ( wc_remainder ) ;
const size_t segment_len = wc_segment . length ( ) ;
assert ( c - > completion . length ( ) > = original_wildcard_len ) ;
const size_t segment_start = original_wildcard_len + this - > original_base . size ( ) - wc_remainder_len - wc_segment . length ( ) - 1 ; // -1 for the slash after our segment
assert ( segment_start < original_wildcard_len ) ;
assert ( c - > completion . substr ( segment_start , segment_len ) = = wc_segment ) ;
c - > completion . replace ( segment_start , segment_len , name_str ) ;
// And every match must be made at least as fuzzy as ours
if ( match . compare ( c - > match ) > 0 )
{
// Our match is fuzzier
c - > match = match ;
}
}
}
}
2015-08-02 07:02:46 +08:00
void wildcard_expander_t : : expand_last_segment ( const wcstring & base_dir , DIR * base_dir_fp , const wcstring & wc )
{
wcstring name_str ;
while ( wreaddir ( base_dir_fp , name_str ) )
{
2015-08-04 07:36:10 +08:00
if ( flags & EXPAND_FOR_COMPLETIONS )
2015-08-02 07:02:46 +08:00
{
2015-08-04 14:58:20 +08:00
this - > try_add_completion_result ( base_dir + name_str , name_str , wc ) ;
2015-08-02 07:02:46 +08:00
}
else
{
2015-08-03 08:58:37 +08:00
// Normal wildcard expansion, not for completions
2015-08-02 07:02:46 +08:00
if ( wildcard_match ( name_str , wc , true /* skip files with leading dots */ ) )
{
2015-08-04 14:34:56 +08:00
this - > add_expansion_result ( base_dir + name_str ) ;
2015-08-02 07:02:46 +08:00
}
}
}
}
/**
The real implementation of wildcard expansion is in this
function . Other functions are just wrappers around this one .
This function traverses the relevant directory tree looking for
matches , and recurses when needed to handle wildcrards spanning
multiple components and recursive wildcards .
Because this function calls itself recursively with substrings ,
it ' s important that the parameters be raw pointers instead of wcstring ,
which would be too expensive to construct for all substrings .
Args :
base_dir : the " working directory " against which the wildcard is to be resolved
wc : the wildcard string itself , e . g . foo * bar / baz ( where * is acutally ANY_CHAR )
*/
void wildcard_expander_t : : expand ( const wcstring & base_dir , const wchar_t * wc )
{
assert ( wc ! = NULL ) ;
if ( interrupted ( ) )
{
return ;
}
/* Get the current segment and compute interesting properties about it. */
const size_t wc_len = wcslen ( wc ) ;
const wchar_t * const next_slash = wcschr ( wc , L ' / ' ) ;
const bool is_last_segment = ( next_slash = = NULL ) ;
const size_t wc_segment_len = next_slash ? next_slash - wc : wc_len ;
const wcstring wc_segment = wcstring ( wc , wc_segment_len ) ;
const bool segment_has_wildcards = wildcard_has ( wc_segment , true /* internal, i.e. look for ANY_CHAR instead of ? */ ) ;
if ( wc_segment . empty ( ) )
{
/* Handle empty segment */
assert ( ! segment_has_wildcards ) ;
if ( is_last_segment )
{
this - > expand_trailing_slash ( base_dir ) ;
}
else
{
/* Multiple adjacent slashes in the wildcard. Just skip them. */
this - > expand ( base_dir , next_slash + 1 ) ;
}
}
else if ( ! segment_has_wildcards & & ! is_last_segment )
{
/* Literal intermediate match. Note that we may not be able to actually read the directory (#2099) */
assert ( next_slash ! = NULL ) ;
2015-08-04 15:32:34 +08:00
const wchar_t * wc_remainder = next_slash ;
while ( * wc_remainder = = L ' / ' )
{
wc_remainder + + ;
}
2015-08-02 07:02:46 +08:00
/* This just trumps everything */
2015-08-04 15:32:34 +08:00
size_t before = this - > resolved_completions - > size ( ) ;
this - > expand ( base_dir + wc_segment + L ' / ' , wc_remainder ) ;
2015-10-04 06:44:57 +08:00
/* Maybe try a fuzzy match (#94) if nothing was found with the literal match. Respect EXPAND_NO_DIRECTORY_ABBREVIATIONS (#2413). */
bool allow_fuzzy = ( this - > flags & ( EXPAND_FUZZY_MATCH | EXPAND_NO_FUZZY_DIRECTORIES ) ) = = EXPAND_FUZZY_MATCH ;
if ( allow_fuzzy & & this - > resolved_completions - > size ( ) = = before )
2015-08-04 15:32:34 +08:00
{
2015-08-08 04:28:47 +08:00
assert ( this - > flags & EXPAND_FOR_COMPLETIONS ) ;
2015-08-04 15:32:34 +08:00
DIR * base_dir_fd = open_dir ( base_dir ) ;
if ( base_dir_fd ! = NULL )
{
2015-08-08 04:28:47 +08:00
this - > expand_literal_intermediate_segment_with_fuzz ( base_dir , base_dir_fd , wc_segment , wc_remainder ) ;
2015-08-04 15:32:34 +08:00
closedir ( base_dir_fd ) ;
}
}
2015-08-02 07:02:46 +08:00
}
else
{
assert ( ! wc_segment . empty ( ) & & ( segment_has_wildcards | | is_last_segment ) ) ;
DIR * dir = open_dir ( base_dir ) ;
if ( dir )
{
if ( is_last_segment )
{
/* Last wildcard segment, nonempty wildcard */
this - > expand_last_segment ( base_dir , dir , wc_segment ) ;
}
else
{
2015-09-28 08:33:00 +08:00
/* Not the last segment, nonempty wildcard */
2015-08-02 07:02:46 +08:00
assert ( next_slash ! = NULL ) ;
const wchar_t * wc_remainder = next_slash ;
while ( * wc_remainder = = L ' / ' )
{
wc_remainder + + ;
}
this - > expand_intermediate_segment ( base_dir , dir , wc_segment , wc_remainder ) ;
}
/* Recursive wildcards require special handling */
size_t asr_idx = wc_segment . find ( ANY_STRING_RECURSIVE ) ;
if ( asr_idx ! = wcstring : : npos )
{
2015-08-03 08:34:05 +08:00
/* Construct a "head + any" wildcard for matching stuff in this directory, and an "any + tail" wildcard for matching stuff in subdirectories. Note that the ANY_STRING_RECURSIVE character is present in both the head and the tail. */
const wcstring head_any ( wc_segment , 0 , asr_idx + 1 ) ;
const wchar_t * any_tail = wc + asr_idx ;
assert ( head_any . at ( head_any . size ( ) - 1 ) = = ANY_STRING_RECURSIVE ) ;
assert ( any_tail [ 0 ] = = ANY_STRING_RECURSIVE ) ;
2015-08-02 07:02:46 +08:00
rewinddir ( dir ) ;
2015-08-03 08:34:05 +08:00
this - > expand_intermediate_segment ( base_dir , dir , head_any , any_tail ) ;
2015-08-02 07:02:46 +08:00
}
closedir ( dir ) ;
}
}
}
2007-03-25 03:07:38 +08:00
2016-02-08 17:29:23 +08:00
int wildcard_expand_string ( const wcstring & wc , const wcstring & working_directory , expand_flags_t flags , std : : vector < completion_t > * output )
2011-12-27 11:18:46 +08:00
{
2015-07-28 09:45:47 +08:00
assert ( output ! = NULL ) ;
2015-08-08 04:28:47 +08:00
/* Fuzzy matching only if we're doing completions */
assert ( ( flags & ( EXPAND_FUZZY_MATCH | EXPAND_FOR_COMPLETIONS ) ) ! = EXPAND_FUZZY_MATCH ) ;
2016-02-18 07:00:38 +08:00
/* EXPAND_SPECIAL_CD requires DIRECTORIES_ONLY and EXPAND_FOR_COMPLETIONS and EXPAND_NO_DESCRIPTIONS */
assert ( ! ( flags & EXPAND_SPECIAL_CD ) | |
( ( flags & DIRECTORIES_ONLY ) & & ( flags & EXPAND_FOR_COMPLETIONS ) & & ( flags & EXPAND_NO_DESCRIPTIONS ) ) ) ;
2015-08-02 07:02:46 +08:00
/* Hackish fix for 1631. We are about to call c_str(), which will produce a string truncated at any embedded nulls. We could fix this by passing around the size, etc. However embedded nulls are never allowed in a filename, so we just check for them and return 0 (no matches) if there is an embedded null. */
2014-08-25 05:28:31 +08:00
if ( wc . find ( L ' \0 ' ) ! = wcstring : : npos )
{
return 0 ;
}
2015-08-08 23:44:04 +08:00
2016-02-08 17:29:23 +08:00
/* Compute the prefix and base dir. The prefix is what we prepend for filesystem operations (i.e. the working directory), the base_dir is the part of the wildcard consumed thus far, which we also have to append. The difference is that the base_dir is returned as part of the expansion, and the prefix is not.
Check for a leading slash . If we find one , we have an absolute path : the prefix is empty , the base dir is / , and the wildcard is the remainder . If we don ' t find one , the prefix is the working directory , there ' s base dir is empty .
*/
wcstring prefix , base_dir , effective_wc ;
if ( string_prefixes_string ( L " / " , wc ) )
{
prefix = L " " ;
base_dir = L " / " ;
effective_wc = wc . substr ( 1 ) ;
}
else
{
prefix = working_directory ;
base_dir = L " " ;
effective_wc = wc ;
}
wildcard_expander_t expander ( prefix , base_dir , effective_wc . c_str ( ) , flags , output ) ;
2015-08-08 23:44:04 +08:00
expander . expand ( base_dir , wc . c_str ( ) ) ;
return expander . status_code ( ) ;
2011-12-27 11:18:46 +08:00
}