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