aboutsummaryrefslogtreecommitdiffhomepage
path: root/complete.c
diff options
context:
space:
mode:
authorGravatar axel <axel@liljencrantz.se>2005-09-20 23:26:39 +1000
committerGravatar axel <axel@liljencrantz.se>2005-09-20 23:26:39 +1000
commit149594f974350bb364a76c73b91b1d5ffddaa1fa (patch)
tree95650e9982d5fabe4bd805d94c5d700cbbc1ca7f /complete.c
Initial revision
darcs-hash:20050920132639-ac50b-fa3b476891e1f5f67207cf4cc7bf623834cc5edc.gz
Diffstat (limited to 'complete.c')
-rw-r--r--complete.c2227
1 files changed, 2227 insertions, 0 deletions
diff --git a/complete.c b/complete.c
new file mode 100644
index 00000000..ea6999cb
--- /dev/null
+++ b/complete.c
@@ -0,0 +1,2227 @@
+/** \file complete.c
+ Functions related to tab-completion.
+
+ These functions are used for storing and retrieving tab-completion data, as well as for performing tab-completion.
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <termios.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <signal.h>
+
+#include "config.h"
+#include "util.h"
+#include "tokenizer.h"
+#include "wildcard.h"
+#include "proc.h"
+#include "parser.h"
+#include "function.h"
+#include "complete.h"
+#include "builtin.h"
+#include "env.h"
+#include "exec.h"
+#include "expand.h"
+#include "common.h"
+#include "reader.h"
+#include "history.h"
+#include "intern.h"
+
+#include "wutil.h"
+
+
+/*
+ Completion description strings for files
+
+ There are a few other completion description strings defined in expand.c
+*/
+
+/**
+ Description for ~USER completion
+*/
+#define COMPLETE_USER_DESC COMPLETE_SEP_STR L"User home"
+
+/**
+ Description for short variables. The value is concatenated to this description
+*/
+#define COMPLETE_VAR_DESC_VAL COMPLETE_SEP_STR L"Variable: "
+
+/**
+ Description for generic executable
+*/
+#define COMPLETE_EXEC_DESC COMPLETE_SEP_STR L"Executable"
+/**
+ Description for link to executable
+*/
+#define COMPLETE_EXEC_LINK_DESC COMPLETE_SEP_STR L"Executable link"
+
+/**
+ Description for regular file
+*/
+#define COMPLETE_FILE_DESC COMPLETE_SEP_STR L"File"
+/**
+ Description for character device
+*/
+#define COMPLETE_CHAR_DESC COMPLETE_SEP_STR L"Character device"
+/**
+ Description for block device
+*/
+#define COMPLETE_BLOCK_DESC COMPLETE_SEP_STR L"Block device"
+/**
+ Description for fifo buffer
+*/
+#define COMPLETE_FIFO_DESC COMPLETE_SEP_STR L"Fifo"
+/**
+ Description for symlink
+*/
+#define COMPLETE_SYMLINK_DESC COMPLETE_SEP_STR L"Symbolic link"
+/**
+ Description for Rotten symlink
+*/
+#define COMPLETE_ROTTEN_SYMLINK_DESC COMPLETE_SEP_STR L"Rotten symbolic link"
+/**
+ Description for socket
+*/
+#define COMPLETE_SOCKET_DESC COMPLETE_SEP_STR L"Socket"
+/**
+ Description for directory
+*/
+#define COMPLETE_DIRECTORY_DESC COMPLETE_SEP_STR L"Directory"
+
+/**
+ Description for function
+*/
+#define COMPLETE_FUNCTION_DESC COMPLETE_SEP_STR L"Function"
+/**
+ Description for builtin command
+*/
+#define COMPLETE_BUILTIN_DESC COMPLETE_SEP_STR L"Builtin"
+
+/**
+ The command to run to get a description from a file suffix
+*/
+#define SUFFIX_CMD_STR L"mimedb 2>/dev/null -fd "
+
+/**
+ The maximum number of commands on which to perform description
+ lookup. The lookup process is quite time consuming, so this should
+ be set to a pretty low number.
+*/
+#define MAX_CMD_DESC_LOOKUP 10
+
+/**
+ Condition cache value returned from hashtable when this condition
+ has not yet been tested
+*/
+#define CC_NOT_TESTED 0
+
+/**
+ Condition cache value returned from hashtable when the condition is met
+*/
+#define CC_TRUE L"true"
+
+/**
+ Condition cache value returned from hashtable when the condition is not met
+*/
+#define CC_FALSE L"false"
+
+/**
+ Struct describing a completion option entry.
+
+ If short_opt and long_opt are both zero, the comp field must not be
+ empty and contains a list of arguments to the command.
+
+ If either short_opt or long_opt are non-zero, they specify a switch
+ for the command. If \c comp is also not empty, it contains a list
+ of arguments that may only follow after the specified switch.
+
+*/
+typedef struct complete_entry_opt
+{
+ /** Short style option */
+ wchar_t short_opt;
+ /** Long style option */
+ const wchar_t *long_opt;
+ /** Arguments to the option */
+ const wchar_t *comp;
+ /** Description of the completion */
+ const wchar_t *desc;
+ /** Condition under which to use the option */
+ const wchar_t *condition;
+ /** Must be one of the values SHARED, NO_FILES, NO_COMMON, EXCLUSIVE. */
+ int result_mode;
+ /** Next option in the linked list */
+ /** True if old style long options are used */
+ int old_mode;
+ struct complete_entry_opt *next;
+}
+ complete_entry_opt;
+
+/**
+ Struct describing a command completion
+*/
+typedef struct complete_entry
+{
+ /** True if command is a path */
+ int cmd_type;
+
+ /** Command string */
+ const wchar_t *cmd;
+ /** String containing all short option characters */
+ wchar_t *short_opt_str;
+ /** Linked list of all options */
+ complete_entry_opt *first_option;
+ /** Next command completion in the linked list */
+ struct complete_entry *next;
+ /** True if no other options than the ones supplied are possible */
+ int authorative;
+}
+ complete_entry;
+
+/** First node in the linked list of all completion entries */
+static complete_entry *first_entry=0;
+
+/** Hashtable containing all descriptions that describe an executable */
+static hash_table_t *suffix_hash=0;
+
+/**
+ Table of completions conditions that have already been tested and
+ the corresponding test results
+*/
+static hash_table_t *condition_cache=0;
+
+/**
+ Set of commands for which completions have already been loaded
+*/
+static hash_table_t *loaded_completions=0;
+
+void complete_init()
+{
+}
+
+/**
+ This command clears the cache of condition tests created by \c condition_test().
+*/
+static void condition_cache_clear()
+{
+ if( condition_cache )
+ {
+ hash_destroy( condition_cache );
+ free( condition_cache );
+ condition_cache = 0;
+ }
+}
+
+/**
+ Test if the specified script returns zero. The result is cached, so
+ that if multiple completions use the same condition, it needs only
+ be evaluated once. condition_cache_clear must be called after a
+ completion run to make sure that there are no stale completions.
+*/
+static int condition_test( const wchar_t *condition )
+{
+ const void *test_res;
+
+ if( !condition || !wcslen(condition) )
+ {
+// fwprintf( stderr, L"No condition specified\n" );
+ return 1;
+ }
+
+ if( !condition_cache )
+ {
+ condition_cache = malloc( sizeof( hash_table_t ) );
+ if( !condition_cache )
+ {
+ die_mem();
+
+ }
+
+ hash_init( condition_cache,
+ &hash_wcs_func,
+ &hash_wcs_cmp );
+
+
+ }
+
+ test_res = hash_get( condition_cache, condition );
+
+ if (test_res == CC_NOT_TESTED )
+ {
+ test_res = exec_subshell( condition, 0 )?CC_FALSE:CC_TRUE;
+// debug( 1, L"Eval returns %ls for '%ls'", test_res, condition );
+
+ hash_put( condition_cache, condition, test_res );
+
+ /*
+ Restore previous status information
+ */
+ }
+
+ if( test_res == CC_TRUE )
+ {
+// debug( 1, L"Use conditional completion on condition %ls", condition );
+ return 1;
+ }
+
+// debug( 1, L"Skip conditional completion on condition %ls", condition );
+ return 0;
+
+}
+
+
+/**
+ Recursively free all complete_entry_opt structs and their contents
+*/
+static void complete_free_opt_recursive( complete_entry_opt *o )
+{
+ if( o->next != 0 )
+ complete_free_opt_recursive( o->next );
+ free(o);
+}
+
+/**
+ Free a complete_entry and its contents
+*/
+static void complete_free_entry( complete_entry *c )
+{
+// free( c->cmd );
+ free( c->short_opt_str );
+ complete_free_opt_recursive( c->first_option );
+ free( c );
+}
+
+/**
+ Free hash key and hash value
+*/
+static void clear_hash_entry( const void *key, const void *data )
+{
+ free( (void *)key );
+ free( (void *)data );
+}
+
+
+static void clear_hash_value( const void *key, const void *data )
+{
+ free( (void *)data );
+}
+
+
+void complete_destroy()
+{
+ complete_entry *i=first_entry, *prev;
+ while( i )
+ {
+ prev = i;
+ i=i->next;
+ complete_free_entry( prev );
+ }
+
+ if( suffix_hash )
+ {
+ hash_foreach( suffix_hash, &clear_hash_entry );
+ hash_destroy( suffix_hash );
+ free( suffix_hash );
+ }
+
+ if( loaded_completions )
+ {
+ hash_foreach( loaded_completions,
+ &clear_hash_value );
+ hash_destroy( loaded_completions );
+ free( loaded_completions );
+ }
+
+}
+
+/**
+ Search for an exactly matching completion entry
+*/
+static complete_entry *complete_find_exact_entry( const wchar_t *cmd,
+ const int cmd_type )
+{
+ complete_entry *i;
+ for( i=first_entry; i; i=i->next )
+ {
+ if( ( wcscmp(cmd, i->cmd)==0) && ( cmd_type == i->cmd_type ))
+ {
+ return i;
+ }
+ }
+ return 0;
+}
+
+void complete_add( const wchar_t *cmd,
+ int cmd_type,
+ wchar_t short_opt,
+ const wchar_t *long_opt,
+ int old_mode,
+ int result_mode,
+ int authorative,
+ const wchar_t *condition,
+ const wchar_t *comp,
+ const wchar_t *desc )
+{
+ complete_entry *c =
+ complete_find_exact_entry( cmd, cmd_type );
+ complete_entry_opt *opt;
+ wchar_t *tmp;
+
+
+ if( c == 0 )
+ {
+ if( !(c = malloc( sizeof(complete_entry) )))
+ die_mem();
+
+
+ c->next = first_entry;
+ first_entry = c;
+
+ c->first_option = 0;
+
+ c->cmd = intern( cmd );
+ c->cmd_type = cmd_type;
+ c->short_opt_str = wcsdup(L"");
+ }
+
+/* wprintf( L"Add completion to option (short %lc, long %ls)\n", short_opt, long_opt );*/
+ if( !(opt = malloc( sizeof( complete_entry_opt ) )))
+ die_mem();
+
+ opt->next = c->first_option;
+ c->first_option = opt;
+ c->authorative = authorative;
+ if( short_opt != L'\0' )
+ {
+ int len = 1 + ((result_mode & NO_COMMON) != 0);
+ c->short_opt_str =
+ realloc( c->short_opt_str,
+ sizeof(wchar_t)*(wcslen( c->short_opt_str ) + 1 + len) );
+ wcsncat( c->short_opt_str,
+ &short_opt, 1 );
+ if( len == 2 )
+ wcscat( c->short_opt_str, L":" );
+ }
+
+ opt->short_opt = short_opt;
+ opt->result_mode = result_mode;
+ opt->old_mode=old_mode;
+
+ opt->comp = intern(comp);
+ opt->condition = intern(condition);
+ opt->long_opt = intern( long_opt );
+
+ if( desc && wcslen( desc ) )
+ {
+ tmp = wcsdupcat( COMPLETE_SEP_STR, desc );
+ opt->desc = intern( tmp );
+ free( tmp );
+ }
+ else
+ opt->desc = L"";
+}
+
+void complete_remove( const wchar_t *cmd,
+ int cmd_type,
+ wchar_t short_opt,
+ const wchar_t *long_opt )
+{
+ complete_entry *e, *eprev=0, *enext=0;
+ for( e = first_entry; e; e=enext )
+ {
+ enext=e->next;
+
+ if( (cmd_type == e->cmd_type ) &&
+ ( wcscmp( cmd, e->cmd) == 0 ) )
+ {
+ complete_entry_opt *o, *oprev=0, *onext=0;
+
+ if(( short_opt == 0 ) && (long_opt == 0 ) )
+ {
+ complete_free_opt_recursive( e->first_option );
+ e->first_option=0;
+ }
+ else
+ {
+
+ for( o= e->first_option; o; o=onext )
+ {
+ onext=o->next;
+
+ if( ( short_opt==o->short_opt ) ||
+ ( wcscmp( long_opt, o->long_opt ) == 0 ) )
+ {
+ wchar_t *pos;
+ /* fwprintf( stderr,
+ L"remove option -%lc --%ls\n",
+ o->short_opt?o->short_opt:L' ',
+ o->long_opt );
+ */
+ if( o->short_opt )
+ {
+ pos = wcschr( e->short_opt_str,
+ o->short_opt );
+ if( pos )
+ {
+ wchar_t *pos2 = pos+1;
+ while( *pos2 == L':' )
+ pos2++;
+
+ memmove( pos,
+ pos2,
+ sizeof(wchar_t)*wcslen(pos2) );
+ }
+ }
+
+ if( oprev == 0 )
+ {
+ e->first_option = o->next;
+ }
+ else
+ {
+ oprev->next = o->next;
+ }
+ free( o );
+ }
+ else
+ oprev = o;
+ }
+ }
+
+ if( e && (e->first_option == 0) )
+ {
+ if( eprev == 0 )
+ {
+ first_entry = e->next;
+ }
+ else
+ {
+ eprev->next = e->next;
+ }
+
+ free( e->short_opt_str );
+ free( e );
+ e=0;
+ }
+
+ }
+
+ if( e )
+ eprev = e;
+ }
+}
+
+/**
+ Find the full path and commandname from a command string. the
+ result of \c pathp must be freed by the caller, the result of \c
+ cmdp must not be freed by the caller.
+*/
+static void parse_cmd_string( const wchar_t *str,
+ wchar_t **pathp,
+ wchar_t **cmdp )
+{
+ wchar_t *cmd, *path;
+
+ /* Get the path of the command */
+ path = get_filename( str );
+ if( path == 0 )
+ path = wcsdup(L"");
+
+ /* Make sure the path is not included in the command */
+ cmd = wcsrchr( str, L'/' );
+ if( cmd != 0 )
+ cmd++;
+ else
+ cmd = (wchar_t *)str;
+
+ *pathp=path;
+ *cmdp=cmd;
+}
+
+int complete_is_valid_option( const wchar_t *str,
+ const wchar_t *opt,
+ array_list_t *errors )
+{
+
+ complete_entry *i;
+ complete_entry_opt *o;
+ wchar_t *cmd, *path;
+ int found_match = 0;
+ int authorative = 1;
+
+ int opt_found=0;
+
+ hash_table_t gnu_match_hash;
+
+ int is_gnu_opt=0;
+ int is_old_opt=0;
+ int is_short_opt=0;
+ int is_gnu_exact=0;
+ int gnu_opt_len=0;
+
+ char *short_validated;
+ /*
+ Check some generic things like -- and - options.
+ */
+ switch( wcslen(opt ) )
+ {
+
+ case 0:
+ return 1;
+
+
+ case 1:
+ return opt[0] == L'-';
+
+ case 2:
+ if( wcscmp( L"--", opt ) == 0 )
+ return 1;
+ }
+
+ if( opt[0] != L'-' )
+ {
+ if( errors )
+ al_push( errors, wcsdup(L"Option does not begin with a '-'") );
+
+ return 0;
+ }
+
+
+
+ if( !(short_validated = malloc( wcslen( opt ) )))
+ die_mem();
+
+ memset( short_validated, 0, wcslen( opt ) );
+
+ hash_init( &gnu_match_hash,
+ &hash_wcs_func,
+ &hash_wcs_cmp );
+
+ is_gnu_opt = opt[1]==L'-';
+ if( is_gnu_opt )
+ {
+ wchar_t *opt_end = wcschr(opt, L'=' );
+ if( opt_end )
+ gnu_opt_len = (opt_end-opt)-2;
+ else
+ gnu_opt_len = wcslen(opt)-2;
+// fwprintf( stderr, L"Length %d optend %d\n", gnu_opt_len, opt_end );
+
+ }
+
+ parse_cmd_string( str, &path, &cmd );
+
+ /*
+ Make sure completions are loaded for the specified command
+ */
+ complete_load( cmd, 0 );
+
+ for( i=first_entry; i; i=i->next )
+ {
+ wchar_t *match = i->cmd_type?path:cmd;
+ const wchar_t *a;
+
+ if( !wildcard_match( match, i->cmd ) )
+ continue;
+
+ found_match = 1;
+
+ if( !i->authorative )
+ {
+ authorative = 0;
+ break;
+ }
+
+
+ if( is_gnu_opt )
+ {
+
+ for( o = i->first_option; o; o=o->next )
+ {
+ if( o->old_mode )
+ continue;
+ //fwprintf( stderr, L"Compare \'%ls\' against \'%ls\'\n", &opt[2], o->long_opt );
+ if( wcsncmp( &opt[2], o->long_opt, gnu_opt_len )==0)
+ {
+ //fwprintf( stderr, L"Found gnu match %ls\n", o->long_opt );
+ hash_put( &gnu_match_hash, o->long_opt, L"" );
+ if( (wcsncmp( &opt[2],
+ o->long_opt,
+ wcslen( o->long_opt) )==0) )
+ is_gnu_exact=1;
+ }
+ }
+ }
+ else
+ {
+ /* Check for old style options */
+ for( o = i->first_option; o; o=o->next )
+ {
+ if( !o->old_mode )
+ continue;
+
+
+ if( wcscmp( &opt[1], o->long_opt )==0)
+ {
+// fwprintf( stderr, L"Found old match %ls\n", o->long_opt );
+ opt_found = 1;
+ is_old_opt = 1;
+ break;
+ }
+
+ }
+
+ if( is_old_opt )
+ break;
+
+
+ for( a = &opt[1]; *a; a++ )
+ {
+
+ wchar_t *str_pos = wcschr(i->short_opt_str, *a);
+
+ if (str_pos )
+ {
+ if( *(str_pos +1)==L':' )
+ {
+ /*
+ This is a short option with an embedded argument,
+ call complete_is_valid_argument on the argument.
+ */
+ wchar_t nopt[3];
+ nopt[0]=L'-';
+ nopt[1]=opt[1];
+ nopt[2]=L'\0';
+
+ //fwprintf( stderr, L"Pos %d, shortopt %lc has argument\n", a-opt, *a );
+ short_validated[a-opt] =
+ complete_is_valid_argument( str, nopt, &opt[2]);
+ }
+ else
+ {
+ // fwprintf( stderr, L"Pos %d, shortopt %lc is ok\n", a-opt, *a );
+ short_validated[a-opt]=1;
+ }
+
+ }
+ }
+
+ }
+
+
+ }
+ free( path );
+
+ if( authorative )
+ {
+
+ if( !is_gnu_opt && !is_old_opt )
+ is_short_opt = 1;
+
+
+ if( is_short_opt )
+ {
+ int j;
+
+ opt_found=1;
+ for( j=1; j<wcslen(opt); j++)
+ {
+ if ( !short_validated[j])
+ {
+ // fwprintf( stderr, L"Pos %d, shortopt %lc is not ok\n", j, opt[j] );
+ if( errors )
+ {
+ wchar_t str[2];
+ str[0] = opt[j];
+ str[1]=0;
+ al_push( errors,
+ wcsdupcat2(L"Unknown option \'", str, L"\'", 0) );
+ }
+
+ opt_found = 0;
+ break;
+ }
+
+ }
+ }
+
+ if( is_gnu_opt )
+ {
+// fwprintf( stderr, L"Found %d matches\n", hash_get_count( &gnu_match_hash ) );
+ opt_found = is_gnu_exact || (hash_get_count( &gnu_match_hash )==1);
+ if( errors && !opt_found )
+ {
+ if( hash_get_count( &gnu_match_hash )==0)
+ {
+ al_push( errors,
+ wcsdupcat2(L"Unknown option \'", opt, L"\'", 0) );
+ }
+ else
+ {
+ al_push( errors,
+ wcsdupcat2(L"Multiple matches for option \'", opt, L"\'", 0) );
+ }
+ }
+ }
+ }
+
+ hash_destroy( &gnu_match_hash );
+ free( short_validated );
+
+ return (authorative && found_match)?opt_found:1;
+}
+
+int complete_is_valid_argument( const wchar_t *str,
+ const wchar_t *opt,
+ const wchar_t *arg )
+{
+ return 1;
+}
+
+/**
+ Use the mimedb command to look up a description for a given suffix
+*/
+static const wchar_t *complete_get_desc_suffix( const wchar_t *suff_orig )
+{
+
+ int len = wcslen(suff_orig );
+
+ wchar_t *suff;
+ wchar_t *pos;
+
+ if( len == 0 )
+ return COMPLETE_FILE_DESC;
+
+ if( !suffix_hash )
+ {
+ suffix_hash = malloc( sizeof( hash_table_t) );
+ if( !suffix_hash )
+ die_mem();
+ hash_init( suffix_hash, &hash_wcs_func, &hash_wcs_cmp );
+ }
+
+ suff = wcsdup(suff_orig);
+
+ for( pos=suff; *pos; pos++ )
+ {
+ if( wcschr( L"?;#~@&", *pos ) )
+ {
+ *pos=0;
+ break;
+ }
+ }
+
+ suff = expand_escape( suff, 0 );
+
+ wchar_t *desc = (wchar_t *)hash_get( suffix_hash, suff );
+
+ if( !desc )
+ {
+
+ wchar_t *cmd = wcsdupcat( SUFFIX_CMD_STR, suff );
+
+ if( cmd )
+ {
+
+ array_list_t l;
+ al_init( &l );
+
+ exec_subshell( cmd, &l );
+ free(cmd);
+
+ if( al_get_count( &l )>0 )
+ {
+ wchar_t *ln = (wchar_t *)al_get(&l, 0 );
+ if( wcscmp( ln, L"unknown" ) != 0 )
+ {
+ desc = wcsdupcat( COMPLETE_SEP_STR, ln);
+ /*
+ I have decided I prefer to have the description
+ begin in uppercase and the whole universe will just
+ have to accept it. Hah!
+ */
+ desc[1]=towupper(desc[1]);
+ }
+ }
+
+ al_foreach( &l, (void (*)(const void *))&free );
+ al_destroy( &l );
+ }
+
+ if( !desc )
+ {
+ desc = wcsdup(COMPLETE_FILE_DESC);
+ }
+
+ hash_put( suffix_hash, suff!=suff_orig?suff:wcsdup(suff), desc );
+ }
+ else
+ {
+ free( suff );
+ }
+
+ return desc;
+}
+
+
+const wchar_t *complete_get_desc( const wchar_t *filename )
+{
+ struct stat buf;
+ const wchar_t *desc = COMPLETE_FILE_DESC;
+
+
+ if( lwstat( filename, &buf )==0)
+ {
+ if( waccess( filename, X_OK ) == 0 )
+ {
+ desc = COMPLETE_EXEC_DESC;
+ }
+
+ if( S_ISCHR(buf.st_mode) )
+ desc= COMPLETE_CHAR_DESC;
+ else if( S_ISBLK(buf.st_mode) )
+ desc = COMPLETE_BLOCK_DESC;
+ else if( S_ISFIFO(buf.st_mode) )
+ desc = COMPLETE_FIFO_DESC;
+ else if( S_ISLNK(buf.st_mode))
+ {
+ struct stat buf2;
+ desc = COMPLETE_SYMLINK_DESC;
+
+ if( waccess( filename, X_OK ) == 0 )
+ desc = COMPLETE_EXEC_LINK_DESC;
+
+ if( wstat( filename, &buf2 ) == 0 )
+ {
+ if( S_ISDIR(buf2.st_mode) )
+ {
+ desc = L"/" COMPLETE_SYMLINK_DESC;
+ }
+ }
+ else
+ {
+ switch( errno )
+ {
+ case ENOENT:
+ desc = COMPLETE_ROTTEN_SYMLINK_DESC;
+ break;
+
+ case EACCES:
+ break;
+
+ default:
+ wperror( L"stat" );
+ break;
+ }
+ }
+ }
+ else if( S_ISSOCK(buf.st_mode))
+ desc= COMPLETE_SOCKET_DESC;
+ else if( S_ISDIR(buf.st_mode) )
+ desc= L"/" COMPLETE_DIRECTORY_DESC;
+ }
+/* else
+ {
+
+ switch( errno )
+ {
+ case EACCES:
+ break;
+
+ default:
+ fprintf( stderr, L"The following error happened on file %ls\n", filename );
+ wperror( L"lstat" );
+ break;
+ }
+ }
+*/
+ if( desc == COMPLETE_FILE_DESC )
+ {
+ wchar_t *suffix = wcsrchr( filename, L'.' );
+ if( suffix != 0 )
+ {
+ if( !wcsrchr( suffix, L'/' ) )
+ {
+ desc = complete_get_desc_suffix( suffix );
+ }
+ }
+ }
+
+ return desc;
+}
+
+/**
+ Copy any strings in possible_comp which have the specified prefix
+ to the list comp_out. The prefix may contain wildcards.
+
+ There are three ways to specify descriptions for each
+ completion. Firstly, if a description has already been added to the
+ completion, it is _not_ replaced. Secondly, if the desc_func
+ function is specified, use it to determine a dynamic
+ completion. Thirdly, if none of the above are available, the desc
+ string is used as a description.
+
+ \param comp_out the destination list
+ \param wc_escaped the prefix, possibly containing wildcards. The wildcard should not have been unescaped, i.e. '*' should be used for any string, not the ANY_STRING character.
+ \param desc the default description, used for completions with no embedded description
+ \param desc_func the function that generates a description for those completions witout an embedded description
+ \param possible_comp the list of possible completions to iterate over
+*/
+static void copy_strings_with_prefix( array_list_t *comp_out,
+ const wchar_t *wc_escaped,
+ const wchar_t *desc,
+ const wchar_t *(*desc_func)(const wchar_t *),
+ array_list_t *possible_comp )
+{
+ int i;
+ wchar_t *wc;
+
+ wc = expand_one( wcsdup(wc_escaped), EXPAND_SKIP_SUBSHELL | EXPAND_SKIP_WILDCARDS);
+ if(!wc)
+ return;
+
+ if( wc[0] == L'~' )
+ {
+ wc=expand_tilde(wc);
+ }
+
+// int str_len = wcslen( str );
+ for( i=0; i<al_get_count( possible_comp ); i++ )
+ {
+ wchar_t *next_str = (wchar_t *)al_get( possible_comp, i );
+// fwprintf( stderr, L"try wc %ls against %ls\n", wc, next_str );
+
+ wildcard_complete( next_str, wc, desc, desc_func, comp_out );
+ }
+ free( wc );
+
+}
+
+/**
+ If command to complete is short enough, substitute
+ the description with the whatis information for the executable.
+*/
+static void complete_cmd_desc( const wchar_t *cmd, array_list_t *comp )
+{
+ int i;
+ const wchar_t *cmd_start;
+ int cmd_len;
+ wchar_t *apropos_cmd=0;
+ array_list_t list;
+ hash_table_t lookup;
+ wchar_t *whatis_path = env_get( L"__fish_whatis_path" );
+ wchar_t *esc;
+
+ if( !cmd )
+ return;
+
+ cmd_start=wcschr(cmd, L'/');
+ if( !cmd_start )
+ cmd_start = cmd;
+
+ cmd_len = wcslen(cmd_start);
+
+ /*
+ Using apropos with a single-character search term produces far
+ to many results - require at least two characters if we don't
+ know the location of the whatis-database.
+ */
+ if( (cmd_len < 2) && (!whatis_path) )
+ return;
+
+ if( wildcard_has( cmd_start, 0 ) )
+ {
+ return;
+ }
+
+ esc = expand_escape( wcsdup(cmd_start), 1 );
+
+ if( esc )
+ {
+
+ if( whatis_path )
+ {
+ apropos_cmd =wcsdupcat2( L"grep ^/dev/null -F <", whatis_path, L" ", esc, 0 );
+ }
+ else
+ {
+ apropos_cmd = wcsdupcat( L"apropos ^/dev/null ", esc );
+ }
+ free(esc);
+
+ al_init( &list );
+ hash_init( &lookup, &hash_wcs_func, &hash_wcs_cmp );
+
+ /*
+ First locate a list of possible descriptions using a single
+ call to apropos or a direct search if we know the location
+ of the whatis database. This can take some time on slower
+ systems with a large set of manuals, but it should be ok
+ since apropos is only called once.
+ */
+ exec_subshell( apropos_cmd, &list );
+ /*
+ Then discard anything that is not a possible completion and put
+ the result into a hashtable with the completion as key and the
+ description as value.
+
+ Should be reasonably fast, since no memory allocations are needed.
+ */
+ for( i=0; i<al_get_count( &list); i++ )
+ {
+ wchar_t *el = (wchar_t *)al_get( &list, i );
+ wchar_t *key, *key_end, *val_begin;
+
+ if( !el )
+ continue;
+
+ //fwprintf( stderr, L"%ls\n", el );
+ if( wcsncmp( el, cmd_start, cmd_len ) != 0 )
+ continue;
+ //fwprintf( stderr, L"%ls\n", el );
+ key = el + cmd_len;
+
+ key_end = wcschr( el, L' ' );
+ if( !key_end )
+ {
+ key_end = wcschr( el, L'\t' );
+ if( !key_end )
+ {
+ continue;
+ }
+ }
+
+ *key_end = 0;
+ val_begin=key_end+1;
+
+ //fwprintf( stderr, L"Key %ls\n", el );
+
+ while( *val_begin != L'-' && *val_begin)
+ {
+ val_begin++;
+ }
+
+ if( !val_begin )
+ {
+ continue;
+ }
+
+ val_begin++;
+
+ while( *val_begin == L' ' || *val_begin == L'\t' )
+ {
+ val_begin++;
+ }
+
+ if( !*val_begin )
+ {
+ continue;
+ }
+
+ /*
+ And once again I make sure the first character is uppercased
+ because I like it that way, and I get to decide these
+ things.
+ */
+ val_begin[0]=towupper(val_begin[0]);
+ hash_put( &lookup, key, val_begin );
+ }
+
+ /*
+ Then do a lookup on every completion and if a match is found,
+ change to the new description.
+
+ This needs to do a reallocation for every description added, but
+ there shouldn't be that many completions, so it should be ok.
+ */
+ for( i=0; i<al_get_count(comp); i++ )
+ {
+ wchar_t *el = (wchar_t *)al_get( comp, i );
+ wchar_t *cmd_end = wcschr( el,
+ COMPLETE_SEP );
+ wchar_t *new_desc;
+
+ if( cmd_end )
+ *cmd_end = 0;
+
+ new_desc = (wchar_t *)hash_get( &lookup,
+ el );
+
+ if( new_desc )
+ {
+ wchar_t *new_el = wcsdupcat2( el,
+ COMPLETE_SEP_STR,
+ new_desc,
+ 0 );
+ al_set( comp, i, new_el );
+ free( el );
+ }
+ else
+ {
+ if( cmd_end )
+ *cmd_end = COMPLETE_SEP;
+ }
+ }
+ }
+
+ hash_destroy( &lookup );
+ al_foreach( &list,
+ (void(*)(const void *))&free );
+ al_destroy( &list );
+ free( apropos_cmd );
+}
+
+/**
+ Complete the specified command name. Search for executables in the
+ path, executables defined using an absolute path, functions,
+ builtins and directories for implicit cd commands.
+
+ \param cmd the command string to find completions for
+
+ B\param comp the list to add all completions to
+*/
+static void complete_cmd( const wchar_t *cmd,
+ array_list_t *comp )
+{
+ wchar_t *path;
+ wchar_t *path_cpy;
+ wchar_t *nxt_path;
+ wchar_t *state;
+ array_list_t possible_comp;
+ int i;
+ array_list_t tmp;
+ wchar_t *nxt_completion;
+
+ wchar_t *cdpath = env_get(L"CDPATH");
+ wchar_t *cdpath_cpy = wcsdup( cdpath?cdpath:L"." );
+
+ if( (wcschr( cmd, L'/') != 0) || (cmd[0] == L'~' ) )
+ {
+ array_list_t tmp;
+ al_init( &tmp );
+
+ expand_string( wcsdup(cmd),
+ comp,
+ ACCEPT_INCOMPLETE | EXECUTABLES_ONLY );
+ complete_cmd_desc( cmd, comp );
+ al_destroy( &tmp );
+ }
+ else
+ {
+ path = env_get(L"PATH");
+ path_cpy = wcsdup( path );
+
+ for( nxt_path = wcstok( path_cpy, ARRAY_SEP_STR, &state );
+ nxt_path != 0;
+ nxt_path = wcstok( 0, ARRAY_SEP_STR, &state) )
+ {
+ nxt_completion = wcsdupcat2( nxt_path,
+ (nxt_path[wcslen(nxt_path)-1]==L'/'?L"":L"/"),
+ cmd,
+ 0 );
+ if( ! nxt_completion )
+ continue;
+
+ al_init( &tmp );
+
+ expand_string( nxt_completion,
+ &tmp,
+ ACCEPT_INCOMPLETE |
+ EXECUTABLES_ONLY );
+
+ for( i=0; i<al_get_count(&tmp); i++ )
+ {
+ al_push( comp, al_get( &tmp, i ) );
+ }
+
+ al_destroy( &tmp );
+
+ }
+ free( path_cpy );
+
+ complete_cmd_desc( cmd, comp );
+
+ /*
+ These return the original strings - don't free them
+ */
+
+ al_init( &possible_comp );
+ function_get_names( &possible_comp, cmd[0] == L'_' );
+ copy_strings_with_prefix( comp, cmd, COMPLETE_FUNCTION_DESC, &function_get_desc, &possible_comp );
+ al_truncate( &possible_comp, 0 );
+
+ builtin_get_names( &possible_comp );
+ copy_strings_with_prefix( comp, cmd, COMPLETE_BUILTIN_DESC, &builtin_get_desc, &possible_comp );
+ al_destroy( &possible_comp );
+
+
+ }
+
+// fwprintf( stderr, L"cdpath %ls\n", cdpath_cpy );
+ /*
+ Tab complete implicit cd for directories in CDPATH
+ */
+ for( nxt_path = wcstok( cdpath_cpy, ARRAY_SEP_STR, &state );
+ nxt_path != 0;
+ nxt_path = wcstok( 0, ARRAY_SEP_STR, &state) )
+ {
+ int i;
+ array_list_t tmp;
+ wchar_t *nxt_completion=
+ wcsdupcat2( nxt_path,
+ (nxt_path[wcslen(nxt_path)-1]==L'/'?L"":L"/"),
+ cmd,
+ 0 );
+ if( ! nxt_completion )
+ continue;
+
+ al_init( &tmp );
+
+ expand_string( nxt_completion,
+ &tmp,
+ ACCEPT_INCOMPLETE | DIRECTORIES_ONLY );
+
+ for( i=0; i<al_get_count(&tmp); i++ )
+ {
+ wchar_t *nxt = (wchar_t *)al_get( &tmp, i );
+
+ wchar_t *desc = wcsrchr( nxt, COMPLETE_SEP );
+ int is_valid = (desc && (wcscmp(desc,
+ COMPLETE_DIRECTORY_DESC)==0));
+ if( is_valid )
+ {
+ al_push( comp, nxt );
+ }
+ else
+ {
+ free(nxt);
+ }
+ }
+
+ al_destroy( &tmp );
+
+ }
+
+ free( cdpath_cpy );
+}
+
+/**
+ Evaluate the argument list (as supplied by complete -a) and insert
+ any return matching completions. Matching is done using\c
+ copy_strings_with_prefix, meaning the completion may contain
+ wildcards. Logically, this is not always the right thing to do, but
+ I have yet to come up with a case where one would not want this.
+
+ \param str The string to complete.
+ \param args The list of option arguments to be evaluated.
+ \param desc Description of the completion
+ \param comp_out The list into which the results will be inserted
+*/
+static void complete_from_args( const wchar_t *str,
+ const wchar_t *args,
+ const wchar_t *desc,
+ array_list_t *comp_out )
+{
+ array_list_t possible_comp;
+ al_init( &possible_comp );
+
+ eval_args( args, &possible_comp );
+
+ debug( 3, L"desc is '%ls', %d long\n", desc, wcslen(desc) );
+
+ copy_strings_with_prefix( comp_out, str, desc, 0, &possible_comp );
+
+ al_foreach( &possible_comp, (void (*)(const void *))&free );
+ al_destroy( &possible_comp );
+}
+
+/**
+ Match against an old style long option
+*/
+static int param_match_old( complete_entry_opt *e,
+ wchar_t *optstr )
+{
+ return (optstr[0] == L'-') && (wcscmp( e->long_opt, &optstr[1] ) == 0);
+}
+
+/**
+ Match a parameter
+*/
+static int param_match( complete_entry_opt *e,
+ wchar_t *optstr )
+{
+ if( e->short_opt != L'\0' &&
+ e->short_opt == optstr[1] )
+ return 1;
+
+ if( !e->old_mode && (wcsncmp( L"--", optstr, 2 ) == 0 ))
+ {
+ if( wcscmp( e->long_opt, &optstr[2] ) == 0 )
+ {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ Test if a string is an option with an argument, like --color=auto or -I/usr/include
+*/
+static wchar_t *param_match2( complete_entry_opt *e,
+ wchar_t *optstr )
+{
+ if( e->short_opt != L'\0' && e->short_opt == optstr[1] )
+ return &optstr[2];
+ if( !e->old_mode && (wcsncmp( L"--", optstr, 2 ) == 0) )
+ {
+ int len = wcslen( e->long_opt );
+
+ if( wcsncmp( e->long_opt, &optstr[2],len ) == 0 )
+ {
+ if( optstr[len+2] == L'=' )
+ return &optstr[len+3];
+ }
+ }
+ return 0;
+}
+
+/**
+ Tests whether a short option is a viable completion
+*/
+static int short_ok( wchar_t *arg,
+ wchar_t nextopt,
+ wchar_t *allopt )
+{
+ wchar_t *ptr;
+
+ if( arg[0] != L'-')
+ return arg[0] == L'\0';
+ if( arg[1] == L'-' )
+ return 0;
+
+ if( wcschr( arg, nextopt ) != 0 )
+ return 0;
+
+ for( ptr = arg+1; *ptr; ptr++ )
+ {
+ wchar_t *tmp = wcschr( allopt, *ptr );
+ /* Unknown option */
+ if( tmp == 0 )
+ {
+ /*fwprintf( stderr, L"Unknown option %lc", *ptr );*/
+
+ return 0;
+ }
+
+ if( *(tmp+1) == L':' )
+ {
+/* fwprintf( stderr, L"Woot %ls", allopt );*/
+ return 0;
+ }
+
+ }
+
+ return 1;
+}
+
+void complete_load( wchar_t *cmd,
+ int reload )
+{
+ const wchar_t *path_var;
+ array_list_t path_list;
+ int i;
+ string_buffer_t path;
+ time_t *tm;
+
+ /*
+ First check that the specified completion hasn't already been loaded
+ */
+ if( !loaded_completions )
+ {
+ loaded_completions = malloc( sizeof( hash_table_t ) );
+ if( !loaded_completions )
+ {
+ die_mem();
+ }
+ hash_init( loaded_completions, &hash_wcs_func, &hash_wcs_cmp );
+ }
+
+ /*
+ Get modification time of file
+ */
+ tm = (time_t *)hash_get( loaded_completions, cmd );
+
+ /*
+ Return if already loaded and we are skipping reloading
+ */
+ if( !reload && tm )
+ return;
+
+ /*
+ Do we know where to look for completions?
+ */
+ path_var = env_get( L"fish_complete_path" );
+ if( !path_var )
+ return;
+
+ al_init( &path_list );
+
+ sb_init( &path );
+
+ expand_variable_array( path_var, &path_list );
+
+
+ /*
+ Iterate over path searching for suitable completion files
+ */
+ for( i=0; i<al_get_count( &path_list ); i++ )
+ {
+ struct stat buf;
+ wchar_t *next = (wchar_t *)al_get( &path_list, i );
+ sb_clear( &path );
+ sb_append2( &path, next, L"/", cmd, L".fish", 0 );
+ if( (wstat( (wchar_t *)path.buff, &buf )== 0) &&
+ (waccess( (wchar_t *)path.buff, R_OK ) == 0) )
+ {
+ if( !tm || (*tm != buf.st_mtime ) )
+ {
+ wchar_t *esc = expand_escape( wcsdup((wchar_t *)path.buff), 1 );
+ wchar_t *src_cmd = wcsdupcat( L". ", esc );
+
+/* if( tm )
+ debug( 0, L"Reload %ls completions, old time was %d, new time is %d",
+ cmd,
+ tm?*tm:-1,
+ buf.st_mtime);
+*/
+
+ if( !tm )
+ {
+ tm = malloc(sizeof(time_t));
+ if( !tm )
+ die_mem();
+ }
+
+ *tm = buf.st_mtime;
+ hash_put( loaded_completions,
+ intern( cmd ),
+ tm );
+
+
+ free( esc );
+
+ complete_remove( cmd, COMMAND, 0, 0 );
+
+ /*
+ Source the completion file for the specified completion
+ */
+ exec_subshell( src_cmd, 0 );
+ free(src_cmd);
+
+ break;
+ }
+ }
+ }
+
+ /*
+ If no file was found we insert a last modified time of Jan 1, 1970.
+ This way, the completions_path wont be searched over and over again
+ when reload is set to 0.
+ */
+ if( !tm )
+ {
+// debug( 0, L"Insert null timestamp for command %ls", cmd );
+
+ tm = malloc(sizeof(time_t));
+ if( !tm )
+ die_mem();
+
+ *tm = 0;
+ hash_put( loaded_completions, intern( cmd ), tm );
+ }
+
+ sb_destroy( &path );
+ al_foreach( &path_list, (void (*)(const void *))&free );
+
+ al_destroy( &path_list );
+}
+
+
+/**
+ Find completion for the argument str of command cmd_orig with
+ previous option popt. Insert results into comp_out. Return 0 if file
+ completion should be disabled, 1 otherwise.
+*/
+static int complete_param( wchar_t *cmd_orig,
+ wchar_t *popt,
+ wchar_t *str,
+ array_list_t *comp_out )
+{
+ complete_entry *i;
+ complete_entry_opt *o;
+
+ array_list_t matches;
+ wchar_t *cmd, *path;
+ int use_common=1, use_files=1;
+
+ parse_cmd_string( cmd_orig, &path, &cmd );
+
+ complete_load( cmd, 1 );
+
+ al_init( &matches );
+
+
+ for( i=first_entry; i; i=i->next )
+ {
+ wchar_t *match = i->cmd_type?path:cmd;
+
+ if( ( (!wildcard_match( match, i->cmd ) ) ) )
+ continue;
+
+/* wprintf( L"Found matching command %ls\n", i->cmd ); */
+
+ use_common=1;
+ if( str[0] == L'-' )
+ {
+ /* Check if we are entering a combined option and argument
+ * (like --color=auto or -I/usr/include) */
+ for( o = i->first_option; o; o=o->next )
+ {
+ wchar_t *arg;
+ if( (arg=param_match2( o, str ))!=0 && condition_test( o->condition ))
+ {
+
+/* wprintf( L"Use option with desc %ls\n", o->desc ); */
+ use_common &= ((o->result_mode & NO_COMMON )==0);
+ use_files &= ((o->result_mode & NO_FILES )==0);
+ complete_from_args( arg, o->comp, o->desc, comp_out );
+ }
+
+ }
+ }
+ else if( popt[0] == L'-' )
+ {
+ /* Check if the previous option has any specified
+ * arguments to match against */
+ int found_old = 0;
+
+ /*
+ If we are using old style long options, check for them
+ first
+ */
+ for( o = i->first_option; o; o=o->next )
+ {
+ if( o->old_mode )
+ {
+ if( param_match_old( o, popt ) && condition_test( o->condition ))
+ {
+ found_old = 1;
+ use_common &= ((o->result_mode & NO_COMMON )==0);
+ use_files &= ((o->result_mode & NO_FILES )==0);
+ complete_from_args( str, o->comp, o->desc, comp_out );
+ }
+ }
+ }
+
+ /*
+ No old style option matched, or we are not using old
+ style options. We check if any short (or gnu style
+ options do.
+ */
+ if( !found_old )
+ {
+ for( o = i->first_option; o; o=o->next )
+ {
+ /*
+ Gnu-style options with _optional_ arguments must
+ be specified as a single token, so that it can
+ be differed from a regular argument.
+ */
+ if( !o->old_mode && wcslen(o->long_opt) && !(o->result_mode & NO_COMMON) )
+ continue;
+
+ if( param_match( o, popt ) && condition_test( o->condition ))
+ {
+ use_common &= ((o->result_mode & NO_COMMON )==0);
+ use_files &= ((o->result_mode & NO_FILES )==0);
+ complete_from_args( str, o->comp, o->desc, comp_out );
+
+ }
+ }
+ }
+ }
+
+ if( use_common )
+ {
+
+ for( o = i->first_option; o; o=o->next )
+ {
+ /*
+ If this entry is for the base command,
+ check if any of the arguments match
+ */
+
+ if( !condition_test( o->condition ))
+ continue;
+
+
+ if( (o->short_opt == L'\0' ) && (o->long_opt[0]==L'\0'))
+ {
+ use_files &= ((o->result_mode & NO_FILES )==0);
+// debug( 0, L"Running argument command %ls", o->comp );
+ complete_from_args( str, o->comp, o->desc, comp_out );
+ }
+
+ if( wcslen(str) > 0 )
+ {
+ /*
+ Check if the short style option matches
+ */
+ if( o->short_opt != L'\0' &&
+ short_ok( str, o->short_opt, i->short_opt_str ) )
+ {
+ wchar_t *next_opt =
+ malloc( sizeof(wchar_t)*(2 + wcslen(o->desc)));
+ if( !next_opt )
+ die_mem();
+
+ next_opt[0]=o->short_opt;
+ next_opt[1]=L'\0';
+ wcscat( next_opt, o->desc );
+ al_push( comp_out, next_opt );
+ }
+
+ /*
+ Check if the long style option matches
+ */
+ if( o->long_opt[0] != L'\0' )
+ {
+ string_buffer_t whole_opt;
+ sb_init( &whole_opt );
+ sb_append2( &whole_opt, o->old_mode?L"-":L"--", o->long_opt, 0 );
+
+ if( wcsncmp( str, (wchar_t *)whole_opt.buff, wcslen(str) )==0)
+ {
+ /*
+ If the option requires arguments, add
+ option with an appended '=' . If the
+ option does not accept arguments, add
+ option. If option accepts but does not
+ require arguments, add both.
+ */
+
+ if( o->old_mode || !(o->result_mode & NO_COMMON ) )
+ {
+ al_push( comp_out,
+ wcsdupcat(&((wchar_t *)whole_opt.buff)[wcslen(str)], o->desc) );
+// fwprintf( stderr, L"Add without param %ls\n", o->long_opt );
+ }
+
+ if( !o->old_mode && ( wcslen(o->comp) || (o->result_mode & NO_COMMON ) ) )
+ {
+ al_push( comp_out,
+ wcsdupcat2(&((wchar_t *)whole_opt.buff)[wcslen(str)], L"=", o->desc, 0) );
+// fwprintf( stderr, L"Add with param %ls\n", o->long_opt );
+ }
+
+// fwprintf( stderr, L"Matching long option %ls\n", o->long_opt );
+ }
+ sb_destroy( &whole_opt );
+
+ }
+ }
+ }
+ }
+ }
+ free( path );
+ return use_files;
+}
+
+/**
+ Perform file completion on the specified string
+*/
+static void complete_param_expand( wchar_t *str,
+ array_list_t *comp_out,
+ int do_file )
+{
+ wchar_t *comp_str;
+ if( (wcsncmp( str, L"--", 2 )) == 0 && (comp_str = wcschr(str, L'=' ) ) )
+ {
+ comp_str++;
+ }
+ else
+ comp_str = str;
+
+// fwprintf( stderr, L"expand_string( \"%ls\", [list], ACCEPT_INCOMPLETE | %ls )\n", comp_str, do_file?L"0":L"EXPAND_SKIP_WILDCARDS" );
+
+ expand_string( wcsdup(comp_str), comp_out, ACCEPT_INCOMPLETE | (do_file?0:EXPAND_SKIP_WILDCARDS) );
+}
+
+/**
+ Complete the specified string as an environment variable
+*/
+static int complete_variable( const wchar_t *var,
+ array_list_t *comp )
+{
+ int i;
+ int varlen = wcslen( var );
+ int res = 0;
+
+ array_list_t names;
+ al_init( &names );
+ env_get_names( &names, 0 );
+/* wprintf( L"Search string %ls\n", var );*/
+
+/* wprintf( L"Got %d variables\n", al_get_count( &names ) );*/
+ for( i=0; i<al_get_count( &names ); i++ )
+ {
+ wchar_t *name = (wchar_t *)al_get( &names, i );
+ int namelen = wcslen( name );
+ if( varlen > namelen )
+ continue;
+
+/* wprintf( L"Try %ls\n", name );*/
+
+ if( wcsncmp( var, name, varlen) == 0 )
+ {
+ wchar_t *value = expand_escape_variable( env_get( name ));
+ wchar_t *blarg;
+ /*
+ What should the description of the variable be?
+
+ If the variable doesn't have a value, or of the value is
+ really long, we just describe it as 'Variable', but if
+ the value is 1..16 characters long, we describe it as
+ 'Variable: VALUE'.
+ */
+/* if( wcslen(value) < 1 || wcslen(value) > 16 )
+ {
+ blarg = wcsdupcat( &name[varlen], COMPLETE_VAR_DESC );
+ }
+ else
+ {*/
+ blarg = wcsdupcat2( &name[varlen], COMPLETE_VAR_DESC_VAL, value, 0 );
+// }
+
+ if( blarg )
+ {
+ res =1;
+ al_push( comp, blarg );
+ }
+ free( value );
+
+ }
+ }
+
+ al_destroy( &names );
+ return res;
+}
+
+/**
+ Search the specified string for the \$ sign, try to complete as an environment variable
+*/
+static int try_complete_variable( const wchar_t *cmd,
+ array_list_t *comp )
+{
+ int len = wcslen( cmd );
+ int i;
+
+ for( i=len-1; i>=0; i-- )
+ {
+ if( cmd[i] == L'$' )
+ {
+/* wprintf( L"Var prefix \'%ls\'\n", &cmd[i+1] );*/
+ return complete_variable( &cmd[i+1], comp );
+ }
+ if( !isalnum(cmd[i]) && cmd[i]!=L'_' )
+ {
+ return 0;
+ }
+
+ }
+ return 0;
+
+
+}
+
+/**
+ Try to complete the specified string as a username. This is used by ~USER type expansion.
+*/
+
+static int try_complete_user( const wchar_t *cmd,
+ array_list_t *comp )
+{
+ const wchar_t *first_char=0;
+ const wchar_t *p;
+ int mode = 0;
+ int res = 0;
+
+
+ for( p=cmd; *p; p++ )
+ {
+ switch( mode )
+ {
+ /*Between parameters*/
+ case 0:
+ switch( *p )
+ {
+ case L'\"':
+ mode=2;
+ p++;
+ first_char = p;
+ break;
+ case L' ':
+ case L'\t':
+ case L'\n':
+ case L'\r':
+ break;
+ default:
+ mode=1;
+ first_char = p;
+ }
+ break;
+ /*Inside non-quoted parameter*/
+ case 1:
+ switch( *p )
+ {
+ case L' ':
+ case L'\t':
+ case L'\n':
+ case L'\r':
+ mode = 0;
+ break;
+ }
+ break;
+ case 2:
+ switch( *p )
+ {
+ case L'\"':
+ if( *(p-1) != L'\\' )
+ mode =0;
+ break;
+ }
+ break;
+ }
+ }
+
+ if( mode != 0 )
+ {
+ if( *first_char ==L'~' )
+ {
+ const wchar_t *user_name = first_char+1;
+ wchar_t *name_end = wcschr( user_name, L'~' );
+ if( name_end == 0 )
+ {
+ struct passwd *pw;
+ int name_len = wcslen( user_name );
+
+/* wprintf( L"Complete name \'%ls\'\n", user_name );*/
+
+ setpwent();
+
+ while((pw=getpwent()) != 0)
+ {
+/* wprintf( L"Try %ls\n", pw->pw_name );*/
+ wchar_t *pw_name = str2wcs( pw->pw_name );
+ if( pw_name )
+ {
+ if( wcsncmp( user_name, pw_name, name_len )==0 )
+ {
+ wchar_t *blarg = wcsdupcat2( &pw_name[name_len],
+ L"/",
+ COMPLETE_USER_DESC,
+ 0 );
+ if( blarg != 0 )
+ {
+ al_push( comp, blarg );
+ res=1;
+ }
+ }
+ free( pw_name );
+ }
+ }
+
+ endpwent();
+
+ }
+
+ }
+
+
+ }
+
+ return res;
+}
+
+void complete( const wchar_t *cmd,
+ array_list_t *comp )
+{
+ wchar_t *begin, *end, *prev_begin, *prev_end, *buff;
+ tokenizer tok;
+ wchar_t *current_token=0, *current_command=0, *prev_token=0;
+
+ int on_command=0;
+ int pos;
+
+ int old_error_max = error_max;
+ int done=0;
+
+ error_max=0;
+
+ /**
+ If we are completing a variable name or a tilde expantion user
+ name, we do that and return. No need for any other competions.
+ */
+
+ if( try_complete_variable( cmd, comp ))
+ {
+ done=1;
+
+ }
+ else if( try_complete_user( cmd, comp ))
+ {
+ done=1;
+ }
+
+
+ /*
+ Set on_command to true if cursor is over a command, and set the
+ name of the current command, and various other parsing to find
+ out what we should complete, and how it should be completed.
+ */
+
+ if( !done )
+ {
+ reader_current_subshell_extent( &begin, &end );
+
+ if( !begin )
+ done=1;
+ }
+
+ if( !done )
+ {
+
+ pos = reader_get_cursor_pos()-(begin-reader_get_buffer());
+
+ buff = wcsndup( begin, end-begin );
+
+ if( !buff )
+ done=1;
+ }
+
+
+ if( !done )
+ {
+ int had_cmd=0;
+ int end_loop=0;
+
+ tok_init( &tok, buff, TOK_ACCEPT_UNFINISHED );
+
+ free( buff );
+
+ while( !end_loop )
+ {
+ switch( tok_last_type( &tok ) )
+ {
+ case TOK_STRING:
+ if( !had_cmd )
+ {
+ if( parser_is_subcommand( tok_last( &tok ) ) )
+ break;
+
+ current_command = wcsdup( tok_last( &tok ) );
+
+ on_command = (pos <= tok_get_pos( &tok) + wcslen( tok_last( &tok ) ) );
+ had_cmd=1;
+ }
+ break;
+
+ case TOK_END:
+ case TOK_PIPE:
+ case TOK_BACKGROUND:
+ had_cmd=0;
+ break;
+
+
+ case TOK_ERROR:
+ end_loop=1;
+ break;
+
+ }
+ if( tok_get_pos( &tok ) >= pos )
+ end_loop=1;
+
+ tok_next( &tok );
+
+ }
+
+ tok_destroy( &tok );
+
+
+ /*
+ Get the string to complete
+ */
+
+ reader_current_token_extent( &begin, &end, &prev_begin, &prev_end );
+
+ current_token = wcsndup( begin, reader_get_cursor_pos()-(begin-reader_get_buffer()) );
+ prev_token = wcsndup( prev_begin, prev_end - prev_begin );
+
+// fwprintf( stderr, L"on_command: %d, %ls %ls\n", on_command, current_compmand, current_token );
+
+ if( current_token && current_command && prev_token )
+ {
+
+ if( on_command )
+ {
+ /* Complete command filename */
+ complete_cmd( current_token,
+ comp );
+ }
+ else
+ {
+ /*
+ Complete parameter. Parameter expansion should be
+ performed against both the globbed and the unglobbed
+ version of the string, so we create a list containing
+ all possible versions of the string that is to be
+ expanded. This is potentially very slow.
+ */
+
+ int cmd_ok = 1;
+ int do_file = 1;
+
+ wchar_t *end_str = current_token;
+
+ /*
+ If the command is an function, we use the
+ completions of the first command in the function
+ and hope for the best...
+ */
+ if( function_exists(current_command ) )
+ {
+ tokenizer tok2;
+ tok_init( &tok2, function_get_definition( current_command), 0 );
+ wchar_t *new_cmd=0;
+
+ switch( tok_last_type( &tok2 ) )
+ {
+ case TOK_STRING:
+ new_cmd = expand_one( wcsdup(tok_last( &tok2 )),
+ EXPAND_SKIP_SUBSHELL | EXPAND_SKIP_VARIABLES );
+ break;
+ default:
+ cmd_ok = 0;
+ break;
+ }
+
+ tok_destroy( &tok2 );
+
+ if( cmd_ok )
+ {
+ do_file &= complete_param( new_cmd,
+ prev_token,
+ end_str,
+ comp );
+ }
+ if( new_cmd != 0 )
+ free(new_cmd);
+ }
+ // fwprintf( stderr, L"complete_param with end_str %ls\n", end_str );
+ do_file &= complete_param( current_command, prev_token, end_str, comp );
+
+ complete_param_expand( current_token, comp, do_file );
+ }
+ }
+
+ free( current_token );
+ free( current_command );
+ free( prev_token );
+
+ }
+
+ error_max=old_error_max;
+ condition_cache_clear();
+
+}
+
+static void append_switch( string_buffer_t *out,
+ const wchar_t *opt,
+ const wchar_t *argument )
+{
+ wchar_t *esc;
+
+ if( !argument || argument==L"" )
+ return;
+
+ esc = expand_escape( wcsdup(argument), 1 );
+ sb_printf( out, L" --%ls %ls", opt, esc );
+ free(esc);
+}
+
+void complete_print( string_buffer_t *out )
+{
+ complete_entry *e;
+
+ for( e = first_entry; e; e=e->next )
+ {
+ complete_entry_opt *o;
+ for( o= e->first_option; o; o=o->next )
+ {
+ wchar_t *modestr[] =
+ {
+ L"",
+ L" --no-files",
+ L" --require-parameter",
+ L" --exclusive"
+ }
+ ;
+
+ sb_printf( out,
+ L"complete%ls",
+ modestr[o->result_mode] );
+
+ append_switch( out,
+ e->cmd_type?L"path":L"command",
+ e->cmd );
+
+
+ if( o->short_opt != 0 )
+ {
+ sb_printf( out,
+ L" --short-option '%lc'",
+ o->short_opt );
+ }
+
+
+ append_switch( out,
+ o->old_mode?L"old-option":L"long-option",
+ o->long_opt );
+
+ append_switch( out,
+ L"description",
+ o->desc );
+
+ append_switch( out,
+ L"arguments",
+ o->comp );
+
+ append_switch( out,
+ L"condition",
+ o->condition );
+
+ sb_printf( out, L"\n" );
+ }
+ }
+}
+