[gnu.bash.bug] Patch for Bash-1.02 - File name completion ignore

gnb@melba.bby.oz.au (Gregory Bond) (08/21/89)

I hope this is a good spot for enhancements.

One of the useful things in SunOS 3.5 csh is a variable called
"fignore".  When set to an array of values, it will ignore filename
completions for files ending in those suffixes, assuming at least one
potential completion does not match.  In practice, I use
	set fignore = ( .o .out ~ .BAK )
so "vi fi<ESC>" gets "file.c" and not "file.c~" or "file.o", which is
the required result about 99% of the time.

The enclosed patches add the equivalent facility to the BASH filename
completion function.  A line like
	FIGNORE=".o:~:.out"
in .bashrc will make the completer ignore those extensions provided
one potential match is acceptable.  In the case of none or multiple
acceptable filenames, the ignore function does nothing, reverting to
the default behaviour of bash in these circumstances (printing the
maximal substring and beeping).  Only when there is exactly one
acceptable match, and when real completion (<TAB>) and not listing
(M-?) is requested, does this feature operate, and only on filenames.

The mechanism used to implement this is quite general, and could
easily be extended to other uses if anyone has any bright ideas for
"post-processing" of completion lists.

It has been runing here for a few days, and I have been beating on it
quite heavily. It hasn't crashed for a few days so I believe it to be
reliable and free of memory leaks and malloc blues.

A small nit - defining MAINTAINER doesn't effect whe mail generated on
noticing bugs.  It should.  Bfox saw a few of these before I fixed it.

Brian - you are welcome to include this in 1.03.

If only !^ would work!

Share and enjoy.

Greg.
--
Gregory Bond, Burdett Buckeridge & Young Ltd, Melbourne, Australia
Internet: gnb@melba.bby.oz.au    non-MX: gnb%melba.bby.oz@uunet.uu.net
Uucp: {uunet,pyramid,ubc-cs,ukc,mcvax,prlb2,nttlab...}!munnari!melba.bby.oz!gnb

*** bashline.c.orig	Fri Jun 30 05:43:02 1989
--- bashline.c	Mon Aug 21 14:59:31 1989
***************
*** 29,38 ****
--- 29,39 ----
  initialize_readline ()
  {
    char **attempt_shell_completion ();
    int shell_expand_line ();
    char *get_string_value ();
+   void filename_completion_ignore (); 
  
    rl_terminal_name = get_string_value ("TERM");
    rl_instream = stdin, rl_outstream = stdout;
    rl_special_prefixes = "$@%";
  
***************
*** 39,48 ****
--- 40,54 ----
    /* Bind up our special shell functions. */
    rl_bind_key (META(CTRL('E')), shell_expand_line);
  
    /* Tell the completer that we want a crack first. */
    rl_attempted_completion_function = (Function *)attempt_shell_completion;
+ 
+   /* Tell the filename completer we want a chance to ignore some names */
+   rl_filename_completion_ignore_function = 
+        (Function *)filename_completion_ignore;
+   
  }
  
  /* **************************************************************** */
  /*								    */
  /*			     Readline Stuff  			    */
***************
*** 597,603 ****
--- 603,727 ----
  	 will return NULL, since it just did. */
        fprintf (rl_outstream, "\n\r");
        pre_process_line (rl_line_buffer, 1, 0);
        rl_forced_update_display ();
      }
+ }
+ 
+ /******************************************************
+  *
+  * Filename completion ignore.  Emulate the "fignore" facility of
+  * tcsh/SunOS csh.  If FIGNORE is set, then don't match files with the
+  * given suffixes.  If only one of the possabilities has an acceptable
+  * suffix, delete the others, else just return and let the completer
+  * signal an error.  It is called by the completer when real
+  * completions are done on filenames by the completer's internal
+  * function, not for completion lists (M-?) and not on "other"
+  * completion types, such as hostnames or commands.
+  * 
+  * It is passed a NULL-terminated array of (char *)'s that must be
+  * free()'d if they are deleted.  The first element (names[0]) is the
+  * least-common-denominator string of the matching patterns (i.e.
+  * u<TAB> produces names[0] = "und", names[1] = "under.c", names[2] =
+  * "undun.c", name[3] = NULL). 
+  */
+ 
+ struct ign {
+   char *val;
+   int len;
+ };
+ 
+ static struct ign *ignores;	/* Store the ignore strings here */
+ static int num_ignores;		/* How many are there? */
+ static char *last_fignore;	/* Last value of fignore - cached for speed */
+ extern char *extract_colon_unit(), *get_string_value();
+ 
+ static void setup_ignore_patterns()
+ {
+   int numitems, maxitems, ptr;
+   char *colon_bit;
+   struct ign *p;
+   
+   char *this_fignore = get_string_value("FIGNORE");
+ 
+   if (this_fignore && last_fignore && 
+                 strcmp(this_fignore, last_fignore) == 0 ||
+       !this_fignore && !last_fignore) {
+     return;			/* Nothing Changed */
+   }
+ 
+   /* Oops.  FIGNORE has changed.  Re-parse it */
+   num_ignores = 0;
+   if (ignores) {
+     for (p = ignores; p->val; p++) free(p->val);
+     free(ignores);
+     ignores = (struct ign*)NULL;
+   }
+   if (last_fignore) free(last_fignore);
+   last_fignore = savestring(this_fignore);
+   
+   if (!this_fignore)		/* User turned off fignore */
+     return;
+ 
+   numitems = maxitems = ptr = 0;
+ 
+   while (colon_bit = extract_colon_unit(this_fignore, &ptr)) {
+     if (numitems + 1 > maxitems) { /* More room! */
+       if (!ignores) 
+ 	ignores = (struct ign *)xmalloc((maxitems = 10) * sizeof (struct ign));
+       else
+ 	ignores = (struct ign *)xrealloc(ignores,
+ 				    (maxitems += 10) * sizeof (struct ign));
+     }
+     ignores[numitems].val = colon_bit;
+     ignores[numitems].len = strlen(colon_bit);
+     numitems++;
+   }
+   ignores[numitems].val = NULL;
+   num_ignores = numitems;
+ }
+ 
+ 
+ static int name_is_acceptable(name)
+      char *name;
+ {
+   struct ign *p;
+   int nlen = strlen(name);
+ 
+   for (p = ignores; p->val; p++) 
+     if (nlen > p->len && p->len > 0 && 
+ 	strcmp(p->val, &name[nlen - p->len]) == 0)
+       return 0;
+   return 1;
+ }
+ 
+ static void filename_completion_ignore(names)
+      char **names;
+ {
+   char **p;
+   int idx;
+ 
+   setup_ignore_patterns();
+   if (!num_ignores) return;
+   
+   for (p = names + 1, idx = -1; *p; p++)
+     if (name_is_acceptable(*p)) {
+       if (idx == -1)		/* First match found */
+ 	idx = p - names;
+       else return;		/* Too many matches */
+     }
+   
+   if (idx == -1)		
+     /* None acceptable - let completer handle it. */
+     return;
+ 
+   /* Delete all non-matching elements */
+   free(names[0]); 
+   for (p = names + 1; *p; p++) {
+     if (idx == p - names)
+       names[0] = *p;		/* Save this one */
+     else 
+       free(*p);
+     *p = NULL;
+   }
  }
  
*** lib/readline.c.orig	Fri Jul  7 03:36:14 1989
--- lib/readline.c	Mon Aug 21 14:59:34 1989
***************
*** 2284,2293 ****
--- 2284,2304 ----
  /* List of characters that are word break characters, but should be left
     in TEXT when it is passed to the completion function.  The shell uses
     this to help determine what kind of completing to do. */
  char *rl_special_prefixes = (char )NULL;
  
+ /* This function, if defined, is called by the completer when real
+    filename completion is done, after all the matching names have been
+    generated. It is passed a (char**) known as matches in the code below.
+    It consists of a NULL-terminated array of pointers to potential
+    matching strings.  The 1st element (matches[0]) is the maximal
+    substring that is common to all matches. This function can re-arrange
+    the list of matches as required, but all elements of the array must be
+    free()'d if they are deleted. The main intent of this function is
+    to implement FIGNORE a la SunOS csh. */
+ Function *rl_filename_completion_ignore_function = (Function *)NULL;
+ 
  /* Complete the word at or before point.  If WHAT_TO_DO is '?', then list
     the possible completions.  Otherwise, insert the completion into the
     line. */
  rl_complete_internal (what_to_do)
       int what_to_do;
***************
*** 2337,2348 ****
    if (rl_attempted_completion_function)
      {
        matches =
  	(char **)(*rl_attempted_completion_function) (text, start, end);
  
!       if (matches)
  	goto after_usual_completion;
      }
  
    matches = completion_matches (text, our_func, start, end);
  
   after_usual_completion:
--- 2348,2361 ----
    if (rl_attempted_completion_function)
      {
        matches =
  	(char **)(*rl_attempted_completion_function) (text, start, end);
  
!       if (matches) {
! 	our_func = (Function *)NULL;
  	goto after_usual_completion;
+       } 
      }
  
    matches = completion_matches (text, our_func, start, end);
  
   after_usual_completion:
***************
*** 2371,2380 ****
--- 2384,2401 ----
  	}
  
        switch (what_to_do)
  	{
  	case TAB:
+ 	  /* If we are matching filenames, then here is our chance to
+ 	     do clever processing by re-examining the list.  Call the
+ 	     ignore function with the array as a parameter.  It can
+ 	     munge the array, deleting matches as it desires */
+ 	  if (rl_filename_completion_ignore_function && 
+ 	      our_func == (int (*)())filename_completion_function)
+ 	    (void)(*rl_filename_completion_ignore_function)(matches);
+ 	  
  	  rl_delete_text (start, rl_point);
  	  rl_point = start;
  	  rl_insert_text (matches[0]);
  
  	  /* If there are more matches, ring the bell to indicate.
*** lib/readline.h.orig	Fri Jun 30 06:07:03 1989
--- lib/readline.h	Fri Aug 18 15:07:51 1989
***************
*** 138,147 ****
--- 138,155 ----
  /* Pointer to the generator function for completion_matches ().
     NULL means to use filename_entry_function (), the default filename
     completer. */
  Function *rl_completion_entry_function;
  
+ /* If set, call this function after FILENAME completion only to get a
+    chance to delete unwanted matches (e.g. from ignored suffixes).  It
+    is called then the completion is requested (TAB), not listed (?).
+    One argument is a null-terminated array of (char *)'s (known as
+    "matches" in the readline package).  These char*'s must be
+    free()'d if they are deleted. */
+ Function *rl_filename_completion_ignore_function;
+ 
  /* Pointer to alternative function to create matches.
     Function is called with TEXT, START, and END.
     START and END are indices in RL_LINE_BUFFER saying what the boundaries
     of TEXT are.
     If this function exists and returns NULL then call the value of